Skip to content

Commit

Permalink
Correct Cache-Control Behavior for GS(S)P (vercel#11022)
Browse files Browse the repository at this point in the history
* Correct Cache-Control Behavior for GS(S)P

* remove old line

* fix test
  • Loading branch information
Timer authored and ScriptedAlchemy committed Mar 17, 2020
1 parent bd09749 commit 09045f3
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 125 deletions.
25 changes: 10 additions & 15 deletions packages/next/build/webpack/loaders/next-serverless-loader.ts
Expand Up @@ -205,6 +205,7 @@ const nextServerlessLoader: loader.Loader = function() {
const {renderToHTML} = require('next/dist/next-server/server/render');
const { tryGetPreviewData } = require('next/dist/next-server/server/api-utils');
const {sendHTML} = require('next/dist/next-server/server/send-html');
const {sendPayload} = require('next/dist/next-server/server/send-payload');
const buildManifest = require('${buildManifest}');
const reactLoadableManifest = require('${reactLoadableManifest}');
const Document = require('${absoluteDocumentPath}').default;
Expand Down Expand Up @@ -328,21 +329,15 @@ const nextServerlessLoader: loader.Loader = function() {
let result = await renderToHTML(req, res, "${page}", Object.assign({}, getStaticProps ? {} : parsedUrl.query, nowParams ? nowParams : params, _params, isFallback ? { __nextFallback: 'true' } : {}), renderOpts)
if (_nextData && !renderMode) {
const payload = JSON.stringify(renderOpts.pageData)
res.setHeader('Content-Type', 'application/json')
res.setHeader('Content-Length', Buffer.byteLength(payload))
res.setHeader(
'Cache-Control',
isPreviewMode
? \`private, no-cache, no-store, max-age=0, must-revalidate\`
: getServerSideProps
? \`no-cache, no-store, must-revalidate\`
: \`s-maxage=\${renderOpts.revalidate}, stale-while-revalidate\`
)
res.end(payload)
return null
if (!renderMode) {
if (_nextData || getStaticProps || getServerSideProps) {
sendPayload(res, _nextData ? JSON.stringify(renderOpts.pageData) : result, _nextData ? 'json' : 'html', {
private: isPreviewMode,
stateful: !!getServerSideProps,
revalidate: renderOpts.revalidate,
})
return null
}
} else if (isPreviewMode) {
res.setHeader(
'Cache-Control',
Expand Down
82 changes: 34 additions & 48 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -52,6 +52,7 @@ import Router, {
Route,
} from './router'
import { sendHTML } from './send-html'
import { sendPayload } from './send-payload'
import { serveStatic } from './serve-static'
import {
getFallback,
Expand Down Expand Up @@ -932,11 +933,11 @@ export default class Server {
sendPayload(
res,
JSON.stringify(renderResult?.renderOpts?.pageData),
'application/json',
'json',
!this.renderOpts.dev
? {
revalidate: -1,
private: isPreviewMode, // Leave to user-land caching
private: isPreviewMode,
stateful: true, // non-SSG data request
}
: undefined
)
Expand All @@ -955,11 +956,11 @@ export default class Server {
sendPayload(
res,
JSON.stringify(props),
'application/json',
'json',
!this.renderOpts.dev
? {
revalidate: -1,
private: isPreviewMode, // Leave to user-land caching
private: isPreviewMode,
stateful: true, // GSSP data request
}
: undefined
)
Expand All @@ -971,11 +972,12 @@ export default class Server {
...opts,
})

if (html && isServerProps && isPreviewMode) {
sendPayload(res, html, 'text/html; charset=utf-8', {
revalidate: -1,
if (html && isServerProps) {
sendPayload(res, html, 'html', {
private: isPreviewMode,
stateful: true, // GSSP request
})
return null
}

return html
Expand All @@ -1000,9 +1002,16 @@ export default class Server {
sendPayload(
res,
data,
isDataReq ? 'application/json' : 'text/html; charset=utf-8',
cachedData.curRevalidate !== undefined && !this.renderOpts.dev
? { revalidate: cachedData.curRevalidate, private: isPreviewMode }
isDataReq ? 'json' : 'html',
!this.renderOpts.dev
? {
private: isPreviewMode,
stateful: false, // GSP response
revalidate:
cachedData.curRevalidate !== undefined
? cachedData.curRevalidate
: /* default to minimum revalidate (this should be an invariant) */ 1,
}
: undefined
)

Expand Down Expand Up @@ -1105,7 +1114,12 @@ export default class Server {
query.__nextFallback = 'true'
if (isLikeServerless) {
prepareServerlessUrl(req, query)
html = await (components.Component as any).renderReqToHTML(req, res)
const renderResult = await (components.Component as any).renderReqToHTML(
req,
res,
'passthrough'
)
html = renderResult.html
} else {
html = (await renderToHTML(req, res, pathname, query, {
...components,
Expand All @@ -1114,7 +1128,7 @@ export default class Server {
}
}

sendPayload(res, html, 'text/html; charset=utf-8')
sendPayload(res, html, 'html')
}

const {
Expand All @@ -1125,9 +1139,13 @@ export default class Server {
sendPayload(
res,
isDataReq ? JSON.stringify(pageData) : html,
isDataReq ? 'application/json' : 'text/html; charset=utf-8',
isDataReq ? 'json' : 'html',
!this.renderOpts.dev
? { revalidate: sprRevalidate, private: isPreviewMode }
? {
private: isPreviewMode,
stateful: false, // GSP response
revalidate: sprRevalidate,
}
: undefined
)
}
Expand Down Expand Up @@ -1348,38 +1366,6 @@ export default class Server {
}
}

function sendPayload(
res: ServerResponse,
payload: any,
type: string,
options?: { revalidate: number | false; private: boolean }
) {
// TODO: ETag? Cache-Control headers? Next-specific headers?
res.setHeader('Content-Type', type)
res.setHeader('Content-Length', Buffer.byteLength(payload))
if (options != null) {
if (options?.private) {
res.setHeader(
'Cache-Control',
`private, no-cache, no-store, max-age=0, must-revalidate`
)
} else if (options?.revalidate) {
res.setHeader(
'Cache-Control',
options.revalidate < 0
? `no-cache, no-store, must-revalidate`
: `s-maxage=${options.revalidate}, stale-while-revalidate`
)
} else if (options?.revalidate === false) {
res.setHeader(
'Cache-Control',
`s-maxage=31536000, stale-while-revalidate`
)
}
}
res.end(payload)
}

function prepareServerlessUrl(req: IncomingMessage, query: ParsedUrlQuery) {
const curUrl = parseUrl(req.url!, true)
req.url = formatUrl({
Expand Down
50 changes: 50 additions & 0 deletions packages/next/next-server/server/send-payload.ts
@@ -0,0 +1,50 @@
import { ServerResponse } from 'http'
import { isResSent } from '../lib/utils'

export function sendPayload(
res: ServerResponse,
payload: any,
type: 'html' | 'json',
options?:
| { private: true }
| { private: boolean; stateful: true }
| { private: boolean; stateful: false; revalidate: number | false }
): void {
if (isResSent(res)) {
return
}

// TODO: ETag headers?
res.setHeader(
'Content-Type',
type === 'json' ? 'application/json' : 'text/html; charset=utf-8'
)
res.setHeader('Content-Length', Buffer.byteLength(payload))
if (options != null) {
if (options.private || options.stateful) {
if (options.private || !res.hasHeader('Cache-Control')) {
res.setHeader(
'Cache-Control',
`private, no-cache, no-store, max-age=0, must-revalidate`
)
}
} else if (typeof options.revalidate === 'number') {
if (options.revalidate < 1) {
throw new Error(
`invariant: invalid Cache-Control duration provided: ${options.revalidate} < 1`
)
}

res.setHeader(
'Cache-Control',
`s-maxage=${options.revalidate}, stale-while-revalidate`
)
} else if (options.revalidate === false) {
res.setHeader(
'Cache-Control',
`s-maxage=31536000, stale-while-revalidate`
)
}
}
res.end(payload)
}

0 comments on commit 09045f3

Please sign in to comment.