Skip to content

Commit c24daa2

Browse files
ijjkTimer
authored andcommitted
Add initial support for unstable_getServerProps (vercel#10077)
* Add support for unstable_getServerProps * Apply suggestions from review * Add no-cache header and update types * Revert sharing of load-components type * Add catchall test and update routes-manifest field * Update header check * Update to pass query for getServerProps data requests * Update to not cache getServerProps requests * Rename server side props identifier * Update to nest props for getServerProps * Add no-cache header in serverless-loader also * Update to throw error for mixed SSG/serverProps earlier * Add comment explaining params chosing in serverless-loader * Update invalidKeysMsg to return a string and inline throwing * Inline throwing mixed SSG/serverProps error * Update setting cache header in serverless-loader * Add separate getServerData method in router * Update checkIsSSG -> isDataIdentifier * Refactor router getData back to ternary * Apply suggestions to build/index.ts * drop return * De-dupe extra escape regex * Add param test
1 parent abd69ec commit c24daa2

File tree

26 files changed

+1070
-77
lines changed

26 files changed

+1070
-77
lines changed

packages/next/build/babel/plugins/next-ssg-transform.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
import { NodePath, PluginObj } from '@babel/core'
22
import * as BabelTypes from '@babel/types'
3+
import { SERVER_PROPS_SSG_CONFLICT } from '../../../lib/constants'
34

45
const pageComponentVar = '__NEXT_COMP'
56
const prerenderId = '__N_SSG'
7+
const serverPropsId = '__N_SSP'
68

79
export const EXPORT_NAME_GET_STATIC_PROPS = 'unstable_getStaticProps'
810
export const EXPORT_NAME_GET_STATIC_PATHS = 'unstable_getStaticPaths'
11+
export const EXPORT_NAME_GET_SERVER_PROPS = 'unstable_getServerProps'
912

1013
const ssgExports = new Set([
1114
EXPORT_NAME_GET_STATIC_PROPS,
1215
EXPORT_NAME_GET_STATIC_PATHS,
16+
EXPORT_NAME_GET_SERVER_PROPS,
1317
])
1418

1519
type PluginState = {
1620
refs: Set<NodePath<BabelTypes.Identifier>>
1721
isPrerender: boolean
22+
isServerProps: boolean
1823
done: boolean
1924
}
2025

@@ -44,7 +49,7 @@ function decorateSsgExport(
4449
'=',
4550
t.memberExpression(
4651
t.identifier(pageComponentVar),
47-
t.identifier(prerenderId)
52+
t.identifier(state.isPrerender ? prerenderId : serverPropsId)
4853
),
4954
t.booleanLiteral(true)
5055
),
@@ -55,6 +60,24 @@ function decorateSsgExport(
5560
})
5661
}
5762

