Skip to content

Commit

Permalink
Update to make sure preview mode works with getServerSideProps (#10813)
Browse files Browse the repository at this point in the history
* Update to make sure preview mode works with getServerSideProps

* Update to only parse previewData in GS(S)P mode
  • Loading branch information
ijjk committed Mar 4, 2020
1 parent 81c126e commit 70cb5bd
Show file tree
Hide file tree
Showing 8 changed files with 423 additions and 40 deletions.
30 changes: 20 additions & 10 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -905,6 +905,14 @@ export default class Server {
})
}

let previewData: string | false | object | undefined
let isPreviewMode = false

if (isServerProps || isSSG) {
previewData = tryGetPreviewData(req, res, this.renderOpts.previewProps)
isPreviewMode = previewData !== false
}

// non-spr requests should render like normal
if (!isSSG) {
// handle serverless
Expand All @@ -923,7 +931,7 @@ export default class Server {
!this.renderOpts.dev
? {
revalidate: -1,
private: false, // Leave to user-land caching
private: isPreviewMode, // Leave to user-land caching
}
: undefined
)
Expand All @@ -946,25 +954,27 @@ export default class Server {
!this.renderOpts.dev
? {
revalidate: -1,
private: false, // Leave to user-land caching
private: isPreviewMode, // Leave to user-land caching
}
: undefined
)
return null
}

return renderToHTML(req, res, pathname, query, {
const html = await renderToHTML(req, res, pathname, query, {
...components,
...opts,
})
}

const previewData = tryGetPreviewData(
req,
res,
this.renderOpts.previewProps
)
const isPreviewMode = previewData !== false
if (html && isServerProps && isPreviewMode) {
sendPayload(res, html, 'text/html; charset=utf-8', {
revalidate: -1,
private: isPreviewMode,
})
}

return html
}

