Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial support for unstable_getServerProps #10077

Merged
merged 41 commits into from Jan 27, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
440d855
Add support for unstable_getServerProps
ijjk Jan 13, 2020
8a2d304
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 14, 2020
1ebb040
Apply suggestions from review
ijjk Jan 14, 2020
2cc40cf
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 14, 2020
576572b
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 15, 2020
6c0ac34
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 15, 2020
def1507
Add no-cache header and update types
ijjk Jan 17, 2020
30ad22f
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 17, 2020
40b3e68
Revert sharing of load-components type
ijjk Jan 17, 2020
7397571
Add catchall test and update routes-manifest field
ijjk Jan 17, 2020
8fcab0b
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 17, 2020
25be5ef
Update header check
ijjk Jan 17, 2020
83c89a8
Update to pass query for getServerProps data requests
ijjk Jan 17, 2020
351d424
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 17, 2020
9a5bf52
Update to not cache getServerProps requests
ijjk Jan 17, 2020
2c77127
Merge remote-tracking branch 'upstream/canary' into add/getserverprops
ijjk Jan 19, 2020
931d86c
Rename server side props identifier
ijjk Jan 19, 2020
3c17711
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 20, 2020
65d3634
Merge branch 'canary' into add/getServerProps
ijjk Jan 21, 2020
e4ad30c
Merge branch 'canary' into add/getServerProps
ijjk Jan 21, 2020
d456df1
Update to nest props for getServerProps
ijjk Jan 22, 2020
9ed63e5
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 22, 2020
fea16ca
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 24, 2020
55e05fc
Merge branch 'canary' into add/getServerProps
ijjk Jan 24, 2020
1ad34d7
Add no-cache header in serverless-loader also
ijjk Jan 24, 2020
7a1cc60
Update to throw error for mixed SSG/serverProps earlier
ijjk Jan 24, 2020
ff69a10
Add comment explaining params chosing in serverless-loader
ijjk Jan 24, 2020
862a617
Update invalidKeysMsg to return a string and inline throwing
ijjk Jan 24, 2020
9a2a4de
Inline throwing mixed SSG/serverProps error
ijjk Jan 24, 2020
a3c2576
Update setting cache header in serverless-loader
ijjk Jan 24, 2020
bb290f1
Add separate getServerData method in router
ijjk Jan 24, 2020
43a88b4
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 24, 2020
aadac7e
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 24, 2020
300d354
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 25, 2020
95fd2d8
Merge remote-tracking branch 'upstream/canary' into add/getServerProps
ijjk Jan 27, 2020
15bf407
Update checkIsSSG -> isDataIdentifier
ijjk Jan 27, 2020
1d8752b
Refactor router getData back to ternary
ijjk Jan 27, 2020
27bbb2b
Apply suggestions to build/index.ts
ijjk Jan 27, 2020
d9ee71c
drop return
ijjk Jan 27, 2020
2e8ef84
De-dupe extra escape regex
ijjk Jan 27, 2020
4fe847a
Add param test
ijjk Jan 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/next/build/babel/plugins/next-ssg-transform.ts
Expand Up @@ -6,10 +6,12 @@ const prerenderId = '__N_SSG'

export const EXPORT_NAME_GET_STATIC_PROPS = 'unstable_getStaticProps'
export const EXPORT_NAME_GET_STATIC_PATHS = 'unstable_getStaticPaths'
export const EXPORT_NAME_GET_SERVER_PROPS = 'unstable_getServerProps'

const ssgExports = new Set([
EXPORT_NAME_GET_STATIC_PROPS,
EXPORT_NAME_GET_STATIC_PATHS,
EXPORT_NAME_GET_SERVER_PROPS,
])

type PluginState = {
Expand Down
57 changes: 42 additions & 15 deletions packages/next/build/index.ts
Expand Up @@ -255,22 +255,23 @@ export default async function build(dir: string, conf = null): Promise<void> {
}
}

const routesManifestPath = path.join(distDir, ROUTES_MANIFEST)
const routesManifest: any = {
version: 1,
basePath: config.experimental.basePath,
redirects: redirects.map(r => buildCustomRoute(r, 'redirect')),
rewrites: rewrites.map(r => buildCustomRoute(r, 'rewrite')),
headers: headers.map(r => buildCustomRoute(r, 'header')),
dynamicRoutes: getSortedRoutes(dynamicRoutes).map(page => ({
page,
regex: getRouteRegex(page).re.source,
})),
}

await mkdirp(distDir)
await fsWriteFile(
path.join(distDir, ROUTES_MANIFEST),
JSON.stringify({
version: 1,
basePath: config.experimental.basePath,
redirects: redirects.map(r => buildCustomRoute(r, 'redirect')),
rewrites: rewrites.map(r => buildCustomRoute(r, 'rewrite')),
headers: headers.map(r => buildCustomRoute(r, 'header')),
dynamicRoutes: getSortedRoutes(dynamicRoutes).map(page => ({
page,
regex: getRouteRegex(page).re.source,
})),
}),
'utf8'
)
// We need to write the manifest with rewrites before build
// so serverless can import the manifest
await fsWriteFile(routesManifestPath, JSON.stringify(routesManifest), 'utf8')

const configs = await Promise.all([
getBaseWebpackConfig(dir, {
Expand Down Expand Up @@ -402,6 +403,7 @@ export default async function build(dir: string, conf = null): Promise<void> {
const staticPages = new Set<string>()
const invalidPages = new Set<string>()
const hybridAmpPages = new Set<string>()
const serverPropsPages = new Set<string>()
const additionalSsgPaths = new Map<string, Array<string>>()
const pageInfos = new Map<string, PageInfo>()
const pagesManifest = JSON.parse(await fsReadFile(manifestPath, 'utf8'))
Expand Down Expand Up @@ -498,6 +500,8 @@ export default async function build(dir: string, conf = null): Promise<void> {
additionalSsgPaths.set(page, result.prerenderRoutes)
ssgPageRoutes = result.prerenderRoutes
}
} else if (result.hasServerProps) {
serverPropsPages.add(page)
} else if (result.static && customAppGetInitialProps === false) {
staticPages.add(page)
isStatic = true
Expand All @@ -521,6 +525,29 @@ export default async function build(dir: string, conf = null): Promise<void> {
)
staticCheckWorkers.end()

if (serverPropsPages.size > 0) {
// We update the routes manifest after the build with the
// serverProps routes since we can't determine this until after build
routesManifest.serverPropsRoutes = {}

for (const page of serverPropsPages) {
routesManifest.serverPropsRoutes[page] = {
page,
dataRoute: path.posix.join(
'/_next/data',
buildId,
`${page === '/' ? '/index' : page}.json`
),
}
}

await fsWriteFile(
routesManifestPath,
JSON.stringify(routesManifest),
'utf8'
)
}

if (invalidPages.size > 0) {
throw new Error(
`Build optimization failed: found page${
Expand Down
19 changes: 17 additions & 2 deletions packages/next/build/utils.ts
Expand Up @@ -9,7 +9,11 @@ import {
Rewrite,
getRedirectStatus,
} from '../lib/check-custom-routes'
import { SSG_GET_INITIAL_PROPS_CONFLICT } from '../lib/constants'
import {
SSG_GET_INITIAL_PROPS_CONFLICT,
SERVER_PROPS_GET_INIT_PROPS_CONFLICT,
SERVER_PROPS_SSG_CONFLICT,
} from '../lib/constants'
import prettyBytes from '../lib/pretty-bytes'
import { recursiveReadDir } from '../lib/recursive-readdir'
import { getRouteMatcher, getRouteRegex } from '../next-server/lib/router/utils'
Expand Down Expand Up @@ -482,6 +486,7 @@ export async function isPageStatic(
static?: boolean
prerender?: boolean
isHybridAmp?: boolean
hasServerProps?: boolean
prerenderRoutes?: string[] | undefined
}> {
try {
Expand All @@ -496,6 +501,7 @@ export async function isPageStatic(
const hasGetInitialProps = !!(Comp as any).getInitialProps
const hasStaticProps = !!mod.unstable_getStaticProps
const hasStaticPaths = !!mod.unstable_getStaticPaths
const hasServerProps = !!mod.unstable_getServerProps
const hasLegacyStaticParams = !!mod.unstable_getStaticParams

if (hasLegacyStaticParams) {
Expand All @@ -510,6 +516,14 @@ export async function isPageStatic(
throw new Error(SSG_GET_INITIAL_PROPS_CONFLICT)
}

if (hasGetInitialProps && hasServerProps) {
throw new Error(SERVER_PROPS_GET_INIT_PROPS_CONFLICT)
}

if (hasStaticProps && hasServerProps) {
throw new Error(SERVER_PROPS_SSG_CONFLICT)
}

// A page cannot have static parameters if it is not a dynamic page.
if (hasStaticProps && hasStaticPaths && !isDynamicRoute(page)) {
throw new Error(
Expand Down Expand Up @@ -579,10 +593,11 @@ export async function isPageStatic(

const config = mod.config || {}
return {
static: !hasStaticProps && !hasGetInitialProps,
static: !hasStaticProps && !hasGetInitialProps && !hasServerProps,
ijjk marked this conversation as resolved.
Show resolved Hide resolved
ijjk marked this conversation as resolved.
Show resolved Hide resolved
isHybridAmp: config.amp === 'hybrid',
prerenderRoutes: prerenderPaths,
prerender: hasStaticProps,
ijjk marked this conversation as resolved.
Show resolved Hide resolved
hasServerProps,
}
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') return {}
Expand Down
19 changes: 14 additions & 5 deletions packages/next/build/webpack/loaders/next-serverless-loader.ts
Expand Up @@ -185,6 +185,7 @@ const nextServerlessLoader: loader.Loader = function() {
export const unstable_getStaticProps = ComponentInfo['unstable_getStaticProp' + 's']
export const unstable_getStaticParams = ComponentInfo['unstable_getStaticParam' + 's']
export const unstable_getStaticPaths = ComponentInfo['unstable_getStaticPath' + 's']
export const unstable_getServerProps = ComponentInfo['unstable_getServerProp' + 's']

${dynamicRouteMatcher}
${handleRewrites}
Expand All @@ -206,6 +207,7 @@ const nextServerlessLoader: loader.Loader = function() {
Document,
buildManifest,
unstable_getStaticProps,
unstable_getServerProps,
unstable_getStaticPaths,
reactLoadableManifest,
canonicalBase: "${canonicalBase}",
Expand Down Expand Up @@ -235,7 +237,7 @@ const nextServerlessLoader: loader.Loader = function() {
${page === '/_error' ? `res.statusCode = 404` : ''}
${
pageIsDynamicRoute
? `const params = fromExport && !unstable_getStaticProps ? {} : dynamicRouteMatcher(parsedUrl.pathname) || {};`
? `const params = fromExport && !unstable_getStaticProps && !unstable_getServerProps ? {} : dynamicRouteMatcher(parsedUrl.pathname) || {};`
: `const params = {};`
}
${
Expand Down Expand Up @@ -271,16 +273,21 @@ const nextServerlessLoader: loader.Loader = function() {
`
: `const nowParams = null;`
}
renderOpts.params = _params || params
ijjk marked this conversation as resolved.
Show resolved Hide resolved

let result = await renderToHTML(req, res, "${page}", Object.assign({}, unstable_getStaticProps ? {} : parsedUrl.query, nowParams ? nowParams : params, _params), renderOpts)

if (_nextData && !fromExport) {
const payload = JSON.stringify(renderOpts.pageData)
res.setHeader('Content-Type', 'application/json')
res.setHeader('Content-Length', Buffer.byteLength(payload))
res.setHeader(
'Cache-Control',
\`s-maxage=\${renderOpts.revalidate}, stale-while-revalidate\`
)

if (renderOpts.revalidate) {
ijjk marked this conversation as resolved.
Show resolved Hide resolved
res.setHeader(
'Cache-Control',
\`s-maxage=\${renderOpts.revalidate}, stale-while-revalidate\`
)
}
res.end(payload)
return null
}
Expand All @@ -293,6 +300,7 @@ const nextServerlessLoader: loader.Loader = function() {
const result = await renderToHTML(req, res, "/_error", parsedUrl.query, Object.assign({}, options, {
unstable_getStaticProps: undefined,
unstable_getStaticPaths: undefined,
unstable_getServerProps: undefined,
Component: Error
}))
return result
Expand All @@ -302,6 +310,7 @@ const nextServerlessLoader: loader.Loader = function() {
const result = await renderToHTML(req, res, "/_error", parsedUrl.query, Object.assign({}, options, {
unstable_getStaticProps: undefined,
unstable_getStaticPaths: undefined,
unstable_getServerProps: undefined,
Component: Error,
err
}))
Expand Down
2 changes: 1 addition & 1 deletion packages/next/export/worker.js
Expand Up @@ -177,7 +177,7 @@ export default async function({
if (typeof components.Component === 'string') {
html = components.Component
} else {
curRenderOpts = { ...components, ...renderOpts, ampPath }
curRenderOpts = { ...components, ...renderOpts, ampPath, params }
html = await renderMethod(req, res, page, query, curRenderOpts)
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/next/lib/constants.ts
Expand Up @@ -25,3 +25,7 @@ export const DOT_NEXT_ALIAS = 'private-dot-next'
export const PUBLIC_DIR_MIDDLEWARE_CONFLICT = `You can not have a '_next' folder inside of your public folder. This conflicts with the internal '/_next' route. https://err.sh/zeit/next.js/public-next-folder-conflict`

export const SSG_GET_INITIAL_PROPS_CONFLICT = `You can not use getInitialProps with unstable_getStaticProps. To use SSG, please remove your getInitialProps`

export const SERVER_PROPS_GET_INIT_PROPS_CONFLICT = `You can not use getInitialProps with unstable_getServerProps. Please remove one or the other`

export const SERVER_PROPS_SSG_CONFLICT = `You can not use unstable_getStaticProps with unstable_getServerProps. To use SSG, please remove your unstable_getServerProps`
9 changes: 9 additions & 0 deletions packages/next/next-server/server/load-components.ts
@@ -1,3 +1,5 @@
import { IncomingMessage, ServerResponse } from 'http'
import { ParsedUrlQuery } from 'querystring'
import {
BUILD_MANIFEST,
CLIENT_STATIC_FILES_PATH,
Expand All @@ -22,6 +24,12 @@ export type LoadComponentsReturnType = {
revalidate?: number | boolean
}
unstable_getStaticPaths?: () => void
unstable_getServerProps?: (context: {
params: { [key: string]: string }
ijjk marked this conversation as resolved.
Show resolved Hide resolved
req: IncomingMessage
res: ServerResponse
query: ParsedUrlQuery
ijjk marked this conversation as resolved.
Show resolved Hide resolved
}) => Promise<{ [key: string]: any }>
buildManifest?: any
reactLoadableManifest?: any
Document?: any
Expand Down Expand Up @@ -90,6 +98,7 @@ export async function loadComponents(
DocumentMiddleware,
reactLoadableManifest,
pageConfig: ComponentMod.config || {},
unstable_getServerProps: ComponentMod.unstable_getServerProps,
unstable_getStaticProps: ComponentMod.unstable_getStaticProps,
unstable_getStaticPaths: ComponentMod.unstable_getStaticPaths,
}
Expand Down
49 changes: 37 additions & 12 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -830,10 +830,36 @@ export default class Server {
typeof result.Component.renderReqToHTML === 'function'
const isSSG = !!result.unstable_getStaticProps

// Toggle whether or not this is a Data request
const isDataReq = query._nextDataReq
delete query._nextDataReq

// Serverless requests need its URL transformed back into the original
// request path (to emulate lambda behavior in production)
if (isLikeServerless && isDataReq) {
let { pathname } = parseUrl(req.url || '', true)
pathname = !pathname || pathname === '/' ? '/index' : pathname
req.url = `/_next/data/${this.buildId}${pathname}.json`
}

// non-spr requests should render like normal
if (!isSSG) {
// handle serverless
if (isLikeServerless) {
if (isDataReq) {
const renderResult = await result.Component.renderReqToHTML(
req,
res,
true
)

this.__sendPayload(
res,
JSON.stringify(renderResult?.renderOpts?.pageData),
'application/json'
)
return null
}
const curUrl = parseUrl(req.url!, true)
req.url = formatUrl({
...curUrl,
Expand All @@ -846,16 +872,22 @@ export default class Server {
return result.Component.renderReqToHTML(req, res)
}

if (isDataReq && typeof result.unstable_getServerProps === 'function') {
const props = await renderToHTML(req, res, pathname, query, {
...result,
...opts,
isDataReq,
})
this.__sendPayload(res, JSON.stringify(props), 'application/json')
return null
}

return renderToHTML(req, res, pathname, query, {
...result,
...opts,
})
}

// Toggle whether or not this is an SPR Data request
const isDataReq = query._nextDataReq
delete query._nextDataReq

// Compute the SPR cache key
const ssgCacheKey = parseUrl(req.url || '').pathname!

Expand All @@ -881,14 +913,6 @@ export default class Server {

// If we're here, that means data is missing or it's stale.

// Serverless requests need its URL transformed back into the original
// request path (to emulate lambda behavior in production)
if (isLikeServerless && isDataReq) {
let { pathname } = parseUrl(req.url || '', true)
pathname = !pathname || pathname === '/' ? '/index' : pathname
req.url = `/_next/data/${this.buildId}${pathname}.json`
}

const doRender = withCoalescedInvoke(async function(): Promise<{
html: string | null
pageData: any
Expand Down Expand Up @@ -1001,6 +1025,7 @@ export default class Server {
result,
{
...this.renderOpts,
params,
amphtml,
hasAmp,
}
Expand Down