Skip to content

Commit 6c46c9d

Browse files
committed
migrate immutable headers for next/static
1 parent 66cd37f commit 6c46c9d

File tree

5 files changed

+40
-25
lines changed

5 files changed

+40
-25
lines changed

adapters-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- Files from `public` not in `outputs.staticFiles`
44
- In `onBuildComplete` - `config.images.remotePatterns` type is `(RemotePattern | URL)[]` but in
55
reality `URL` inputs are converted to `RemotePattern` so type should be just `RemotePattern[]`
6+
- `routes.headers` does not contain immutable cache-control headers for \_next/static
67

78
## Plan
89

src/adapter/adapter.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { dirname } from 'node:path'
33

44
import type { NextAdapter } from 'next-with-adapters'
55

6+
import { onBuildComplete as onBuildCompleteForHeaders } from './header.js'
67
import {
78
modifyConfig as modifyConfigForImageCDN,
89
onBuildComplete as onBuildCompleteForImageCDN,
@@ -23,12 +24,13 @@ const adapter: NextAdapter = {
2324

2425
return config
2526
},
26-
async onBuildComplete(ctx) {
27+
async onBuildComplete(nextAdapterContext) {
2728
console.log('onBuildComplete hook called')
2829

2930
let frameworksAPIConfig: FrameworksAPIConfig = null
3031

31-
frameworksAPIConfig = onBuildCompleteForImageCDN(ctx, frameworksAPIConfig)
32+
frameworksAPIConfig = onBuildCompleteForImageCDN(nextAdapterContext, frameworksAPIConfig)
33+
frameworksAPIConfig = onBuildCompleteForHeaders(nextAdapterContext, frameworksAPIConfig)
3234

3335
if (frameworksAPIConfig) {
3436
// write out config if there is any
@@ -40,7 +42,7 @@ const adapter: NextAdapter = {
4042
}
4143

4244
// for dev/debugging purposes only
43-
await writeFile('./onBuildComplete.json', JSON.stringify(ctx, null, 2))
45+
await writeFile('./onBuildComplete.json', JSON.stringify(nextAdapterContext, null, 2))
4446
debugger
4547
},
4648
}

src/adapter/header.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { FrameworksAPIConfig, OnBuildCompleteContext } from './types.js'
2+
3+
export function onBuildComplete(
4+
ctx: OnBuildCompleteContext,
5+
frameworksAPIConfigArg: FrameworksAPIConfig,
6+
) {
7+
const frameworksAPIConfig: FrameworksAPIConfig = frameworksAPIConfigArg ?? {}
8+
9+
frameworksAPIConfig.headers ??= []
10+
11+
frameworksAPIConfig.headers.push({
12+
for: `${ctx.config.basePath}/_next/static/*`,
13+
values: {
14+
'Cache-Control': 'public, max-age=31536000, immutable',
15+
},
16+
})
17+
18+
// TODO: we should apply ctx.routes.headers here as well, but the matching
19+
// is currently not compatible with anything we can express with our redirect engine
20+
// {
21+
// regex: "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?(?:/)?$"
22+
// source: "/:path*" // <- this is defined in next.config
23+
// }
24+
// per https://docs.netlify.com/manage/routing/headers/#wildcards-and-placeholders-in-paths
25+
// this is example of something we can't currently do
26+
27+
return frameworksAPIConfig
28+
}

src/build/content/static.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,6 @@ export const copyStaticAssets = async (ctx: PluginContext): Promise<void> => {
7979
})
8080
}
8181

82-
export const setHeadersConfig = async (ctx: PluginContext): Promise<void> => {
83-
// https://nextjs.org/docs/app/api-reference/config/next-config-js/headers#cache-control
84-
// Next.js sets the Cache-Control header of public, max-age=31536000, immutable for truly
85-
// immutable assets. It cannot be overridden. These immutable files contain a SHA-hash in
86-
// the file name, so they can be safely cached indefinitely.
87-
const { basePath } = ctx.buildConfig
88-
ctx.netlifyConfig.headers.push({
89-
for: `${basePath}/_next/static/*`,
90-
values: {
91-
'Cache-Control': 'public, max-age=31536000, immutable',
92-
},
93-
})
94-
}
95-
9682
export const copyStaticExport = async (ctx: PluginContext): Promise<void> => {
9783
await tracer.withActiveSpan('copyStaticExport', async () => {
9884
if (!ctx.exportDetail?.outDirectory) {

src/index.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
copyStaticContent,
1313
copyStaticExport,
1414
publishStaticDir,
15-
setHeadersConfig,
1615
unpublishStaticDir,
1716
} from './build/content/static.js'
1817
import { clearStaleEdgeHandlers, createEdgeHandlers } from './build/functions/edge.js'
@@ -88,19 +87,18 @@ export const onBuild = async (options: NetlifyPluginOptions) => {
8887

8988
// static exports only need to be uploaded to the CDN and setup /_next/image handler
9089
if (ctx.buildConfig.output === 'export') {
91-
return Promise.all([copyStaticExport(ctx), setHeadersConfig(ctx)])
90+
return Promise.all([copyStaticExport(ctx)])
9291
}
9392

9493
await verifyAdvancedAPIRoutes(ctx)
9594
await verifyNetlifyFormsWorkaround(ctx)
9695

9796
await Promise.all([
98-
copyStaticAssets(ctx),
99-
copyStaticContent(ctx),
100-
copyPrerenderedContent(ctx),
101-
createServerHandler(ctx),
102-
createEdgeHandlers(ctx),
103-
setHeadersConfig(ctx),
97+
copyStaticAssets(ctx), // this
98+
copyStaticContent(ctx), // this
99+
copyPrerenderedContent(ctx), // maybe this
100+
createServerHandler(ctx), // not this while we use standalone
101+
createEdgeHandlers(ctx), // this - middleware
104102
])
105103
})
106104
}

0 commit comments

Comments
 (0)