// Compute the SPR cache key
const urlPathname = parseUrl(req.url || '').pathname!
Expand Down
67 changes: 37 additions & 30 deletions packages/next/next-server/server/render.tsx
Expand Up @@ -321,7 +321,7 @@ export async function renderToHTML(
const isFallback = !!query.__nextFallback
delete query.__nextFallback

const isSpr = !!getStaticProps
const isSSG = !!getStaticProps
const defaultAppGetInitialProps =
App.getInitialProps === (App as any).origGetInitialProps

Expand All @@ -332,7 +332,7 @@ export async function renderToHTML(
const isAutoExport =
!hasPageGetInitialProps &&
defaultAppGetInitialProps &&
!isSpr &&
!isSSG &&
!getServerSideProps

if (
Expand All @@ -355,25 +355,25 @@ export async function renderToHTML(
)
}

if (hasPageGetInitialProps && isSpr) {
if (hasPageGetInitialProps && isSSG) {
throw new Error(SSG_GET_INITIAL_PROPS_CONFLICT + ` ${pathname}`)
}

if (hasPageGetInitialProps && getServerSideProps) {
throw new Error(SERVER_PROPS_GET_INIT_PROPS_CONFLICT + ` ${pathname}`)
}

if (getServerSideProps && isSpr) {
if (getServerSideProps && isSSG) {
throw new Error(SERVER_PROPS_SSG_CONFLICT + ` ${pathname}`)
}

if (!!getStaticPaths && !isSpr) {
if (!!getStaticPaths && !isSSG) {
throw new Error(
`getStaticPaths was added without a getStaticProps in ${pathname}. Without getStaticProps, getStaticPaths does nothing`
)
}

if (isSpr && pageIsDynamic && !getStaticPaths) {
if (isSSG && pageIsDynamic && !getStaticPaths) {
throw new Error(
`getStaticPaths is required for dynamic SSG pages and is missing for '${pathname}'.` +
`\nRead more: https://err.sh/next.js/invalid-getstaticpaths-value`
Expand Down Expand Up @@ -414,7 +414,7 @@ export async function renderToHTML(
}
}
if (isAutoExport) renderOpts.autoExport = true
if (isSpr) renderOpts.nextExport = false
if (isSSG) renderOpts.nextExport = false

await Loadable.preloadAll() // Make sure all dynamic imports are loaded

Expand Down Expand Up @@ -471,12 +471,16 @@ export async function renderToHTML(
router,
ctx,
})
let previewData: string | false | object | undefined

if (isSpr && !isFallback) {
if ((isSSG || getServerSideProps) && !isFallback) {
// Reads of this are cached on the `req` object, so this should resolve
// instantly. There's no need to pass this data down from a previous
// invoke, where we'd have to consider server & serverless.
const previewData = tryGetPreviewData(req, res, previewProps)
previewData = tryGetPreviewData(req, res, previewProps)
}

if (isSSG && !isFallback) {
const data = await getStaticProps!({
...(pageIsDynamic ? { params: query as ParsedUrlQuery } : undefined),
...(previewData !== false
Expand Down Expand Up @@ -529,33 +533,36 @@ export async function renderToHTML(
;(renderOpts as any).revalidate = data.revalidate
;(renderOpts as any).pageData = props
}

if (getServerSideProps && !isFallback) {
const data = await getServerSideProps({
req,
res,
query,
...(pageIsDynamic ? { params: params as ParsedUrlQuery } : undefined),
...(previewData !== false
? { preview: true, previewData: previewData }
: undefined),
})

const invalidKeys = Object.keys(data).filter(key => key !== 'props')

if (invalidKeys.length) {
throw new Error(invalidKeysMsg('getServerSideProps', invalidKeys))
}

props.pageProps = data.props
;(renderOpts as any).pageData = props
}
} catch (err) {
if (!dev || !err) throw err
ctx.err = err
renderOpts.err = err
console.error(err)
}

if (getServerSideProps && !isFallback) {
const data = await getServerSideProps({
req,
res,
...(pageIsDynamic ? { params: params as ParsedUrlQuery } : undefined),
query,
})

const invalidKeys = Object.keys(data).filter(key => key !== 'props')

if (invalidKeys.length) {
throw new Error(invalidKeysMsg('getServerSideProps', invalidKeys))
}

props.pageProps = data.props
;(renderOpts as any).pageData = props
}

if (
!isSpr && // we only show this warning for legacy pages
!isSSG && // we only show this warning for legacy pages
!getServerSideProps &&
process.env.NODE_ENV !== 'production' &&
Object.keys(props?.pageProps || {}).includes('url')
Expand All @@ -577,7 +584,7 @@ export async function renderToHTML(
}

// the response might be finished on the getInitialProps call
if (isResSent(res) && !isSpr) return null
if (isResSent(res) && !isSSG) return null

const devFiles = buildManifest.devFiles
const files = [
Expand Down Expand Up @@ -634,7 +641,7 @@ export async function renderToHTML(
documentCtx
)
// the response might be finished on the getInitialProps call
if (isResSent(res) && !isSpr) return null
if (isResSent(res) && !isSSG) return null

if (!docProps || typeof docProps.html !== 'string') {
const message = `"${getDisplayName(
Expand Down
2 changes: 2 additions & 0 deletions packages/next/types/index.d.ts
Expand Up @@ -83,6 +83,8 @@ export type GetServerSideProps = (context: {
res: ServerResponse
params?: ParsedUrlQuery
query: ParsedUrlQuery
preview?: boolean
previewData?: any
}) => Promise<{ [key: string]: any }>

export default next
@@ -0,0 +1,4 @@
export default (req, res) => {
res.setPreviewData(req.query)
res.status(200).end()
}
@@ -0,0 +1,4 @@
export default (req, res) => {
res.clearPreviewData()
res.status(200).end()
}
15 changes: 15 additions & 0 deletions test/integration/getserversideprops-preview/pages/index.js
@@ -0,0 +1,15 @@
export function getServerSideProps({ preview, previewData }) {
return { props: { hasProps: true, preview, previewData } }
}

export default function({ hasProps, preview, previewData }) {
if (!hasProps) {
return <pre id="props-pre">Has No Props</pre>
}

return (
<pre id="props-pre">
{JSON.stringify(preview) + ' and ' + JSON.stringify(previewData)}
</pre>
)
}
41 changes: 41 additions & 0 deletions test/integration/getserversideprops-preview/server.js
@@ -0,0 +1,41 @@
const http = require('http')
const url = require('url')
const fs = require('fs')
const path = require('path')
const server = http.createServer((req, res) => {
let { pathname } = url.parse(req.url)
if (pathname.startsWith('/_next/data')) {
pathname = pathname
.replace(`/_next/data/${process.env.BUILD_ID}/`, '/')
.replace(/\.json$/, '')
}
console.log('serving', pathname)

if (pathname === '/favicon.ico') {
res.statusCode = 404
return res.end()
}

if (pathname.startsWith('/_next/static/')) {
res.write(
fs.readFileSync(
path.join(
__dirname,
'./.next/static/',
pathname.slice('/_next/static/'.length)
),
'utf8'
)
)
return res.end()
} else {
const re = require(`./.next/serverless/pages${pathname}`)
return typeof re.render === 'function'
? re.render(req, res)
: re.default(req, res)
}
})

server.listen(process.env.PORT, () => {
console.log('ready on', process.env.PORT)
})

0 comments on commit 70cb5bd

Please sign in to comment.