Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/next/src/build/templates/app-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,12 @@ export async function handler(

// In dev, we should not cache pages for any reason.
if (routeModule.isDev) {
res.setHeader('Cache-Control', 'no-store, must-revalidate')
res.setHeader(
'Cache-Control',
nextConfig.experimental.devCacheControlNoCache
? 'no-cache, must-revalidate'
: 'no-store, must-revalidate'
)
}

if (!cacheEntry) {
Expand Down
7 changes: 6 additions & 1 deletion packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1782,7 +1782,12 @@ export default abstract class Server<

// In dev, we should not cache pages for any reason.
if (dev) {
res.setHeader('Cache-Control', 'no-store, must-revalidate')
res.setHeader(
'Cache-Control',
this.nextConfig.experimental.devCacheControlNoCache
? 'no-cache, must-revalidate'
: 'no-store, must-revalidate'
)
cacheControl = undefined
}

Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ export const experimentalSchema = {
lockDistDir: z.boolean().optional(),
hideLogsAfterAbort: z.boolean().optional(),
runtimeServerDeploymentId: z.boolean().optional(),
devCacheControlNoCache: z.boolean().optional(),
}

export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
Expand Down
15 changes: 15 additions & 0 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,18 @@ export interface ExperimentalConfig {
* @default false
*/
runtimeServerDeploymentId?: boolean

/**
* Use 'no-cache' instead of 'no-store' in the Cache-Control header for development.
* This allows conditional requests to the server, which can help with development
* workflows that benefit from caching validation.
*
* When enabled, the Cache-Control header changes from 'no-store, must-revalidate'
* to 'no-cache, must-revalidate'.
*
* @default false
*/
devCacheControlNoCache?: boolean
}

export type ExportPathMap = {
Expand Down Expand Up @@ -1599,6 +1611,7 @@ export const defaultConfig = Object.freeze({
turbopackFileSystemCacheForDev: true,
turbopackFileSystemCacheForBuild: false,
turbopackInferModuleSideEffects: !isStableBuild(),
devCacheControlNoCache: false,
},
htmlLimitedBots: undefined,
bundlePagesRouterDependencies: false,
Expand Down Expand Up @@ -1695,6 +1708,7 @@ export interface NextConfigRuntime {
| 'testProxy'
| 'runtimeServerDeploymentId'
| 'maxPostponedStateSize'
| 'devCacheControlNoCache'
> & {
// Pick on @internal fields generates invalid .d.ts files
/** @internal */
Expand Down Expand Up @@ -1752,6 +1766,7 @@ export function getNextConfigRuntime(
testProxy: ex.testProxy,
runtimeServerDeploymentId: ex.runtimeServerDeploymentId,
maxPostponedStateSize: ex.maxPostponedStateSize,
devCacheControlNoCache: ex.devCacheControlNoCache,

trustHostHeader: ex.trustHostHeader,
isExperimentalCompile: ex.isExperimentalCompile,
Expand Down
7 changes: 6 additions & 1 deletion packages/next/src/server/lib/router-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,12 @@ export async function initialize(opts: {
matchedOutput.type === 'nextStaticFolder'
) {
if (opts.dev && !isNextFont(parsedUrl.pathname)) {
res.setHeader('Cache-Control', 'no-store, must-revalidate')
res.setHeader(
'Cache-Control',
config.experimental.devCacheControlNoCache
? 'no-cache, must-revalidate'
: 'no-store, must-revalidate'
)
} else {
res.setHeader(
'Cache-Control',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,12 @@ export const getHandler = ({

// In dev, we should not cache pages for any reason.
if (routeModule.isDev) {
res.setHeader('Cache-Control', 'no-store, must-revalidate')
res.setHeader(
'Cache-Control',
nextConfig.experimental.devCacheControlNoCache
? 'no-cache, must-revalidate'
: 'no-store, must-revalidate'
)
}

// Draft mode should never be cached
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function AppRoute() {
return <div>App Route</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { nextTestSetup } from 'e2e-utils'

describe('experimental.devCacheControlNoCache disabled (default)', () => {
const { next } = nextTestSetup({
files: __dirname,
})

it('should use no-store for pages router by default', async () => {
const res = await next.fetch('/pages-route')
expect(res.headers.get('Cache-Control')).toBe('no-store, must-revalidate')
})

it('should use no-store for app router by default', async () => {
const res = await next.fetch('/app-route')
expect(res.headers.get('Cache-Control')).toBe('no-store, must-revalidate')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** @type {import('next').NextConfig} */
module.exports = {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function PagesRoute() {
return <div>Pages Route</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function AppRoute() {
return <div>App Route</div>
}
7 changes: 7 additions & 0 deletions test/development/dev-cache-control-no-cache/app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { nextTestSetup } from 'e2e-utils'

describe('experimental.devCacheControlNoCache', () => {
describe('when enabled', () => {
const { next } = nextTestSetup({
files: __dirname,
})

it('should use no-cache instead of no-store for pages router', async () => {
const res = await next.fetch('/pages-route')
expect(res.headers.get('Cache-Control')).toBe('no-cache, must-revalidate')
})

it('should use no-cache instead of no-store for app router', async () => {
const res = await next.fetch('/app-route')
expect(res.headers.get('Cache-Control')).toBe('no-cache, must-revalidate')
})
})
})
6 changes: 6 additions & 0 deletions test/development/dev-cache-control-no-cache/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
devCacheControlNoCache: true,
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function PagesRoute() {
return <div>Pages Route</div>
}
Loading