Skip to content

Commit

Permalink
refactor: update revalidate handling for render responses
Browse files Browse the repository at this point in the history
  • Loading branch information
wyattjoh committed Oct 21, 2023
1 parent b2d2160 commit 28f5b38
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 94 deletions.
74 changes: 45 additions & 29 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import type { PreviewData, ServerRuntime, SizeLimit } from 'next/types'
import type { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin'
import type { OutgoingHttpHeaders } from 'http2'
import type { BaseNextRequest, BaseNextResponse } from './base-http'
import type { PayloadOptions } from './send-payload'
import type {
ManifestRewriteRoute,
ManifestRoute,
Expand All @@ -40,6 +39,7 @@ import type {
} from './future/route-modules/app-route/module'
import type { Server as HTTPServer } from 'http'
import type { ImageConfigComplete } from '../shared/lib/image-config'
import type { MiddlewareMatcher } from '../build/analysis/get-page-static-info'

import { format as formatUrl, parse as parseUrl } from 'url'
import { formatHostname } from './lib/format-hostname'
Expand All @@ -55,7 +55,7 @@ import {
import { isDynamicRoute } from '../shared/lib/router/utils'
import { checkIsOnDemandRevalidate } from './api-utils'
import { setConfig } from '../shared/lib/runtime-config.external'
import { setRevalidateHeaders } from './send-payload/revalidate-headers'
import { formatRevalidate, type Revalidate } from './lib/revalidate'
import { execOnce } from '../shared/lib/utils'
import { isBlockedPage } from './utils'
import { isBot } from '../shared/lib/router/utils/is-bot'
Expand All @@ -76,7 +76,6 @@ import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
import { getHostname } from '../shared/lib/get-hostname'
import { parseUrl as parseUrlUtil } from '../shared/lib/router/utils/parse-url'
import { getNextPathnameInfo } from '../shared/lib/router/utils/get-next-pathname-info'
import type { MiddlewareMatcher } from '../build/analysis/get-page-static-info'
import {
RSC,
RSC_VARY_HEADER,
Expand Down Expand Up @@ -108,6 +107,7 @@ import {
toNodeOutgoingHttpHeaders,
} from './web/utils'
import {
CACHE_ONE_YEAR,
NEXT_CACHE_TAGS_HEADER,
NEXT_QUERY_PARAM_PREFIX,
} from '../lib/constants'
Expand Down Expand Up @@ -282,7 +282,7 @@ export class WrappedBuildError extends Error {
type ResponsePayload = {
type: 'html' | 'json' | 'rsc'
body: RenderResult
revalidateOptions?: any
revalidate?: Revalidate
}

export default abstract class Server<ServerOptions extends Options = Options> {
Expand Down Expand Up @@ -343,7 +343,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
type: 'html' | 'json' | 'rsc'
generateEtags: boolean
poweredByHeader: boolean
options?: PayloadOptions
revalidate?: Revalidate
}
): Promise<void>

Expand Down Expand Up @@ -1429,19 +1429,23 @@ export default abstract class Server<ServerOptions extends Options = Options> {
return
}
const { req, res } = ctx
const { body, type, revalidateOptions } = payload
const { body, type } = payload
let { revalidate } = payload
if (!res.sent) {
const { generateEtags, poweredByHeader, dev } = this.renderOpts

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

return this.sendRenderResult(req, res, {
result: body,
type,
generateEtags,
poweredByHeader,
options: revalidateOptions,
revalidate,
})
}
}
Expand Down Expand Up @@ -2425,23 +2429,38 @@ export default abstract class Server<ServerOptions extends Options = Options> {
)
}

const { revalidate, value: cachedData } = cacheEntry
const revalidateOptions: any =
typeof revalidate !== 'undefined' &&
(!this.renderOpts.dev || (hasServerProps && !isDataReq))
? {
// When the page is 404 cache-control should not be added unless
// we are rendering the 404 page for notFound: true which should
// cache according to revalidate correctly
private: isPreviewMode || (is404Page && cachedData),
stateful: !isSSG,
revalidate,
}
: undefined
const { value: cachedData } = cacheEntry

// Coerce the revalidate parameter from the render.
let revalidate: Revalidate | undefined
if (
typeof cacheEntry.revalidate !== 'undefined' &&
!this.renderOpts.dev &&
hasServerProps &&
!isDataReq &&
cachedData?.kind !== 'ROUTE'
) {
if (isPreviewMode || (is404Page && !isDataReq)) {
revalidate = 0
} else if (!isSSG && !res.getHeader('Cache-Control')) {
revalidate = 0
} else if (typeof cacheEntry.revalidate === 'number') {
if (cacheEntry.revalidate < 1) {
throw new Error(
`invariant: invalid Cache-Control duration provided: ${cacheEntry.revalidate} < 1`
)
}

revalidate = cacheEntry.revalidate
} else if (typeof cacheEntry.revalidate === 'boolean') {
revalidate = CACHE_ONE_YEAR
}
}
cacheEntry.revalidate = revalidate

if (!cachedData) {
if (revalidateOptions) {
setRevalidateHeaders(res, revalidateOptions)
if (cacheEntry.revalidate) {
res.setHeader('Cache-Control', formatRevalidate(cacheEntry.revalidate))
}
if (isDataReq) {
res.statusCode = 404
Expand All @@ -2455,17 +2474,14 @@ export default abstract class Server<ServerOptions extends Options = Options> {
return null
}
} else if (cachedData.kind === 'REDIRECT') {
if (revalidateOptions) {
setRevalidateHeaders(res, revalidateOptions)
}
if (isDataReq) {
return {
type: 'json',
body: RenderResult.fromStatic(
// @TODO: Handle flight data.
JSON.stringify(cachedData.props)
),
revalidateOptions,
revalidate: cacheEntry.revalidate,
}
} else {
await handleRedirect(cachedData.props)
Expand Down Expand Up @@ -2518,7 +2534,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
body: isDataReq
? RenderResult.fromStatic(cachedData.pageData as string)
: cachedData.html,
revalidateOptions,
revalidate: cacheEntry.revalidate,
}
}

Expand All @@ -2527,7 +2543,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
body: isDataReq
? RenderResult.fromStatic(JSON.stringify(cachedData.pageData))
: cachedData.html,
revalidateOptions,
revalidate: cacheEntry.revalidate,
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions packages/next/src/server/lib/revalidate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CACHE_ONE_YEAR } from '../../lib/constants'

/**
* The revalidate option used internally for pages. A value of `false` means
* that the page should not be revalidated. A number means that the page
Expand All @@ -6,3 +8,13 @@
* value for this option.
*/
export type Revalidate = number | false

export function formatRevalidate(revalidate: Revalidate): string {
if (revalidate === 0) {
return 'private, no-cache, no-store, max-age=0, must-revalidate'
} else if (typeof revalidate === 'number') {
return `s-maxage=${revalidate}, stale-while-revalidate`
}

return `s-maxage=${CACHE_ONE_YEAR}, stale-while-revalidate`
}
22 changes: 13 additions & 9 deletions packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@ import type { FetchEventResult } from './web/types'
import type { PrerenderManifest } from '../build'
import type { BaseNextRequest, BaseNextResponse } from './base-http'
import type { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin'
import type { PayloadOptions } from './send-payload'
import type { NextParsedUrlQuery, NextUrlWithParsedQuery } from './request-meta'
import { getRouteMatcher } from '../shared/lib/router/utils/route-matcher'
import type { Params } from '../shared/lib/router/utils/route-matcher'
import type { MiddlewareRouteMatch } from '../shared/lib/router/utils/middleware-route-matcher'
import type { RouteMatch } from './future/route-matches/route-match'
import type { IncomingMessage, ServerResponse } from 'http'
import type { PagesAPIRouteModule } from './future/route-modules/pages-api/module'
import type { UrlWithParsedQuery } from 'url'
import type { ParsedUrlQuery } from 'querystring'
import type { ParsedUrl } from '../shared/lib/router/utils/parse-url'
import type { Revalidate } from './lib/revalidate'

import fs from 'fs'
import { join, resolve, isAbsolute } from 'path'
import type { IncomingMessage, ServerResponse } from 'http'
import type { PagesAPIRouteModule } from './future/route-modules/pages-api/module'
import { getRouteMatcher } from '../shared/lib/router/utils/route-matcher'
import { addRequestMeta, getRequestMeta } from './request-meta'
import {
PAGES_MANIFEST,
Expand All @@ -41,11 +44,8 @@ import {
INTERNAL_HEADERS,
} from '../shared/lib/constants'
import { findDir } from '../lib/find-pages-dir'
import type { UrlWithParsedQuery } from 'url'
import { NodeNextRequest, NodeNextResponse } from './base-http/node'
import { sendRenderResult } from './send-payload'
import type { ParsedUrlQuery } from 'querystring'
import type { ParsedUrl } from '../shared/lib/router/utils/parse-url'
import { parseUrl } from '../shared/lib/router/utils/parse-url'
import * as Log from '../build/output/log'

Expand Down Expand Up @@ -387,13 +387,17 @@ export default class NextNodeServer extends BaseServer {
type: 'html' | 'json'
generateEtags: boolean
poweredByHeader: boolean
options?: PayloadOptions | undefined
revalidate: Revalidate | undefined
}
): Promise<void> {
return sendRenderResult({
req: req.originalRequest,
res: res.originalResponse,
...options,
result: options.result,
type: options.type,
generateEtags: options.generateEtags,
poweredByHeader: options.poweredByHeader,
revalidate: options.revalidate,
})
}

Expand Down
11 changes: 3 additions & 8 deletions packages/next/src/server/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import type { PagesModule } from './future/route-modules/pages/module'
import type { ComponentsEnhancer } from '../shared/lib/utils'
import type { NextParsedUrlQuery } from './request-meta'
import type { Revalidate } from './lib/revalidate'
import type { COMPILER_NAMES } from '../shared/lib/constants'

import React from 'react'
import ReactDOMServer from 'react-dom/server.browser'
Expand All @@ -51,9 +52,7 @@ import {
SERVER_PROPS_SSG_CONFLICT,
SSG_GET_INITIAL_PROPS_CONFLICT,
UNSTABLE_REVALIDATE_RENAME_ERROR,
CACHE_ONE_YEAR,
} from '../lib/constants'
import type { COMPILER_NAMES } from '../shared/lib/constants'
import {
NEXT_BUILTIN_DOCUMENT,
SERVER_PROPS_ID,
Expand Down Expand Up @@ -105,7 +104,7 @@ import {
import { getTracer } from './lib/trace/tracer'
import { RenderSpan } from './lib/trace/constants'
import { ReflectAdapter } from './web/spec-extension/adapters/reflect'
import { setRevalidateHeaders } from './send-payload'
import { formatRevalidate } from './lib/revalidate'

let tryGetPreviewData: typeof import('./api-utils/node/try-get-preview-data').tryGetPreviewData
let warn: typeof import('../build/output/log').warn
Expand Down Expand Up @@ -510,11 +509,7 @@ export async function renderToHTMLImpl(
// ensure we set cache header so it's not rendered on-demand
// every request
if (isAutoExport && !dev && isExperimentalCompile) {
setRevalidateHeaders(res, {
revalidate: CACHE_ONE_YEAR,
private: false,
stateful: false,
})
res.setHeader('Cache-Control', formatRevalidate(false))
isAutoExport = false
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import type { IncomingMessage, ServerResponse } from 'http'
import type RenderResult from '../render-result'
import type RenderResult from './render-result'
import type { Revalidate } from './lib/revalidate'

import { isResSent } from '../../shared/lib/utils'
import { generateETag } from '../lib/etag'
import { isResSent } from '../shared/lib/utils'
import { generateETag } from './lib/etag'
import fresh from 'next/dist/compiled/fresh'
import { setRevalidateHeaders } from './revalidate-headers'
import { RSC_CONTENT_TYPE_HEADER } from '../../client/components/app-router-headers'

export type PayloadOptions =
| { private: true }
| { private: boolean; stateful: true }
| { private: boolean; stateful: false; revalidate: number | false }

export { setRevalidateHeaders }
import { formatRevalidate } from './lib/revalidate'
import { RSC_CONTENT_TYPE_HEADER } from '../client/components/app-router-headers'

export function sendEtagResponse(
req: IncomingMessage,
Expand Down Expand Up @@ -45,15 +39,15 @@ export async function sendRenderResult({
type,
generateEtags,
poweredByHeader,
options,
revalidate,
}: {
req: IncomingMessage
res: ServerResponse
result: RenderResult
type: 'html' | 'json' | 'rsc'
generateEtags: boolean
poweredByHeader: boolean
options?: PayloadOptions
revalidate: Revalidate | undefined
}): Promise<void> {
if (isResSent(res)) {
return
Expand All @@ -63,8 +57,8 @@ export async function sendRenderResult({
res.setHeader('X-Powered-By', 'Next.js')
}

if (options != null) {
setRevalidateHeaders(res, options)
if (typeof revalidate !== 'undefined') {
res.setHeader('Cache-Control', formatRevalidate(revalidate))
}

const payload = result.isDynamic ? null : result.toUnchunkedString()
Expand Down
30 changes: 0 additions & 30 deletions packages/next/src/server/send-payload/revalidate-headers.ts

This file was deleted.

Loading

0 comments on commit 28f5b38

Please sign in to comment.