diff --git a/src/presets/vercel/utils.ts b/src/presets/vercel/utils.ts index 4f6d3158e3..c1098d3ebd 100644 --- a/src/presets/vercel/utils.ts +++ b/src/presets/vercel/utils.ts @@ -5,6 +5,7 @@ import { writeFile } from "../_utils/fs.ts"; import type { Nitro, NitroRouteRules, ProxyRuleOptions } from "nitro/types"; import { basename, dirname, relative, resolve } from "pathe"; import { Router } from "../../routing.ts"; +import { escapeRegExp } from "../../utils/regex.ts"; import { joinURL, withLeadingSlash, withoutLeadingSlash } from "ufo"; import type { PrerenderFunctionConfig, @@ -223,6 +224,11 @@ function generateBuildConfig(nitro: Nitro, o11Routes?: ObservabilityRoute[]) { .map(([path]) => path) ); + const publicAssetRouteSources = nitro.options.publicAssets + .filter((asset) => !asset.fallthrough) + .map((asset) => joinURL(nitro.options.baseURL, asset.baseURL || "/")) + .map((baseURL) => joinURL(escapeRegExp(baseURL), "(.*)")); + const config = defu(nitro.options.vercel?.config, { version: 3, framework: { @@ -301,17 +307,22 @@ function generateBuildConfig(nitro: Nitro, o11Routes?: ObservabilityRoute[]) { ] : []), // Public asset rules - ...nitro.options.publicAssets - .filter((asset) => !asset.fallthrough) - .map((asset) => joinURL(nitro.options.baseURL, asset.baseURL || "/")) - .map((baseURL) => ({ - src: baseURL + "(.*)", - headers: { - "cache-control": "public,max-age=31536000,immutable", - }, - continue: true, - })), + ...publicAssetRouteSources.map((src) => ({ + src, + headers: { + "cache-control": "public,max-age=31536000,immutable", + }, + continue: true, + })), { handle: "filesystem" }, + ...publicAssetRouteSources.map((src) => ({ + src, + status: 404, + headers: { + "cache-control": "no-store", + }, + continue: false, + })), ], } as VercelBuildConfigV3); diff --git a/test/presets/vercel.test.ts b/test/presets/vercel.test.ts index ae4d31e71a..d3fab8aa8b 100644 --- a/test/presets/vercel.test.ts +++ b/test/presets/vercel.test.ts @@ -167,11 +167,19 @@ describe("nitro:preset:vercel:web", async () => { "headers": { "cache-control": "public,max-age=31536000,immutable", }, - "src": "/build(.*)", + "src": "/build/(.*)", }, { "handle": "filesystem", }, + { + "continue": false, + "headers": { + "cache-control": "no-store", + }, + "src": "/build/(.*)", + "status": 404, + }, { "dest": "/rules/_/noncached/cached-isr?__isr_route=$__isr_route", "src": "(?<__isr_route>/rules/_/noncached/cached)", @@ -454,6 +462,24 @@ describe("nitro:preset:vercel:web", async () => { `); }); + it("should not cache missing immutable public assets", async () => { + const config = await fsp + .readFile(resolve(ctx.outDir, "config.json"), "utf8") + .then((r) => JSON.parse(r)); + const filesystemIndex = config.routes.findIndex( + (route: { handle?: string }) => route.handle === "filesystem" + ); + + expect(config.routes[filesystemIndex + 1]).toEqual({ + src: "/build/(.*)", + status: 404, + headers: { + "cache-control": "no-store", + }, + continue: false, + }); + }); + it("should generate prerender config", async () => { const isrRouteConfig = await fsp.readFile( resolve(ctx.outDir, "functions/rules/isr/[...]-isr.prerender-config.json"),