Skip to content

Commit

Permalink
prerendered pages should use static staleTime
Browse files Browse the repository at this point in the history
  • Loading branch information
ztanner committed Jul 17, 2024
1 parent b39c71d commit 40f6a57
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/next/src/client/components/app-router-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export const FLIGHT_HEADERS = [
export const NEXT_RSC_UNION_QUERY = '_rsc' as const

export const NEXT_DID_POSTPONE_HEADER = 'x-nextjs-postponed' as const
export const NEXT_IS_PRERENDER_HEADER = 'x-nextjs-prerender' as const
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
RSC_HEADER,
RSC_CONTENT_TYPE_HEADER,
NEXT_HMR_REFRESH_HEADER,
NEXT_IS_PRERENDER_HEADER,
} from '../app-router-headers'
import { callServer } from '../../app-call-server'
import { PrefetchKind } from './router-reducer-types'
Expand Down Expand Up @@ -59,6 +60,7 @@ function doMpaNavigation(url: string): FetchServerResponseResult {
f: urlToUrlWithoutFlightMarker(url).toString(),
c: undefined,
i: false,
p: false,
}
}

Expand Down Expand Up @@ -156,6 +158,7 @@ export async function fetchServerResponse(

const contentType = res.headers.get('content-type') || ''
const interception = !!res.headers.get('vary')?.includes(NEXT_URL)
const isPrerender = !!res.headers.get(NEXT_IS_PRERENDER_HEADER)
let isFlightResponse = contentType === RSC_CONTENT_TYPE_HEADER

if (process.env.NODE_ENV === 'production') {
Expand Down Expand Up @@ -193,6 +196,7 @@ export async function fetchServerResponse(
f: response.f,
c: canonicalUrl,
i: interception,
p: isPrerender,
}
} catch (err) {
console.error(
Expand All @@ -206,6 +210,7 @@ export async function fetchServerResponse(
f: url.toString(),
c: undefined,
i: false,
p: false,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ function prefixExistingPrefetchCacheEntry({
const newCacheKey = createPrefetchCacheKey(url, nextUrl)
prefetchCache.set(newCacheKey, existingCacheEntry)
prefetchCache.delete(existingCacheKey)

return newCacheKey
}

/**
Expand Down Expand Up @@ -201,8 +203,28 @@ function createLazyPrefetchEntry({
// TODO: `fetchServerResponse` should be more tighly coupled to these prefetch cache operations
// to avoid drift between this cache key prefixing logic
// (which is currently directly influenced by the server response)
let newCacheKey

if (prefetchResponse.i) {
prefixExistingPrefetchCacheEntry({ url, nextUrl, prefetchCache })
// Determine if we need to prefix the cache key with the nextUrl
newCacheKey = prefixExistingPrefetchCacheEntry({
url,
nextUrl,
prefetchCache,
})
}

// If the prefetch was a cache hit, we want to update the existing cache entry to reflect that it was a full prefetch.
// This is because we know that a static response will contain the full RSC payload, and can be updated to respect the `static`
// staleTime.
if (prefetchResponse.p) {
const existingCacheEntry = prefetchCache.get(
// if we prefixed the cache key due to route interception, we want to use the new key. Otherwise we use the original key
newCacheKey ?? prefetchCacheKey
)
if (existingCacheEntry) {
existingCacheEntry.kind = PrefetchKind.FULL
}
}

return prefetchResponse
Expand Down
4 changes: 4 additions & 0 deletions packages/next/src/export/routes/app-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { hasNextSupport } from '../../telemetry/ci-info'
import { lazyRenderAppPage } from '../../server/route-modules/app-page/module.render'
import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr'
import { NodeNextRequest, NodeNextResponse } from '../../server/base-http/node'
import { NEXT_IS_PRERENDER_HEADER } from '../../client/components/app-router-headers'

export const enum ExportedAppPageFiles {
HTML = 'HTML',
Expand Down Expand Up @@ -114,6 +115,9 @@ export async function exportAppPage(

const headers: OutgoingHttpHeaders = { ...metadata.headers }

// If we're writing the file to disk, we know it's a prerender.
headers[NEXT_IS_PRERENDER_HEADER] = '1'

if (fetchTags) {
headers[NEXT_CACHE_TAGS_HEADER] = fetchTags
}
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/server/app-render/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ export type FetchServerResponseResult = {
c: URL | undefined
/** couldBeIntercepted */
i: boolean
/** isPrerender */
p: boolean
}

export type RSCPayload =
Expand Down
4 changes: 4 additions & 0 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ import {
NEXT_DID_POSTPONE_HEADER,
NEXT_URL,
NEXT_ROUTER_STATE_TREE_HEADER,
NEXT_IS_PRERENDER_HEADER,
} from '../client/components/app-router-headers'
import type {
MatchOptions,
Expand Down Expand Up @@ -2917,6 +2918,9 @@ export default abstract class Server<
? 'STALE'
: 'HIT'
)
// Set a header used by the client router to signal the response is static
// and should respect the `static` cache staleTime value.
res.setHeader(NEXT_IS_PRERENDER_HEADER, '1')
}

const { value: cachedData } = cacheEntry
Expand Down
17 changes: 17 additions & 0 deletions test/e2e/app-dir/app-prefetch/prefetching.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,23 @@ describe('app dir - prefetching', () => {
expect(
requests.filter((request) => request === '/static-page').length
).toBe(1)

// return to the home page
await browser.elementByCss('#to-home').click()
await browser.waitForElementByCss('#to-static-page')
// there shouldn't be any additional prefetches
expect(
requests.filter((request) => request === '/static-page').length
).toBe(1)

// navigate to the static page again
await browser.elementByCss('#to-static-page').click()
await browser.waitForElementByCss('#static-page')

// there still should only be the initial request to the static page
expect(
requests.filter((request) => request === '/static-page').length
).toBe(1)
})

it('should calculate `_rsc` query based on `Next-Url`', async () => {
Expand Down

0 comments on commit 40f6a57

Please sign in to comment.