Skip to content

Commit 24647b9

Browse files
committed
Add ability to customize Cache-Control (#69802)
This continues #39707 bringing the changes up to date with canary and adds test cases to ensure it's working as expected. Closes: #22319 Closes: #39707 Closes: NDX-148
1 parent 6fa8982 commit 24647b9

File tree

13 files changed

+266
-7
lines changed

13 files changed

+266
-7
lines changed

packages/next/src/server/base-server.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -2587,6 +2587,13 @@ export default abstract class Server<ServerOptions extends Options = Options> {
25872587
}
25882588
)
25892589

2590+
if (isPreviewMode) {
2591+
res.setHeader(
2592+
'Cache-Control',
2593+
'private, no-cache, no-store, max-age=0, must-revalidate'
2594+
)
2595+
}
2596+
25902597
if (!cacheEntry) {
25912598
if (ssgCacheKey && !(isOnDemandRevalidate && revalidateOnlyGenerated)) {
25922599
// A cache entry might not be generated if a response is written
@@ -2719,7 +2726,9 @@ export default abstract class Server<ServerOptions extends Options = Options> {
27192726
// for the revalidate value
27202727
addRequestMeta(req, 'notFoundRevalidate', cacheEntry.revalidate)
27212728

2722-
if (cacheEntry.revalidate) {
2729+
// If cache control is already set on the response we don't
2730+
// override it to allow users to customize it via next.config
2731+
if (cacheEntry.revalidate && !res.getHeader('Cache-Control')) {
27232732
res.setHeader(
27242733
'Cache-Control',
27252734
formatRevalidate({
@@ -2740,7 +2749,9 @@ export default abstract class Server<ServerOptions extends Options = Options> {
27402749
await this.render404(req, res, { pathname, query }, false)
27412750
return null
27422751
} else if (cachedData.kind === 'REDIRECT') {
2743-
if (cacheEntry.revalidate) {
2752+
// If cache control is already set on the response we don't
2753+
// override it to allow users to customize it via next.config
2754+
if (cacheEntry.revalidate && !res.getHeader('Cache-Control')) {
27442755
res.setHeader(
27452756
'Cache-Control',
27462757
formatRevalidate({
@@ -3223,7 +3234,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
32233234
if (setHeaders) {
32243235
res.setHeader(
32253236
'Cache-Control',
3226-
'no-cache, no-store, max-age=0, must-revalidate'
3237+
'private, no-cache, no-store, max-age=0, must-revalidate'
32273238
)
32283239
}
32293240

packages/next/src/server/lib/router-server.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ export async function initialize(opts: {
526526
// 404 case
527527
res.setHeader(
528528
'Cache-Control',
529-
'no-cache, no-store, max-age=0, must-revalidate'
529+
'private, no-cache, no-store, max-age=0, must-revalidate'
530530
)
531531

532532
// Short-circuit favicon.ico serving so that the 404 page doesn't get built as favicon is requested by the browser when loading any route.

packages/next/src/server/send-payload.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ export async function sendRenderResult({
5959
res.setHeader('X-Powered-By', 'Next.js')
6060
}
6161

62-
if (typeof revalidate !== 'undefined') {
62+
// If cache control is already set on the response we don't
63+
// override it to allow users to customize it via next.config
64+
if (typeof revalidate !== 'undefined' && !res.getHeader('Cache-Control')) {
6365
res.setHeader(
6466
'Cache-Control',
6567
formatRevalidate({
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export const revalidate = 120
2+
3+
export function generateStaticParams() {
4+
return [
5+
{
6+
slug: 'first',
7+
},
8+
]
9+
}
10+
11+
export default function Page({ params }) {
12+
return (
13+
<>
14+
<p>/app-ssg/[slug]</p>
15+
<p>{JSON.stringify(params)}</p>
16+
</>
17+
)
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const dynamic = 'force-dynamic'
2+
3+
export default function Page() {
4+
return (
5+
<>
6+
<p>/app-ssr</p>
7+
</>
8+
)
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ReactNode } from 'react'
2+
export default function Root({ children }: { children: ReactNode }) {
3+
return (
4+
<html>
5+
<body>{children}</body>
6+
</html>
7+
)
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('custom-cache-control', () => {
4+
const { next, isNextDev, isNextDeploy } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
if (isNextDeploy) {
9+
// customizing these headers won't apply on environments
10+
// where headers are applied outside of the Next.js server
11+
it('should skip for deploy', () => {})
12+
return
13+
}
14+
15+
it('should have custom cache-control for app-ssg prerendered', async () => {
16+
const res = await next.fetch('/app-ssg/first')
17+
expect(res.headers.get('cache-control')).toBe(
18+
isNextDev ? 'no-store, must-revalidate' : 's-maxage=30'
19+
)
20+
})
21+
22+
it('should have custom cache-control for app-ssg lazy', async () => {
23+
const res = await next.fetch('/app-ssg/lazy')
24+
expect(res.headers.get('cache-control')).toBe(
25+
isNextDev ? 'no-store, must-revalidate' : 's-maxage=31'
26+
)
27+
})
28+
;(process.env.__NEXT_EXPERIMENTAL_PPR ? it.skip : it)(
29+
'should have default cache-control for app-ssg another',
30+
async () => {
31+
const res = await next.fetch('/app-ssg/another')
32+
// eslint-disable-next-line jest/no-standalone-expect
33+
expect(res.headers.get('cache-control')).toBe(
34+
isNextDev
35+
? 'no-store, must-revalidate'
36+
: 's-maxage=120, stale-while-revalidate'
37+
)
38+
}
39+
)
40+
41+
it('should have custom cache-control for app-ssr', async () => {
42+
const res = await next.fetch('/app-ssr')
43+
expect(res.headers.get('cache-control')).toBe(
44+
isNextDev ? 'no-store, must-revalidate' : 's-maxage=32'
45+
)
46+
})
47+
48+
it('should have custom cache-control for auto static page', async () => {
49+
const res = await next.fetch('/pages-auto-static')
50+
expect(res.headers.get('cache-control')).toBe(
51+
isNextDev ? 'no-store, must-revalidate' : 's-maxage=33'
52+
)
53+
})
54+
55+
it('should have custom cache-control for pages-ssg prerendered', async () => {
56+
const res = await next.fetch('/pages-ssg/first')
57+
expect(res.headers.get('cache-control')).toBe(
58+
isNextDev ? 'no-store, must-revalidate' : 's-maxage=34'
59+
)
60+
})
61+
62+
it('should have custom cache-control for pages-ssg lazy', async () => {
63+
const res = await next.fetch('/pages-ssg/lazy')
64+
expect(res.headers.get('cache-control')).toBe(
65+
isNextDev ? 'no-store, must-revalidate' : 's-maxage=35'
66+
)
67+
})
68+
69+
it('should have default cache-control for pages-ssg another', async () => {
70+
const res = await next.fetch('/pages-ssg/another')
71+
expect(res.headers.get('cache-control')).toBe(
72+
isNextDev
73+
? 'no-store, must-revalidate'
74+
: 's-maxage=120, stale-while-revalidate'
75+
)
76+
})
77+
78+
it('should have default cache-control for pages-ssr', async () => {
79+
const res = await next.fetch('/pages-ssr')
80+
expect(res.headers.get('cache-control')).toBe(
81+
isNextDev ? 'no-store, must-revalidate' : 's-maxage=36'
82+
)
83+
})
84+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @type {import('next').NextConfig}
3+
*/
4+
const nextConfig = {
5+
headers() {
6+
return [
7+
{
8+
source: '/app-ssg/first',
9+
headers: [
10+
{
11+
key: 'Cache-Control',
12+
value: 's-maxage=30',
13+
},
14+
],
15+
},
16+
{
17+
source: '/app-ssg/lazy',
18+
headers: [
19+
{
20+
key: 'Cache-Control',
21+
value: 's-maxage=31',
22+
},
23+
],
24+
},
25+
{
26+
source: '/app-ssr',
27+
headers: [
28+
{
29+
key: 'Cache-Control',
30+
value: 's-maxage=32',
31+
},
32+
],
33+
},
34+
{
35+
source: '/pages-auto-static',
36+
headers: [
37+
{
38+
key: 'Cache-Control',
39+
value: 's-maxage=33',
40+
},
41+
],
42+
},
43+
{
44+
source: '/pages-ssg/first',
45+
headers: [
46+
{
47+
key: 'Cache-Control',
48+
value: 's-maxage=34',
49+
},
50+
],
51+
},
52+
{
53+
source: '/pages-ssg/lazy',
54+
headers: [
55+
{
56+
key: 'Cache-Control',
57+
value: 's-maxage=35',
58+
},
59+
],
60+
},
61+
{
62+
source: '/pages-ssr',
63+
headers: [
64+
{
65+
key: 'Cache-Control',
66+
value: 's-maxage=36',
67+
},
68+
],
69+
},
70+
]
71+
},
72+
}
73+
74+
module.exports = nextConfig
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Page() {
2+
return (
3+
<>
4+
<p>/pages-auto-static</p>
5+
</>
6+
)
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export function getStaticProps({ params }) {
2+
return {
3+
props: {
4+
now: Date.now(),
5+
params,
6+
},
7+
revalidate: 120,
8+
}
9+
}
10+
11+
export function getStaticPaths() {
12+
return {
13+
paths: [
14+
{
15+
params: { slug: 'first' },
16+
},
17+
],
18+
fallback: 'blocking',
19+
}
20+
}
21+
22+
export default function Page({ params }) {
23+
return (
24+
<>
25+
<p>/pages-ssg/[slug]</p>
26+
<p>{JSON.stringify(params)}</p>
27+
</>
28+
)
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function getServerSideProps() {
2+
return {
3+
props: {
4+
now: Date.now(),
5+
},
6+
}
7+
}
8+
9+
export default function Page() {
10+
return (
11+
<>
12+
<p>/pages-ssr</p>
13+
</>
14+
)
15+
}

test/integration/404-page/test/index.test.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,9 @@ describe('404 Page Support', () => {
262262
await killApp(app)
263263

264264
expect(cache404).toBe(null)
265-
expect(cacheNext).toBe('no-cache, no-store, max-age=0, must-revalidate')
265+
expect(cacheNext).toBe(
266+
'private, no-cache, no-store, max-age=0, must-revalidate'
267+
)
266268
})
267269

268270
it('shows error with getInitialProps in pages/404 build', async () => {

test/production/pages-dir/production/test/index.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ createNextDescribe(
618618

619619
expect(res.status).toBe(404)
620620
expect(res.headers.get('Cache-Control')).toBe(
621-
'no-cache, no-store, max-age=0, must-revalidate'
621+
'private, no-cache, no-store, max-age=0, must-revalidate'
622622
)
623623
})
624624

0 commit comments

Comments
 (0)