Fix RenderOpts in next-server
(#10776)
* Correctly pass preview data * remove todo * re-do change * fix types * Prevent regression
This commit is contained in:
parent
94009422c5
commit
8f01a4ae83
4 changed files with 90 additions and 11 deletions
|
@ -260,6 +260,7 @@ const COOKIE_NAME_PRERENDER_BYPASS = `__prerender_bypass`
|
||||||
const COOKIE_NAME_PRERENDER_DATA = `__next_preview_data`
|
const COOKIE_NAME_PRERENDER_DATA = `__next_preview_data`
|
||||||
|
|
||||||
export const SYMBOL_PREVIEW_DATA = Symbol(COOKIE_NAME_PRERENDER_DATA)
|
export const SYMBOL_PREVIEW_DATA = Symbol(COOKIE_NAME_PRERENDER_DATA)
|
||||||
|
const SYMBOL_CLEARED_COOKIES = Symbol(COOKIE_NAME_PRERENDER_BYPASS)
|
||||||
|
|
||||||
export function tryGetPreviewData(
|
export function tryGetPreviewData(
|
||||||
req: IncomingMessage,
|
req: IncomingMessage,
|
||||||
|
@ -405,6 +406,10 @@ function setPreviewData<T>(
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearPreviewData<T>(res: NextApiResponse<T>): NextApiResponse<T> {
|
function clearPreviewData<T>(res: NextApiResponse<T>): NextApiResponse<T> {
|
||||||
|
if (SYMBOL_CLEARED_COOKIES in res) {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
const { serialize } = require('cookie') as typeof import('cookie')
|
const { serialize } = require('cookie') as typeof import('cookie')
|
||||||
const previous = res.getHeader('Set-Cookie')
|
const previous = res.getHeader('Set-Cookie')
|
||||||
res.setHeader(`Set-Cookie`, [
|
res.setHeader(`Set-Cookie`, [
|
||||||
|
@ -432,6 +437,11 @@ function clearPreviewData<T>(res: NextApiResponse<T>): NextApiResponse<T> {
|
||||||
path: '/',
|
path: '/',
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
Object.defineProperty(res, SYMBOL_CLEARED_COOKIES, {
|
||||||
|
value: true,
|
||||||
|
enumerable: false,
|
||||||
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ import pathMatch from './lib/path-match'
|
||||||
import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
|
import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
|
||||||
import { loadComponents, LoadComponentsReturnType } from './load-components'
|
import { loadComponents, LoadComponentsReturnType } from './load-components'
|
||||||
import { normalizePagePath } from './normalize-page-path'
|
import { normalizePagePath } from './normalize-page-path'
|
||||||
import { renderToHTML } from './render'
|
import { RenderOpts, RenderOptsPartial, renderToHTML } from './render'
|
||||||
import { getPagePath } from './require'
|
import { getPagePath } from './require'
|
||||||
import Router, {
|
import Router, {
|
||||||
DynamicRoutes,
|
DynamicRoutes,
|
||||||
|
@ -115,6 +115,7 @@ export default class Server {
|
||||||
documentMiddlewareEnabled: boolean
|
documentMiddlewareEnabled: boolean
|
||||||
hasCssMode: boolean
|
hasCssMode: boolean
|
||||||
dev?: boolean
|
dev?: boolean
|
||||||
|
previewProps: __ApiPreviewProps
|
||||||
}
|
}
|
||||||
private compression?: Middleware
|
private compression?: Middleware
|
||||||
private onErrorMiddleware?: ({ err }: { err: Error }) => Promise<void>
|
private onErrorMiddleware?: ({ err }: { err: Error }) => Promise<void>
|
||||||
|
@ -165,6 +166,7 @@ export default class Server {
|
||||||
staticMarkup,
|
staticMarkup,
|
||||||
buildId: this.buildId,
|
buildId: this.buildId,
|
||||||
generateEtags,
|
generateEtags,
|
||||||
|
previewProps: this.getPreviewProps(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only the `publicRuntimeConfig` key is exposed to the client side
|
// Only the `publicRuntimeConfig` key is exposed to the client side
|
||||||
|
@ -668,13 +670,12 @@ export default class Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const previewProps = this.getPreviewProps()
|
|
||||||
await apiResolver(
|
await apiResolver(
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
query,
|
query,
|
||||||
pageModule,
|
pageModule,
|
||||||
{ ...previewProps },
|
this.renderOpts.previewProps,
|
||||||
this.onErrorMiddleware
|
this.onErrorMiddleware
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
|
@ -869,7 +870,7 @@ export default class Server {
|
||||||
res: ServerResponse,
|
res: ServerResponse,
|
||||||
pathname: string,
|
pathname: string,
|
||||||
{ components, query }: FindComponentsResult,
|
{ components, query }: FindComponentsResult,
|
||||||
opts: any
|
opts: RenderOptsPartial
|
||||||
): Promise<string | false | null> {
|
): Promise<string | false | null> {
|
||||||
// we need to ensure the status code if /404 is visited directly
|
// we need to ensure the status code if /404 is visited directly
|
||||||
if (pathname === '/404') {
|
if (pathname === '/404') {
|
||||||
|
@ -890,7 +891,7 @@ export default class Server {
|
||||||
const hasStaticPaths = !!components.getStaticPaths
|
const hasStaticPaths = !!components.getStaticPaths
|
||||||
|
|
||||||
// Toggle whether or not this is a Data request
|
// Toggle whether or not this is a Data request
|
||||||
const isDataReq = query._nextDataReq
|
const isDataReq = !!query._nextDataReq
|
||||||
delete query._nextDataReq
|
delete query._nextDataReq
|
||||||
|
|
||||||
// Serverless requests need its URL transformed back into the original
|
// Serverless requests need its URL transformed back into the original
|
||||||
|
@ -958,8 +959,11 @@ export default class Server {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const previewProps = this.getPreviewProps()
|
const previewData = tryGetPreviewData(
|
||||||
const previewData = tryGetPreviewData(req, res, { ...previewProps })
|
req,
|
||||||
|
res,
|
||||||
|
this.renderOpts.previewProps
|
||||||
|
)
|
||||||
const isPreviewMode = previewData !== false
|
const isPreviewMode = previewData !== false
|
||||||
|
|
||||||
// Compute the SPR cache key
|
// Compute the SPR cache key
|
||||||
|
@ -1017,15 +1021,16 @@ export default class Server {
|
||||||
pageData = renderResult.renderOpts.pageData
|
pageData = renderResult.renderOpts.pageData
|
||||||
sprRevalidate = renderResult.renderOpts.revalidate
|
sprRevalidate = renderResult.renderOpts.revalidate
|
||||||
} else {
|
} else {
|
||||||
const renderOpts = {
|
const renderOpts: RenderOpts = {
|
||||||
...components,
|
...components,
|
||||||
...opts,
|
...opts,
|
||||||
}
|
}
|
||||||
renderResult = await renderToHTML(req, res, pathname, query, renderOpts)
|
renderResult = await renderToHTML(req, res, pathname, query, renderOpts)
|
||||||
|
|
||||||
html = renderResult
|
html = renderResult
|
||||||
pageData = renderOpts.pageData
|
// TODO: change this to a different passing mechanism
|
||||||
sprRevalidate = renderOpts.revalidate
|
pageData = (renderOpts as any).pageData
|
||||||
|
sprRevalidate = (renderOpts as any).revalidate
|
||||||
}
|
}
|
||||||
|
|
||||||
return { html, pageData, sprRevalidate }
|
return { html, pageData, sprRevalidate }
|
||||||
|
|
|
@ -124,7 +124,7 @@ function render(
|
||||||
return { html, head }
|
return { html, head }
|
||||||
}
|
}
|
||||||
|
|
||||||
type RenderOpts = LoadComponentsReturnType & {
|
export type RenderOptsPartial = {
|
||||||
staticMarkup: boolean
|
staticMarkup: boolean
|
||||||
buildId: string
|
buildId: string
|
||||||
canonicalBase: string
|
canonicalBase: string
|
||||||
|
@ -147,6 +147,8 @@ type RenderOpts = LoadComponentsReturnType & {
|
||||||
previewProps: __ApiPreviewProps
|
previewProps: __ApiPreviewProps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
|
||||||
|
|
||||||
function renderDocument(
|
function renderDocument(
|
||||||
Document: DocumentType,
|
Document: DocumentType,
|
||||||
{
|
{
|
||||||
|
@ -517,6 +519,7 @@ export async function renderToHTML(
|
||||||
|
|
||||||
props.pageProps = data.props
|
props.pageProps = data.props
|
||||||
// pass up revalidate and props for export
|
// pass up revalidate and props for export
|
||||||
|
// TODO: change this to a different passing mechanism
|
||||||
;(renderOpts as any).revalidate = data.revalidate
|
;(renderOpts as any).revalidate = data.revalidate
|
||||||
;(renderOpts as any).pageData = props
|
;(renderOpts as any).pageData = props
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
findPort,
|
findPort,
|
||||||
initNextServerScript,
|
initNextServerScript,
|
||||||
killApp,
|
killApp,
|
||||||
|
launchApp,
|
||||||
nextBuild,
|
nextBuild,
|
||||||
nextStart,
|
nextStart,
|
||||||
renderViaHTTP,
|
renderViaHTTP,
|
||||||
|
@ -201,6 +202,66 @@ const startServerlessEmulator = async (dir, port) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Prerender Preview Mode', () => {
|
describe('Prerender Preview Mode', () => {
|
||||||
|
describe('Development Mode', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await fs.remove(nextConfigPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
let appPort, app
|
||||||
|
it('should start development application', async () => {
|
||||||
|
appPort = await findPort()
|
||||||
|
app = await launchApp(appDir, appPort)
|
||||||
|
})
|
||||||
|
|
||||||
|
let previewCookieString
|
||||||
|
it('should enable preview mode', async () => {
|
||||||
|
const res = await fetchViaHTTP(appPort, '/api/preview', { lets: 'goooo' })
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
|
||||||
|
const cookies = res.headers
|
||||||
|
.get('set-cookie')
|
||||||
|
.split(',')
|
||||||
|
.map(cookie.parse)
|
||||||
|
|
||||||
|
expect(cookies.length).toBe(2)
|
||||||
|
previewCookieString =
|
||||||
|
cookie.serialize('__prerender_bypass', cookies[0].__prerender_bypass) +
|
||||||
|
'; ' +
|
||||||
|
cookie.serialize('__next_preview_data', cookies[1].__next_preview_data)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return cookies to be expired after dev server reboot', async () => {
|
||||||
|
await killApp(app)
|
||||||
|
app = await launchApp(appDir, appPort)
|
||||||
|
|
||||||
|
const res = await fetchViaHTTP(
|
||||||
|
appPort,
|
||||||
|
'/',
|
||||||
|
{},
|
||||||
|
{ headers: { Cookie: previewCookieString } }
|
||||||
|
)
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
|
||||||
|
const body = await res.text()
|
||||||
|
// "err":{"name":"TypeError","message":"Cannot read property 'previewModeId' of undefined"
|
||||||
|
expect(body).not.toContain('err')
|
||||||
|
expect(body).not.toContain('TypeError')
|
||||||
|
expect(body).not.toContain('previewModeId')
|
||||||
|
|
||||||
|
const cookies = res.headers
|
||||||
|
.get('set-cookie')
|
||||||
|
.replace(/(=\w{3}),/g, '$1')
|
||||||
|
.split(',')
|
||||||
|
.map(cookie.parse)
|
||||||
|
|
||||||
|
expect(cookies.length).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await killApp(app)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Server Mode', () => {
|
describe('Server Mode', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await fs.remove(nextConfigPath)
|
await fs.remove(nextConfigPath)
|
||||||
|
|
Loading…
Reference in a new issue