From f56829ffbdbf714938d734a132afa6db329e5d48 Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Thu, 31 Oct 2024 08:51:19 -0400 Subject: [PATCH] perf(netlify-legacy): exclude static paths from SSR function Backporting https://github.com/unjs/nitro/pull/2822 to the netlify-legacy preset. Since it's using "v1" Netlify Functions (https://docs.netlify.com/functions/lambda-compatibility), it doesn't have access to `excludedPath` and `preferStatic`, so we implement this with redirects. --- src/presets/netlify/legacy/preset.ts | 2 +- src/presets/netlify/legacy/utils.ts | 29 ++++++++++++++++++++++++++-- test/presets/netlify-legacy.test.ts | 4 ++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/presets/netlify/legacy/preset.ts b/src/presets/netlify/legacy/preset.ts index 9e5af03997..e56f31e0c7 100644 --- a/src/presets/netlify/legacy/preset.ts +++ b/src/presets/netlify/legacy/preset.ts @@ -24,7 +24,7 @@ const netlify = defineNitroPreset( }, async compiled(nitro: Nitro) { await writeHeaders(nitro); - await writeRedirects(nitro); + await writeRedirects(nitro, "/.netlify/functions/server"); if (nitro.options.netlify) { const configPath = join( diff --git a/src/presets/netlify/legacy/utils.ts b/src/presets/netlify/legacy/utils.ts index 107f0c0d87..f85d70632d 100644 --- a/src/presets/netlify/legacy/utils.ts +++ b/src/presets/netlify/legacy/utils.ts @@ -2,7 +2,25 @@ import { existsSync, promises as fsp } from "node:fs"; import type { Nitro } from "nitropack/types"; import { join } from "pathe"; -export async function writeRedirects(nitro: Nitro) { +export function generateCatchAllRedirects( + nitro: Nitro, + catchAllPath?: string +): string { + if (!catchAllPath) return ""; + + return [ + // e.g.: /_nuxt/* /_nuxt/:splat 200 + // Because of Netlify CDN shadowing + // (https://docs.netlify.com/routing/redirects/rewrites-proxies/#shadowing), + // this config avoids function invocations for all static paths, even 404s. + ...getStaticPaths(nitro).map( + (path) => `${path} ${path.replace("/*", "/:splat")} 200` + ), + `/* ${catchAllPath} 200`, + ].join("\n"); +} + +export async function writeRedirects(nitro: Nitro, catchAllPath?: string) { const redirectsPath = join(nitro.options.output.publicDir, "_redirects"); const staticFallback = existsSync( join(nitro.options.output.publicDir, "404.html") @@ -11,7 +29,7 @@ export async function writeRedirects(nitro: Nitro) { : ""; let contents = nitro.options.static ? staticFallback - : "/* /.netlify/functions/server 200"; + : generateCatchAllRedirects(nitro, catchAllPath); const rules = Object.entries(nitro.options.routeRules).sort( (a, b) => a[0].split(/\/(?!\*)/).length - b[0].split(/\/(?!\*)/).length @@ -103,6 +121,13 @@ export async function writeHeaders(nitro: Nitro) { await fsp.writeFile(headersPath, contents); } +export function getStaticPaths(nitro: Nitro): string[] { + const publicAssets = nitro.options.publicAssets.filter( + (dir) => dir.fallthrough !== true && dir.baseURL && dir.baseURL !== "/" + ); + return ["/.netlify/*", ...publicAssets.map((dir) => `${dir.baseURL}/*`)]; +} + export function deprecateSWR(nitro: Nitro) { if (nitro.options.future.nativeSWR) { return; diff --git a/test/presets/netlify-legacy.test.ts b/test/presets/netlify-legacy.test.ts index bbd68c48f2..24abb3fa2a 100644 --- a/test/presets/netlify-legacy.test.ts +++ b/test/presets/netlify-legacy.test.ts @@ -70,6 +70,10 @@ describe("nitro:preset:netlify-legacy", async () => { /rules/isr-ttl/* /.netlify/builders/server 200 /rules/isr/* /.netlify/builders/server 200 /rules/dynamic /.netlify/functions/server 200 + /.netlify/* /.netlify/:splat 200 + /build/* /build/:splat 200 + /with-default-fallthrough/* /with-default-fallthrough/:splat 200 + /nested/no-fallthrough/* /nested/no-fallthrough/:splat 200 /* /.netlify/functions/server 200" `); });