8000 [backport] Reinstate `vary` by gaojude · Pull Request #79939 · vercel/next.js · GitHub
[go: up one dir, main page]

Skip to content

[backport] Reinstate vary #79939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 29, 2025
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
12 changes: 9 additions & 3 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import {
NEXT_ROUTER_SEGMENT_PREFETCH_HEADER,
NEXT_DID_POSTPONE_HEADER,
NEXT_URL,
NEXT_ROUTER_STATE_TREE_HEADER,
NEXT_IS_PRERENDER_HEADER,
} from '../client/components/app-router-headers'
import type {
Expand Down Expand Up @@ -1981,16 +1982,21 @@ export default abstract class Server<
isAppPath: boolean,
resolvedPathname: string
): void {
const baseVaryHeader = `${RSC_HEADER}, ${NEXT_ROUTER_STATE_TREE_HEADER}, ${NEXT_ROUTER_PREFETCH_HEADER}, ${NEXT_ROUTER_SEGMENT_PREFETCH_HEADER}`
const isRSCRequest = getRequestMeta(req, 'isRSCRequest') ?? false

let addedNextUrlToVary = false

if (isAppPath && this.pathCouldBeIntercepted(resolvedPathname)) {
// Interception route responses can vary based on the `Next-URL` header.
// We use the Vary header to signal this behavior to the client to properly cache the response.
res.appendHeader('vary', `${NEXT_URL}`)
res.appendHeader('vary', `${baseVaryHeader}, ${NEXT_URL}`)
addedNextUrlToVary = true
} else if (isAppPath || isRSCRequest) {
// We don't need to include `Next-URL` in the Vary header for non-interception routes since it won't affect the response.
// We also set this header for pages to avoid caching issues when navigating between pages and app.
res.appendHeader('vary', baseVaryHeader)
}
// For other cases such as App Router requests or RSC requests we don't need to set vary header since we already
// have the _rsc query with the unique hash value.

if (!addedNextUrlToVary) {
// Remove `Next-URL` from the request headers we determined it wasn't necessary to include in the Vary header.
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/app-dir/app/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,27 @@ describe('app dir - basic', () => {
expect(res.headers.get('Content-Type')).toBe('text/x-component')
})

it('should return the `vary` header from edge runtime', async () => {
const res = await next.fetch('/dashboard')
expect(res.headers.get('x-edge-runtime')).toBe('1')
expect(res.headers.get('vary')).toBe(
'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch'
)
})

it('should return the `vary` header from pages for flight requests', async () => {
const res = await next.fetch('/', {
headers: {
['RSC'.toString()]: '1',
},
})
expect(res.headers.get('vary')).toBe(
isNextDeploy
? 'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch'
: 'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept-Encoding'
)
})

it('should pass props from getServerSideProps in root layout', async () => {
const $ = await next.render$('/dashboard')
expect($('title').first().text()).toBe('hello world')
Expand Down
14 changes: 12 additions & 2 deletions test/e2e/vary-header/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ describe('Vary Header Tests', () => {
expect(res.headers.get('vary')).toContain('Custom-Header')
})

it('should preserve custom vary header', async () => {
it('should preserve custom vary header and append RSC headers in app route handlers', async () => {
const res = await next.fetch('/normal')
const varyHeader = res.headers.get('vary')

// Custom header is preserved
expect(varyHeader).toContain('User-Agent')
expect(res.headers.get('cache-control')).toBe('s-maxage=3600')

// Next.js internal headers are appended
expect(varyHeader).toContain('RSC')
expect(varyHeader).toContain('Next-Router-State-Tree')
expect(varyHeader).toContain('Next-Router-Prefetch')
})

it('should preserve middleware vary header', async () => {
it('should preserve middleware vary header in combination with route handlers', async () => {
const res = await next.fetch('/normal')
const varyHeader = res.headers.get('vary')
const customHeader = res.headers.get('my-custom-header')
Expand All @@ -32,5 +37,10 @@ describe('Vary Header Tests', () => {
// Both middleware and route handler vary headers are preserved
expect(varyHeader).toContain('my-custom-header')
expect(varyHeader).toContain('User-Agent')

// Next.js internal headers are still present
expect(varyHeader).toContain('RSC')
expect(varyHeader).toContain('Next-Router-State-Tree')
expect(varyHeader).toContain('Next-Router-Prefetch')
})
})
Loading
0