Skip to content

Commit

Permalink
SSG Preview Mode (#10459)
Browse files Browse the repository at this point in the history
* checkpoint: api impl

* Add support for tryGetPreviewData

* snapshot: server(less) support

* Add X-Prerender-Bypass-Mode header support

* Pass preview data to getStaticProps call

* add TODO

* setPreviewData

* 100k iterations

* Handle jwt error

* Write out preview values

* forgot file

* set preview props

* Send preview props

* add preview props

* Pass around more data

* update yarn lock

* Fail on Invalid Prerender Manifest

* Make Missing Prerender Manifest Fatal

* fix ts errors

* fix test

* Fix setting cookies + maxage

* Secure is not needed as we encrypt necessary data

* Set on domain root

* Set cookie max ages

* Render a fallback on-demand for non-dynamic pages

* Test preview mode

* remove old build

* remove snapshots

* Add serverless tests

* use afterAll

* Remove object assigns

* fix cookie spread

* add comment
  • Loading branch information
Timer committed Feb 12, 2020
1 parent 9a9c0d2 commit 3cb3498
Show file tree
Hide file tree
Showing 19 changed files with 768 additions and 75 deletions.
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -40,6 +40,7 @@
"@babel/preset-react": "7.7.0",
"@fullhuman/postcss-purgecss": "1.3.0",
"@mdx-js/loader": "0.18.0",
"@types/cheerio": "0.22.16",
"@types/http-proxy": "1.17.3",
"@types/jest": "24.0.13",
"@types/string-hash": "1.1.1",
Expand All @@ -59,6 +60,7 @@
"caniuse-lite": "^1.0.30001019",
"cheerio": "0.22.0",
"clone": "2.1.2",
"cookie": "0.4.0",
"coveralls": "3.0.3",
"cross-env": "6.0.3",
"cross-spawn": "6.0.5",
Expand Down
6 changes: 4 additions & 2 deletions packages/next/build/entries.ts
@@ -1,12 +1,12 @@
import chalk from 'chalk'
import { join } from 'path'
import { stringify } from 'querystring'

import { API_ROUTE, DOT_NEXT_ALIAS, PAGES_DIR_ALIAS } from '../lib/constants'
import { __ApiPreviewProps } from '../next-server/server/api-utils'
import { isTargetLikeServerless } from '../next-server/server/config'
import { normalizePagePath } from '../next-server/server/normalize-page-path'
import { warn } from './output/log'
import { ServerlessLoaderQuery } from './webpack/loaders/next-serverless-loader'
import { normalizePagePath } from '../next-server/server/normalize-page-path'

type PagesMapping = {
[page: string]: string
Expand Down Expand Up @@ -63,6 +63,7 @@ export function createEntrypoints(
pages: PagesMapping,
target: 'server' | 'serverless' | 'experimental-serverless-trace',
buildId: string,
previewMode: __ApiPreviewProps,
config: any
): Entrypoints {
const client: WebpackEntrypoints = {}
Expand All @@ -88,6 +89,7 @@ export function createEntrypoints(
serverRuntimeConfig: config.serverRuntimeConfig,
})
: '',
previewProps: JSON.stringify(previewMode),
}

Object.keys(pages).forEach(page => {
Expand Down
21 changes: 19 additions & 2 deletions packages/next/build/index.ts
@@ -1,5 +1,6 @@
import chalk from 'chalk'
import ciEnvironment from 'ci-info'
import crypto from 'crypto'
import escapeStringRegexp from 'escape-string-regexp'
import findUp from 'find-up'
import fs from 'fs'
Expand Down Expand Up @@ -41,9 +42,11 @@ import {
getSortedRoutes,
isDynamicRoute,
} from '../next-server/lib/router/utils'
import { __ApiPreviewProps } from '../next-server/server/api-utils'
import loadConfig, {
isTargetLikeServerless,
} from '../next-server/server/config'
import { normalizePagePath } from '../next-server/server/normalize-page-path'
import {
eventBuildCompleted,
eventBuildOptimize,
Expand All @@ -67,7 +70,6 @@ import {
} from './utils'
import getBaseWebpackConfig from './webpack-config'
import { writeBuildId } from './write-build-id'
import { normalizePagePath } from '../next-server/server/normalize-page-path'

const fsAccess = promisify(fs.access)
const fsUnlink = promisify(fs.unlink)
Expand Down Expand Up @@ -97,6 +99,7 @@ export type PrerenderManifest = {
version: number
routes: { [route: string]: SsgRoute }
dynamicRoutes: { [route: string]: DynamicSsgRoute }
preview: __ApiPreviewProps
}

export default async function build(dir: string, conf = null): Promise<void> {
Expand Down Expand Up @@ -198,8 +201,20 @@ export default async function build(dir: string, conf = null): Promise<void> {
const allStaticPages = new Set<string>()
let allPageInfos = new Map<string, PageInfo>()

const previewProps: __ApiPreviewProps = {
previewModeId: crypto.randomBytes(16).toString('hex'),
previewModeSigningKey: crypto.randomBytes(32).toString('hex'),
previewModeEncryptionKey: crypto.randomBytes(32).toString('hex'),
}

const mappedPages = createPagesMapping(pagePaths, config.pageExtensions)
const entrypoints = createEntrypoints(mappedPages, target, buildId, config)
const entrypoints = createEntrypoints(
mappedPages,
target,
buildId,
previewProps,
config
)
const pageKeys = Object.keys(mappedPages)
const dynamicRoutes = pageKeys.filter(page => isDynamicRoute(page))
const conflictingPublicFiles: string[] = []
Expand Down Expand Up @@ -802,6 +817,7 @@ export default async function build(dir: string, conf = null): Promise<void> {
version: 1,
routes: finalPrerenderRoutes,
dynamicRoutes: finalDynamicRoutes,
preview: previewProps,
}

await fsWriteFile(
Expand All @@ -814,6 +830,7 @@ export default async function build(dir: string, conf = null): Promise<void> {
version: 1,
routes: {},
dynamicRoutes: {},
preview: previewProps,
}
await fsWriteFile(
path.join(distDir, PRERENDER_MANIFEST),
Expand Down
18 changes: 14 additions & 4 deletions packages/next/build/webpack/loaders/next-serverless-loader.ts
@@ -1,14 +1,16 @@
import { loader } from 'webpack'
import devalue from 'devalue'
import escapeRegexp from 'escape-string-regexp'
import { join } from 'path'
import { parse } from 'querystring'
import { loader } from 'webpack'
import { API_ROUTE } from '../../../lib/constants'
import {
BUILD_MANIFEST,
ROUTES_MANIFEST,
REACT_LOADABLE_MANIFEST,
ROUTES_MANIFEST,
} from '../../../next-server/lib/constants'
import { isDynamicRoute } from '../../../next-server/lib/router/utils'
import { API_ROUTE } from '../../../lib/constants'
import escapeRegexp from 'escape-string-regexp'
import { __ApiPreviewProps } from '../../../next-server/server/api-utils'

export type ServerlessLoaderQuery = {
page: string
Expand All @@ -23,6 +25,7 @@ export type ServerlessLoaderQuery = {
canonicalBase: string
basePath: string
runtimeConfig: string
previewProps: string
}

const nextServerlessLoader: loader.Loader = function() {
Expand All @@ -39,6 +42,7 @@ const nextServerlessLoader: loader.Loader = function() {
generateEtags,
basePath,
runtimeConfig,
previewProps,
}: ServerlessLoaderQuery =
typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query

Expand All @@ -52,6 +56,10 @@ const nextServerlessLoader: loader.Loader = function() {
const escapedBuildId = escapeRegexp(buildId)
const pageIsDynamicRoute = isDynamicRoute(page)

const encodedPreviewProps = devalue(
JSON.parse(previewProps) as __ApiPreviewProps
)

const runtimeConfigImports = runtimeConfig
? `
const { setConfig } = require('next/dist/next-server/lib/runtime-config')
Expand Down Expand Up @@ -176,6 +184,7 @@ const nextServerlessLoader: loader.Loader = function() {
res,
Object.assign({}, parsedUrl.query, params ),
resolver,
${encodedPreviewProps},
onError
)
} catch (err) {
Expand Down Expand Up @@ -243,6 +252,7 @@ const nextServerlessLoader: loader.Loader = function() {
buildId: "${buildId}",
assetPrefix: "${assetPrefix}",
runtimeConfig: runtimeConfig.publicRuntimeConfig || {},
previewProps: ${encodedPreviewProps},
..._renderOpts
}
let _nextData = false
Expand Down
18 changes: 17 additions & 1 deletion packages/next/next-server/lib/utils.ts
Expand Up @@ -2,7 +2,6 @@ import { IncomingMessage, ServerResponse } from 'http'
import { ParsedUrlQuery } from 'querystring'
import { ComponentType } from 'react'
import { format, URLFormatOptions, UrlObject } from 'url'

import { ManifestItem } from '../server/load-components'
import { NextRouter } from './router/router'

Expand Down Expand Up @@ -201,6 +200,23 @@ export type NextApiResponse<T = any> = ServerResponse & {
*/
json: Send<T>
status: (statusCode: number) => NextApiResponse<T>

/**
* Set preview data for Next.js' prerender mode
*/
setPreviewData: (
data: object | string,
options?: {
/**
* Specifies the number (in seconds) for the preview session to last for.
* The given number will be converted to an integer by rounding down.
* By default, no maximum age is set and the preview session finishes
* when the client shuts down (browser is closed).
*/
maxAge?: number
}
) => NextApiResponse<T>
clearPreviewData: () => NextApiResponse<T>
}

/**
Expand Down

0 comments on commit 3cb3498

Please sign in to comment.