diff --git a/src/build/templates/handler-monorepo.tmpl.js b/src/build/templates/handler-monorepo.tmpl.js index 89f4411d5e..4aa376f79a 100644 --- a/src/build/templates/handler-monorepo.tmpl.js +++ b/src/build/templates/handler-monorepo.tmpl.js @@ -53,4 +53,9 @@ export default async function (req, context) { export const config = { path: '/*', preferStatic: true, + excludedPath: [ + // We use `preferStatic: true` so we already won't run this on *existing* static assets, + // but by excluding this entire path we also avoid invoking the function just to 404. + '/_next/static/*', + ], } diff --git a/src/build/templates/handler.tmpl.js b/src/build/templates/handler.tmpl.js index c86fe13131..ac7207b72c 100644 --- a/src/build/templates/handler.tmpl.js +++ b/src/build/templates/handler.tmpl.js @@ -46,4 +46,9 @@ export default async function handler(req, context) { export const config = { path: '/*', preferStatic: true, + excludedPath: [ + // We use `preferStatic: true` so we already won't run this on *existing* static assets, + // but by excluding this entire path we also avoid invoking the function just to 404. + '/_next/static/*', + ], } diff --git a/tests/e2e/simple-app.test.ts b/tests/e2e/simple-app.test.ts index 980ad84dd0..81cf59c0fd 100644 --- a/tests/e2e/simple-app.test.ts +++ b/tests/e2e/simple-app.test.ts @@ -1,6 +1,8 @@ import { expect, type Locator } from '@playwright/test' import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' import { test } from '../utils/playwright-helpers.js' +import { join } from 'node:path' +import { readdir } from 'node:fs/promises' const expectImageWasLoaded = async (locator: Locator) => { expect(await locator.evaluate((img: HTMLImageElement) => img.naturalHeight)).toBeGreaterThan(0) @@ -282,3 +284,27 @@ test('can require CJS module that is not bundled', async ({ simple }) => { expect(parsedBody.notBundledCJSModule.isBundled).toEqual(false) expect(parsedBody.bundledCJSModule.isBundled).toEqual(true) }) + +test('serves a 200 for an existing static asset without invoking a function', async ({ + page, + simple, +}) => { + // Since assets are hashed, we can't hardcode anything here. Find something to fetch. + const [staticAsset] = await readdir(join(simple.isolatedFixtureRoot, '.next', 'static', 'chunks')) + expect(staticAsset).toBeDefined() + + const response = await page.goto(`${simple.url}/_next/static/chunks/${staticAsset}`) + + expect(response?.status()).toBe(200) + expect(response?.headers()).not.toHaveProperty('x-nf-function-type') +}) + +test('serves a 404 for a nonexistent static asset without invoking a function', async ({ + page, + simple, +}) => { + const response = await page.goto(`${simple.url}/_next/static/stale123abcdef.js`) + + expect(response?.status()).toBe(404) + expect(response?.headers()).not.toHaveProperty('x-nf-function-type') +}) diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index cc9c588496..08367ff8f0 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -21,6 +21,7 @@ export interface DeployResult { deployID: string url: string logs: string + isolatedFixtureRoot: string } type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'berry' @@ -92,6 +93,7 @@ export const createE2EFixture = async (fixture: string, config: E2EConfig = {}) cleanup: _cleanup, deployID: result.deployID, url: result.url, + isolatedFixtureRoot, } } catch (error) { await _cleanup(true)