63+
const isDataIdentifier = (name: string, state: PluginState): boolean => {
64+
if (ssgExports.has(name)) {
65+
if (name === EXPORT_NAME_GET_SERVER_PROPS) {
66+
if (state.isPrerender) {
67+
throw new Error(SERVER_PROPS_SSG_CONFLICT)
68+
}
69+
state.isServerProps = true
70+
} else {
71+
if (state.isServerProps) {
72+
throw new Error(SERVER_PROPS_SSG_CONFLICT)
73+
}
74+
state.isPrerender = true
75+
}
76+
return true
77+
}
78+
return false
79+
}
80+
5881
export default function nextTransformSsg({
5982
types: t,
6083
}: {
@@ -134,10 +157,11 @@ export default function nextTransformSsg({
134157
enter(_, state) {
135158
state.refs = new Set<NodePath<BabelTypes.Identifier>>()
136159
state.isPrerender = false
160+
state.isServerProps = false
137161
state.done = false
138162
},
139163
exit(path, state) {
140-
if (!state.isPrerender) {
164+
if (!state.isPrerender && !state.isServerProps) {
141165
return
142166
}
143167

@@ -239,8 +263,7 @@ export default function nextTransformSsg({
239263
const specifiers = path.get('specifiers')
240264
if (specifiers.length) {
241265
specifiers.forEach(s => {
242-
if (ssgExports.has(s.node.exported.name)) {
243-
state.isPrerender = true
266+
if (isDataIdentifier(s.node.exported.name, state)) {
244267
s.remove()
245268
}
246269
})
@@ -259,8 +282,7 @@ export default function nextTransformSsg({
259282
switch (decl.node.type) {
260283
case 'FunctionDeclaration': {
261284
const name = decl.node.id!.name
262-
if (ssgExports.has(name)) {
263-
state.isPrerender = true
285+
if (isDataIdentifier(name, state)) {
264286
path.remove()
265287
}
266288
break
@@ -274,8 +296,7 @@ export default function nextTransformSsg({
274296
return
275297
}
276298
const name = d.node.id.name
277-
if (ssgExports.has(name)) {
278-
state.isPrerender = true
299+
if (isDataIdentifier(name, state)) {
279300
d.remove()
280301
}
281302
})

packages/next/build/index.ts

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
} from './utils'
6464
import getBaseWebpackConfig from './webpack-config'
6565
import { writeBuildId } from './write-build-id'
66+
import escapeStringRegexp from 'escape-string-regexp'
6667

6768
const fsAccess = promisify(fs.access)
6869
const fsUnlink = promisify(fs.unlink)
@@ -258,22 +259,23 @@ export default async function build(dir: string, conf = null): Promise<void> {
258259
}
259260
}
260261

262+
const routesManifestPath = path.join(distDir, ROUTES_MANIFEST)
263+
const routesManifest: any = {
264+
version: 1,
265+
basePath: config.experimental.basePath,
266+
redirects: redirects.map(r => buildCustomRoute(r, 'redirect')),
267+
rewrites: rewrites.map(r => buildCustomRoute(r, 'rewrite')),
268+
headers: headers.map(r => buildCustomRoute(r, 'header')),
269+
dynamicRoutes: getSortedRoutes(dynamicRoutes).map(page => ({
270+
page,
271+
regex: getRouteRegex(page).re.source,
272+
})),
273+
}
274+
261275
await mkdirp(distDir)
262-
await fsWriteFile(
263-
path.join(distDir, ROUTES_MANIFEST),
264-
JSON.stringify({
265-
version: 1,
266-
basePath: config.experimental.basePath,
267-
redirects: redirects.map(r => buildCustomRoute(r, 'redirect')),
268-
rewrites: rewrites.map(r => buildCustomRoute(r, 'rewrite')),
269-
headers: headers.map(r => buildCustomRoute(r, 'header')),
270-
dynamicRoutes: getSortedRoutes(dynamicRoutes).map(page => ({
271-
page,
272-
regex: getRouteRegex(page).re.source,
273-
})),
274-
}),
275-
'utf8'
276-
)
276+
// We need to write the manifest with rewrites before build
277+
// so serverless can import the manifest
278+
await fsWriteFile(routesManifestPath, JSON.stringify(routesManifest), 'utf8')
277279

278280
const configs = await Promise.all([
279281
getBaseWebpackConfig(dir, {
@@ -405,6 +407,7 @@ export default async function build(dir: string, conf = null): Promise<void> {
405407
const staticPages = new Set<string>()
406408
const invalidPages = new Set<string>()
407409
const hybridAmpPages = new Set<string>()
410+
const serverPropsPages = new Set<string>()
408411
const additionalSsgPaths = new Map<string, Array<string>>()
409412
const pageInfos = new Map<string, PageInfo>()
410413
const pagesManifest = JSON.parse(await fsReadFile(manifestPath, 'utf8'))
@@ -502,6 +505,8 @@ export default async function build(dir: string, conf = null): Promise<void> {
502505
additionalSsgPaths.set(page, result.prerenderRoutes)
503506
ssgPageRoutes = result.prerenderRoutes
504507
}
508+
} else if (result.hasServerProps) {
509+
serverPropsPages.add(page)
505510
} else if (result.isStatic && customAppGetInitialProps === false) {
506511
staticPages.add(page)
507512
isStatic = true
@@ -525,6 +530,41 @@ export default async function build(dir: string, conf = null): Promise<void> {
525530
)
526531
staticCheckWorkers.end()
527532

533+
if (serverPropsPages.size > 0) {
534+
// We update the routes manifest after the build with the
535+
// serverProps routes since we can't determine this until after build
536+
routesManifest.serverPropsRoutes = {}
537+
538+
for (const page of serverPropsPages) {
539+
const dataRoute = path.posix.join(
540+
'/_next/data',
541+
buildId,
542+
`${page === '/' ? '/index' : page}.json`
543+
)
544+
545+
routesManifest.serverPropsRoutes[page] = {
546+
page,
547+
dataRouteRegex: isDynamicRoute(page)
548+
? getRouteRegex(dataRoute.replace(/\.json$/, '')).re.source.replace(
549+
/\(\?:\\\/\)\?\$$/,
550+
'\\.json$'
551+
)
552+
: new RegExp(
553+
`^${path.posix.join(
554+
'/_next/data',
555+
escapeStringRegexp(buildId),
556+
`${page === '/' ? '/index' : page}.json`
557+
)}$`
558+
).source,
559+
}
560+
}
561+
562+
await fsWriteFile(
563+
routesManifestPath,
564+
JSON.stringify(routesManifest),
565+
'utf8'
566+
)
567+
}
528568
// Since custom _app.js can wrap the 404 page we have to opt-out of static optimization if it has getInitialProps
529569
// Only export the static 404 when there is no /_error present
530570
const useStatic404 =

packages/next/build/utils.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import {
99
Rewrite,
1010
getRedirectStatus,
1111
} from '../lib/check-custom-routes'
12-
import { SSG_GET_INITIAL_PROPS_CONFLICT } from '../lib/constants'
12+
import {
13+
SSG_GET_INITIAL_PROPS_CONFLICT,
14+
SERVER_PROPS_GET_INIT_PROPS_CONFLICT,
15+
SERVER_PROPS_SSG_CONFLICT,
16+
} from '../lib/constants'
1317
import prettyBytes from '../lib/pretty-bytes'
1418
import { recursiveReadDir } from '../lib/recursive-readdir'
1519
import { getRouteMatcher, getRouteRegex } from '../next-server/lib/router/utils'
@@ -481,6 +485,7 @@ export async function isPageStatic(
481485
): Promise<{
482486
isStatic?: boolean
483487
isHybridAmp?: boolean
488+
hasServerProps?: boolean
484489
hasStaticProps?: boolean
485490
prerenderRoutes?: string[] | undefined
486491
}> {
@@ -496,6 +501,7 @@ export async function isPageStatic(
496501
const hasGetInitialProps = !!(Comp as any).getInitialProps
497502
const hasStaticProps = !!mod.unstable_getStaticProps
498503
const hasStaticPaths = !!mod.unstable_getStaticPaths
504+
const hasServerProps = !!mod.unstable_getServerProps
499505
const hasLegacyStaticParams = !!mod.unstable_getStaticParams
500506

501507
if (hasLegacyStaticParams) {
@@ -510,6 +516,14 @@ export async function isPageStatic(
510516
throw new Error(SSG_GET_INITIAL_PROPS_CONFLICT)
511517
}
512518

519+
if (hasGetInitialProps && hasServerProps) {
520+
throw new Error(SERVER_PROPS_GET_INIT_PROPS_CONFLICT)
521+
}
522+
523+
if (hasStaticProps && hasServerProps) {
524+
throw new Error(SERVER_PROPS_SSG_CONFLICT)
525+
}
526+
513527
// A page cannot have static parameters if it is not a dynamic page.
514528
if (hasStaticProps && hasStaticPaths && !isDynamicRoute(page)) {
515529
throw new Error(
@@ -593,10 +607,11 @@ export async function isPageStatic(
593607

594608
const config = mod.config || {}
595609
return {
596-
isStatic: !hasStaticProps && !hasGetInitialProps,
610+
isStatic: !hasStaticProps && !hasGetInitialProps && !hasServerProps,
597611
isHybridAmp: config.amp === 'hybrid',
598612
prerenderRoutes: prerenderPaths,
599613
hasStaticProps,
614+
hasServerProps,
600615
}
601616
} catch (err) {
602617
if (err.code === 'MODULE_NOT_FOUND') return {}

packages/next/build/webpack/loaders/next-serverless-loader.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ const nextServerlessLoader: loader.Loader = function() {
186186
export const unstable_getStaticProps = ComponentInfo['unstable_getStaticProp' + 's']
187187
export const unstable_getStaticParams = ComponentInfo['unstable_getStaticParam' + 's']
188188
export const unstable_getStaticPaths = ComponentInfo['unstable_getStaticPath' + 's']
189+
export const unstable_getServerProps = ComponentInfo['unstable_getServerProp' + 's']
189190
190191
${dynamicRouteMatcher}
191192
${handleRewrites}
@@ -207,6 +208,7 @@ const nextServerlessLoader: loader.Loader = function() {
207208
Document,
208209
buildManifest,
209210
unstable_getStaticProps,
211+
unstable_getServerProps,
210212
unstable_getStaticPaths,
211213
reactLoadableManifest,
212214
canonicalBase: "${canonicalBase}",
@@ -237,7 +239,7 @@ const nextServerlessLoader: loader.Loader = function() {
237239
${page === '/_error' ? `res.statusCode = 404` : ''}
238240
${
239241
pageIsDynamicRoute
240-
? `const params = fromExport && !unstable_getStaticProps ? {} : dynamicRouteMatcher(parsedUrl.pathname) || {};`
242+
? `const params = fromExport && !unstable_getStaticProps && !unstable_getServerProps ? {} : dynamicRouteMatcher(parsedUrl.pathname) || {};`
241243
: `const params = {};`
242244
}
243245
${
@@ -273,15 +275,22 @@ const nextServerlessLoader: loader.Loader = function() {
273275
`
274276
: `const nowParams = null;`
275277
}
278+
// make sure to set renderOpts to the correct params e.g. _params
279+
// if provided from worker or params if we're parsing them here
280+
renderOpts.params = _params || params
281+
276282
let result = await renderToHTML(req, res, "${page}", Object.assign({}, unstable_getStaticProps ? {} : parsedUrl.query, nowParams ? nowParams : params, _params), renderOpts)
277283
278284
if (_nextData && !fromExport) {
279285
const payload = JSON.stringify(renderOpts.pageData)
280286
res.setHeader('Content-Type', 'application/json')
281287
res.setHeader('Content-Length', Buffer.byteLength(payload))
288+
282289
res.setHeader(
283290
'Cache-Control',
284-
\`s-maxage=\${renderOpts.revalidate}, stale-while-revalidate\`
291+
unstable_getServerProps
292+
? \`no-cache, no-store, must-revalidate\`
293+
: \`s-maxage=\${renderOpts.revalidate}, stale-while-revalidate\`
285294
)
286295
res.end(payload)
287296
return null
@@ -295,6 +304,7 @@ const nextServerlessLoader: loader.Loader = function() {
295304
const result = await renderToHTML(req, res, "/_error", parsedUrl.query, Object.assign({}, options, {
296305
unstable_getStaticProps: undefined,
297306
unstable_getStaticPaths: undefined,
307+
unstable_getServerProps: undefined,
298308
Component: Error
299309
}))
300310
return result
@@ -304,6 +314,7 @@ const nextServerlessLoader: loader.Loader = function() {
304314
const result = await renderToHTML(req, res, "/_error", parsedUrl.query, Object.assign({}, options, {
305315
unstable_getStaticProps: undefined,
306316
unstable_getStaticPaths: undefined,
317+
unstable_getServerProps: undefined,
307318
Component: Error,
308319
err
309320
}))

packages/next/export/worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export default async function({
191191
html = components.Component
192192
queryWithAutoExportWarn()
193193
} else {
194-
curRenderOpts = { ...components, ...renderOpts, ampPath }
194+
curRenderOpts = { ...components, ...renderOpts, ampPath, params }
195195
html = await renderMethod(req, res, page, query, curRenderOpts)
196196
}
197197
}

packages/next/lib/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ export const DOT_NEXT_ALIAS = 'private-dot-next'
2525
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`
2626

2727
export const SSG_GET_INITIAL_PROPS_CONFLICT = `You can not use getInitialProps with unstable_getStaticProps. To use SSG, please remove your getInitialProps`
28+
29+
export const SERVER_PROPS_GET_INIT_PROPS_CONFLICT = `You can not use getInitialProps with unstable_getServerProps. Please remove one or the other`
30+
31+
export const SERVER_PROPS_SSG_CONFLICT = `You can not use unstable_getStaticProps with unstable_getServerProps. To use SSG, please remove your unstable_getServerProps`

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy