Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ff2201d

Browse files
committed
fix(perf): exclude /_next/static/* from generated functions
1 parent e7dd5f1 commit ff2201d

File tree

5 files changed

+80
-4
lines changed

5 files changed

+80
-4
lines changed

‎src/build/functions/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ const getHandlerFile = async (ctx: PluginContext): Promise<string> => {
106106

107107
const templateVariables: Record<string, string> = {
108108
'{{useRegionalBlobs}}': ctx.useRegionalBlobs.toString(),
109+
'{{excludeStaticPath}}': join(ctx.buildConfig.basePath, '/_next/static/*'),
109110
}
110111
// In this case it is a monorepo and we need to use a own template for it
111112
// as we have to change the process working directory

‎src/build/templates/handler-monorepo.tmpl.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,9 @@ export default async function (req, context) {
4949
export const config = {
5050
path: '/*',
5151
preferStatic: true,
52+
excludedPath: [
53+
// We use `preferStatic: true` so we already won't run this on *existing* static assets,
54+
// but by excluding this entire path we also avoid invoking the function just to 404.
55+
'{{excludeStaticPath}}',
56+
],
5257
}

‎src/build/templates/handler.tmpl.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ export default async function handler(req, context) {
4343
export const config = {
4444
path: '/*',
4545
preferStatic: true,
46+
excludedPath: [
47+
// We use `preferStatic: true` so we already won't run this on *existing* static assets,
48+
// but by excluding this entire path we also avoid invoking the function just to 404.
49+
'{{excludeStaticPath}}',
50+
],
4651
}

‎tests/e2e/page-router.test.ts

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { expect } from '@playwright/test'
22
import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs'
33
import { test } from '../utils/playwright-helpers.js'
4+
import { join } from 'node:path'
5+
import { readdir } from 'node:fs/promises'
46

57
export function waitFor(millis: number) {
68
return new Promise((resolve) => setTimeout(resolve, millis))
@@ -614,6 +616,34 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
614616
/(s-maxage|max-age)/,
615617
)
616618
})
619+
620+
test.describe('static assets and function invocations', () => {
621+
test('should return 200 for an existing static asset without invoking a function', async ({
622+
page,
623+
pageRouter,
624+
}) => {
625+
// Since assets are hashed, we can't hardcode anything here. Find something to fetch.
626+
const [staticAsset] = await readdir(
627+
join(pageRouter.isolatedFixtureRoot, '.next', 'static', 'chunks'),
628+
)
629+
expect(staticAsset).toBeDefined()
630+
631+
const response = await page.goto(`${pageRouter.url}/_next/static/chunks/${staticAsset}`)
632+
633+
expect(response?.status()).toBe(200)
634+
expect(response?.headers()).not.toHaveProperty('x-nf-function-type')
635+
})
636+
637+
test('should return 404 for a nonexistent static asset without invoking a function', async ({
638+
page,
639+
pageRouter,
640+
}) => {
641+
const response = await page.goto(`${pageRouter.url}/_next/static/stale123abcdef.js`)
642+
643+
expect(response?.status()).toBe(404)
644+
expect(response?.headers()).not.toHaveProperty('x-nf-function-type')
645+
})
646+
})
617647
})
618648

619649
test.describe('Page Router with basePath and i18n', () => {
@@ -1352,9 +1382,9 @@ test.describe('Page Router with basePath and i18n', () => {
13521382

13531383
test('requesting a non existing page route that needs to be fetched from the blob store like 404.html', async ({
13541384
page,
1355-
pageRouter,
1385+
pageRouterBasePathI18n,
13561386
}) => {
1357-
const response = await page.goto(new URL('non-existing', pageRouter.url).href)
1387+
const response = await page.goto(new URL('non-existing', pageRouterBasePathI18n.url).href)
13581388
const headers = response?.headers() || {}
13591389
expect(response?.status()).toBe(404)
13601390

@@ -1375,9 +1405,9 @@ test.describe('Page Router with basePath and i18n', () => {
13751405

13761406
test('requesting a non existing page route that needs to be fetched from the blob store like 404.html (notFound: true)', async ({
13771407
page,
1378-
pageRouter,
1408+
pageRouterBasePathI18n,
13791409
}) => {
1380-
const response = await page.goto(new URL('static/not-found', pageRouter.url).href)
1410+
const response = await page.goto(new URL('static/not-found', pageRouterBasePathI18n.url).href)
13811411
const headers = response?.headers() || {}
13821412
expect(response?.status()).toBe(404)
13831413

@@ -1390,4 +1420,36 @@ test.describe('Page Router with basePath and i18n', () => {
13901420
)
13911421
expect(headers['cache-control']).toBe('public,max-age=0,must-revalidate')
13921422
})
1423+
1424+
test.describe('static assets and function invocations', () => {
1425+
test('should return 200 for an existing static asset without invoking a function', async ({
1426+
page,
1427+
pageRouterBasePathI18n,
1428+
}) => {
1429+
// Since assets are hashed, we can't hardcode anything here. Find something to fetch.
1430+
const [staticAsset] = await readdir(
1431+
join(pageRouterBasePathI18n.isolatedFixtureRoot, '.next', 'static', 'chunks'),
1432+
)
1433+
expect(staticAsset).toBeDefined()
1434+
1435+
const response = await page.goto(
1436+
`${pageRouterBasePathI18n.url}/_next/static/chunks/${staticAsset}`,
1437+
)
1438+
1439+
expect(response?.status()).toBe(200)
1440+
expect(response?.headers()).not.toHaveProperty('x-nf-function-type')
1441+
})
1442+
1443+
test('should return 404 for a nonexistent static asset without invoking a function', async ({
1444+
page,
1445+
pageRouterBasePathI18n,
1446+
}) => {
1447+
const response = await page.goto(
1448+
`${pageRouterBasePathI18n.url}/_next/static/stale123abcdef.js`,
1449+
)
1450+
1451+
expect(response?.status()).toBe(404)
1452+
expect(response?.headers()).not.toHaveProperty('x-nf-function-type')
1453+
})
1454+
})
13931455
})

‎tests/utils/create-e2e-fixture.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface DeployResult {
2121
deployID: string
2222
url: string
2323
logs: string
24+
isolatedFixtureRoot: string
2425
}
2526

2627
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'berry'
@@ -92,6 +93,7 @@ export const createE2EFixture = async (fixture: string, config: E2EConfig = {})
9293
cleanup: _cleanup,
9394
deployID: result.deployID,
9495
url: result.url,
96+
isolatedFixtureRoot: result.isolatedFixtureRoot,
9597
}
9698
} catch (error) {
9799
await _cleanup(true)
@@ -292,6 +294,7 @@ async function deploySite(
292294
url: `https://${deployID}--${siteName}.netlify.app`,
293295
deployID,
294296
logs: output,
297+
isolatedFixtureRoot,
295298
}
296299
}
297300

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /