From 45066cdf4483288c914a141972aa8d4e41e0d2e4 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 26 Feb 2020 15:47:51 -0600 Subject: [PATCH] [now-next] Add /_next/data routes for getServerProps pages (#3771) This adds the `/_next/data` routes for `getServerProps` pages if they exist x-ref: https://github.com/zeit/next.js/pull/10077 x-ref: https://github.com/zeit/next.js/pull/10622 --- packages/now-cli/test/unit.js | 4 +- packages/now-next/src/index.ts | 78 +++++++-- packages/now-next/src/utils.ts | 1 + .../test/fixtures/05-spr-support/now.json | 15 ++ .../fixtures/18-ssg-fallback-support/now.json | 15 ++ .../18-ssg-fallback-support/package.json | 2 +- .../fixtures/21-server-props/next.config.js | 5 + .../test/fixtures/21-server-props/now.json | 164 ++++++++++++++++++ .../fixtures/21-server-props/package.json | 7 + .../fixtures/21-server-props/pages/another.js | 20 +++ .../21-server-props/pages/another2.js | 20 +++ .../pages/blog/[post]/[comment].js | 22 +++ .../pages/blog/[post]/index.js | 26 +++ .../fixtures/21-server-props/pages/forever.js | 20 +++ .../fixtures/21-server-props/pages/index.js | 1 + .../fixtures/21-server-props/pages/lambda.js | 5 + 16 files changed, 382 insertions(+), 23 deletions(-) create mode 100644 packages/now-next/test/fixtures/21-server-props/next.config.js create mode 100644 packages/now-next/test/fixtures/21-server-props/now.json create mode 100644 packages/now-next/test/fixtures/21-server-props/package.json create mode 100644 packages/now-next/test/fixtures/21-server-props/pages/another.js create mode 100644 packages/now-next/test/fixtures/21-server-props/pages/another2.js create mode 100644 packages/now-next/test/fixtures/21-server-props/pages/blog/[post]/[comment].js create mode 100644 packages/now-next/test/fixtures/21-server-props/pages/blog/[post]/index.js create mode 100644 packages/now-next/test/fixtures/21-server-props/pages/forever.js create mode 100644 packages/now-next/test/fixtures/21-server-props/pages/index.js create mode 100644 packages/now-next/test/fixtures/21-server-props/pages/lambda.js diff --git a/packages/now-cli/test/unit.js b/packages/now-cli/test/unit.js index ffa5c34008a..2d0620fb16a 100755 --- a/packages/now-cli/test/unit.js +++ b/packages/now-cli/test/unit.js @@ -69,9 +69,7 @@ const getStaticFiles = async (dir, isBuilds = false) => { const normalizeWindowsPaths = files => { if (process.platform === 'win32') { const prefix = 'D:/a/now/now/packages/now-cli/test/fixtures/unit/'; - return files.map(f => - f.replace(/\\/g, '/').slice(prefix.length) - ); + return files.map(f => f.replace(/\\/g, '/').slice(prefix.length)); } return files; }; diff --git a/packages/now-next/src/index.ts b/packages/now-next/src/index.ts index f02b84233d0..314d72103e2 100644 --- a/packages/now-next/src/index.ts +++ b/packages/now-next/src/index.ts @@ -18,7 +18,7 @@ import { runNpmInstall, runPackageJsonScript, } from '@now/build-utils'; -import { Route, Source } from '@now/routing-utils'; +import { Route } from '@now/routing-utils'; import { convertHeaders, convertRedirects, @@ -336,11 +336,17 @@ export const build = async ({ env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`; await runPackageJsonScript(entryPath, shouldRunScript, { ...spawnOpts, env }); + const appMountPrefixNoTrailingSlash = path.posix + .join('/', entryDirectory) + .replace(/\/+$/, ''); + const routesManifest = await getRoutesManifest(entryPath, realNextVersion); + const prerenderManifest = await getPrerenderManifest(entryPath); const headers: Route[] = []; const rewrites: Route[] = []; const redirects: Route[] = []; const nextBasePathRoute: Route[] = []; + const dataRoutes: Route[] = []; let nextBasePath: string | undefined; // whether they have enabled pages/404.js as the custom 404 page let hasPages404 = false; @@ -356,6 +362,35 @@ export const build = async ({ headers.push(...convertHeaders(routesManifest.headers)); } + if (routesManifest.dataRoutes) { + // Load the /_next/data routes for both dynamic SSG and SSP pages. + // These must be combined and sorted to prevent conflicts + for (const dataRoute of routesManifest.dataRoutes) { + const ssgDataRoute = prerenderManifest.lazyRoutes[dataRoute.page]; + + // we don't need to add routes for non-lazy SSG routes since + // they have outputs which would override the routes anyways + if (prerenderManifest.routes[dataRoute.page]) { + continue; + } + + dataRoutes.push({ + src: dataRoute.dataRouteRegex.replace( + /^\^/, + `^${appMountPrefixNoTrailingSlash}` + ), + dest: path.join( + '/', + entryDirectory, + // make sure to route SSG data route to the data prerender + // output, we don't do this for SSP routes since they don't + // have a separate data output + (ssgDataRoute && ssgDataRoute.dataRoute) || dataRoute.page + ), + }); + } + } + if (routesManifest.pages404) { hasPages404 = true; } @@ -521,13 +556,8 @@ export const build = async ({ const prerenders: { [key: string]: Prerender | FileFsRef } = {}; const staticPages: { [key: string]: FileFsRef } = {}; const dynamicPages: string[] = []; - const dynamicDataRoutes: Array = []; let static404Page: string | undefined; - const appMountPrefixNoTrailingSlash = path.posix - .join('/', entryDirectory) - .replace(/\/+$/, ''); - if (isLegacy) { const filesAfterBuild = await glob('**', entryPath); @@ -629,7 +659,6 @@ export const build = async ({ const pages = await glob('**/*.js', pagesDir); const staticPageFiles = await glob('**/*.html', pagesDir); - const prerenderManifest = await getPrerenderManifest(entryPath); Object.keys(staticPageFiles).forEach((page: string) => { const pathname = page.replace(/\.html$/, ''); @@ -976,18 +1005,25 @@ export const build = async ({ onPrerenderRoute(route, true) ); - // Dynamic pages for lazy routes should be handled by the lambda flow. - Object.keys(prerenderManifest.lazyRoutes).forEach(lazyRoute => { - const { dataRouteRegex, dataRoute } = prerenderManifest.lazyRoutes[ - lazyRoute - ]; - dynamicDataRoutes.push({ - // Next.js provided data route regex - src: dataRouteRegex.replace(/^\^/, `^${appMountPrefixNoTrailingSlash}`), - // Location of lambda in builder output - dest: path.posix.join(entryDirectory, dataRoute), + // We still need to use lazyRoutes if the dataRoutes field + // isn't available for backwards compatibility + if (!(routesManifest && routesManifest.dataRoutes)) { + // Dynamic pages for lazy routes should be handled by the lambda flow. + Object.keys(prerenderManifest.lazyRoutes).forEach(lazyRoute => { + const { dataRouteRegex, dataRoute } = prerenderManifest.lazyRoutes[ + lazyRoute + ]; + dataRoutes.push({ + // Next.js provided data route regex + src: dataRouteRegex.replace( + /^\^/, + `^${appMountPrefixNoTrailingSlash}` + ), + // Location of lambda in builder output + dest: path.posix.join(entryDirectory, dataRoute), + }); }); - }); + } } const nextStaticFiles = await glob( @@ -1086,6 +1122,7 @@ export const build = async ({ continue: true, }, { src: path.join('/', entryDirectory, '_next(?!/data(?:/|$))(?:/.*)?') }, + // Next.js page lambdas, `static/` folder, reserved assets, and `public/` // folder { handle: 'filesystem' }, @@ -1107,7 +1144,10 @@ export const build = async ({ ...rewrites, // Dynamic routes ...dynamicRoutes, - ...dynamicDataRoutes, + + // /_next/data routes for getServerProps/getStaticProps pages + ...dataRoutes, + // Custom Next.js 404 page (TODO: do we want to remove this?) ...(isLegacy ? [] diff --git a/packages/now-next/src/utils.ts b/packages/now-next/src/utils.ts index 589aea762a5..069cdec6f22 100644 --- a/packages/now-next/src/utils.ts +++ b/packages/now-next/src/utils.ts @@ -316,6 +316,7 @@ export type RoutesManifest = { regex: string; }[]; version: number; + dataRoutes?: Array<{ page: string; dataRouteRegex: string }>; }; export async function getRoutesManifest( diff --git a/packages/now-next/test/fixtures/05-spr-support/now.json b/packages/now-next/test/fixtures/05-spr-support/now.json index ef9226ba6a9..631aae8d984 100644 --- a/packages/now-next/test/fixtures/05-spr-support/now.json +++ b/packages/now-next/test/fixtures/05-spr-support/now.json @@ -84,6 +84,21 @@ "x-now-cache": "/HIT|STALE/" } }, + { + "path": "/_next/data/testing-build-id/blog/post-4.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { "delay": 2000 }, + { + "path": "/_next/data/testing-build-id/blog/post-4.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "/HIT|STALE/" + } + }, { "path": "/blog/post-1/comment-1", "status": 200, diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/now.json b/packages/now-next/test/fixtures/18-ssg-fallback-support/now.json index 4d9e0a055c7..f43e67b5e45 100644 --- a/packages/now-next/test/fixtures/18-ssg-fallback-support/now.json +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/now.json @@ -82,6 +82,21 @@ "x-now-cache": "/HIT|STALE/" } }, + { + "path": "/_next/data/testing-build-id/blog/post-4.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { "delay": 2000 }, + { + "path": "/_next/data/testing-build-id/blog/post-4.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "/HIT|STALE/" + } + }, { "path": "/blog/post-3", "status": 200, diff --git a/packages/now-next/test/fixtures/18-ssg-fallback-support/package.json b/packages/now-next/test/fixtures/18-ssg-fallback-support/package.json index 93e31bc44d9..17a2cb2a8fa 100644 --- a/packages/now-next/test/fixtures/18-ssg-fallback-support/package.json +++ b/packages/now-next/test/fixtures/18-ssg-fallback-support/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "next": "9.2.2-canary.16", + "next": "9.2.3-canary.13", "react": "^16.8.6", "react-dom": "^16.8.6" } diff --git a/packages/now-next/test/fixtures/21-server-props/next.config.js b/packages/now-next/test/fixtures/21-server-props/next.config.js new file mode 100644 index 00000000000..ac717504266 --- /dev/null +++ b/packages/now-next/test/fixtures/21-server-props/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + generateBuildId() { + return 'testing-build-id'; + }, +}; diff --git a/packages/now-next/test/fixtures/21-server-props/now.json b/packages/now-next/test/fixtures/21-server-props/now.json new file mode 100644 index 00000000000..49a82db6c1c --- /dev/null +++ b/packages/now-next/test/fixtures/21-server-props/now.json @@ -0,0 +1,164 @@ +{ + "version": 2, + "builds": [{ "src": "package.json", "use": "@now/next" }], + "probes": [ + { + "path": "/lambda", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/forever", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { "delay": 2000 }, + { + "path": "/forever", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/another", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { "delay": 2000 }, + { + "path": "/another", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/blog/post-1", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { "delay": 2000 }, + { + "path": "/blog/post-1", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/blog/post-2", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { "delay": 2000 }, + { + "path": "/blog/post-2", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/blog/post-3", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { "delay": 2000 }, + { + "path": "/blog/post-3", + "status": 200, + "responseHeaders": { + "x-now-cache": "/MISS/" + } + }, + { + "path": "/blog/post-1/comment-1", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/blog/post-2/comment-2", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/blog/post-3/comment-3", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/_next/data/testing-build-id/lambda.json", + "status": 404 + }, + { + "path": "/_next/data/testing-build-id/another.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "/MISS/" + } + }, + { + "path": "/_next/data/testing-build-id/another2.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { "delay": 2000 }, + { + "path": "/_next/data/testing-build-id/another2.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/_next/data/testing-build-id/blog/post-1.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "/MISS/" + } + }, + { + "path": "/_next/data/testing-build-id/blog/post-1.json", + "status": 200, + "mustContain": "post-1" + }, + { + "path": "/_next/data/testing-build-id/blog/post-1337/comment-1337.json", + "status": 200, + "responseHeaders": { + "x-now-cache": "MISS" + } + }, + { + "path": "/_next/data/testing-build-id/blog/post-1337/comment-1337.json", + "status": 200, + "mustContain": "comment-1337" + }, + { + "path": "/_next/data/testing-build-id/blog/post-1337/comment-1337.json", + "status": 200, + "mustContain": "post-1337" + } + ] +} diff --git a/packages/now-next/test/fixtures/21-server-props/package.json b/packages/now-next/test/fixtures/21-server-props/package.json new file mode 100644 index 00000000000..17a2cb2a8fa --- /dev/null +++ b/packages/now-next/test/fixtures/21-server-props/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "next": "9.2.3-canary.13", + "react": "^16.8.6", + "react-dom": "^16.8.6" + } +} diff --git a/packages/now-next/test/fixtures/21-server-props/pages/another.js b/packages/now-next/test/fixtures/21-server-props/pages/another.js new file mode 100644 index 00000000000..bc8ad36db24 --- /dev/null +++ b/packages/now-next/test/fixtures/21-server-props/pages/another.js @@ -0,0 +1,20 @@ +import React from 'react'; + +// eslint-disable-next-line camelcase +export async function unstable_getServerProps() { + return { + props: { + world: 'world', + time: new Date().getTime(), + }, + }; +} + +export default ({ world, time }) => { + return ( + <> +

hello: {world}

+ time: {time} + + ); +}; diff --git a/packages/now-next/test/fixtures/21-server-props/pages/another2.js b/packages/now-next/test/fixtures/21-server-props/pages/another2.js new file mode 100644 index 00000000000..bc8ad36db24 --- /dev/null +++ b/packages/now-next/test/fixtures/21-server-props/pages/another2.js @@ -0,0 +1,20 @@ +import React from 'react'; + +// eslint-disable-next-line camelcase +export async function unstable_getServerProps() { + return { + props: { + world: 'world', + time: new Date().getTime(), + }, + }; +} + +export default ({ world, time }) => { + return ( + <> +

hello: {world}

+ time: {time} + + ); +}; diff --git a/packages/now-next/test/fixtures/21-server-props/pages/blog/[post]/[comment].js b/packages/now-next/test/fixtures/21-server-props/pages/blog/[post]/[comment].js new file mode 100644 index 00000000000..bb823d6883c --- /dev/null +++ b/packages/now-next/test/fixtures/21-server-props/pages/blog/[post]/[comment].js @@ -0,0 +1,22 @@ +import React from 'react'; + +// eslint-disable-next-line camelcase +export async function unstable_getServerProps ({ params }) { + return { + props: { + post: params.post, + comment: params.comment, + time: new Date().getTime(), + }, + }; +} + +export default ({ post, comment, time }) => { + return ( + <> +

Post: {post}

+

Comment: {comment}

+ time: {time} + + ); +}; diff --git a/packages/now-next/test/fixtures/21-server-props/pages/blog/[post]/index.js b/packages/now-next/test/fixtures/21-server-props/pages/blog/[post]/index.js new file mode 100644 index 00000000000..cf7b35c360b --- /dev/null +++ b/packages/now-next/test/fixtures/21-server-props/pages/blog/[post]/index.js @@ -0,0 +1,26 @@ +import React from 'react' + +// eslint-disable-next-line camelcase +export async function unstable_getServerProps ({ params }) { + if (params.post === 'post-10') { + await new Promise(resolve => { + setTimeout(() => resolve(), 1000) + }) + } + + return { + props: { + post: params.post, + time: (await import('perf_hooks')).performance.now() + }, + } +} + +export default ({ post, time }) => { + return ( + <> +

Post: {post}

+ time: {time} + + ) +} diff --git a/packages/now-next/test/fixtures/21-server-props/pages/forever.js b/packages/now-next/test/fixtures/21-server-props/pages/forever.js new file mode 100644 index 00000000000..bc8ad36db24 --- /dev/null +++ b/packages/now-next/test/fixtures/21-server-props/pages/forever.js @@ -0,0 +1,20 @@ +import React from 'react'; + +// eslint-disable-next-line camelcase +export async function unstable_getServerProps() { + return { + props: { + world: 'world', + time: new Date().getTime(), + }, + }; +} + +export default ({ world, time }) => { + return ( + <> +

hello: {world}

+ time: {time} + + ); +}; diff --git a/packages/now-next/test/fixtures/21-server-props/pages/index.js b/packages/now-next/test/fixtures/21-server-props/pages/index.js new file mode 100644 index 00000000000..cdeee3e6335 --- /dev/null +++ b/packages/now-next/test/fixtures/21-server-props/pages/index.js @@ -0,0 +1 @@ +export default () => 'Hi'; diff --git a/packages/now-next/test/fixtures/21-server-props/pages/lambda.js b/packages/now-next/test/fixtures/21-server-props/pages/lambda.js new file mode 100644 index 00000000000..c2a3ce85b6a --- /dev/null +++ b/packages/now-next/test/fixtures/21-server-props/pages/lambda.js @@ -0,0 +1,5 @@ +const Page = ({ data }) =>

{data} world

; + +Page.getInitialProps = () => ({ data: 'hello' }); + +export default Page;