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

[now-next] Add /_next/data routes for getServerProps pages #3771

Merged
merged 10 commits into from Feb 26, 2020
4 changes: 1 addition & 3 deletions packages/now-cli/test/unit.js
Expand Up @@ -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;
};
Expand Down
78 changes: 59 additions & 19 deletions packages/now-next/src/index.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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<Source> = [];
let static404Page: string | undefined;

const appMountPrefixNoTrailingSlash = path.posix
.join('/', entryDirectory)
.replace(/\/+$/, '');

if (isLegacy) {
const filesAfterBuild = await glob('**', entryPath);

Expand Down Expand Up @@ -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$/, '');
Expand Down Expand Up @@ -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 => {
ijjk marked this conversation as resolved.
Show resolved Hide resolved
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(
Expand Down Expand Up @@ -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' },
Expand All @@ -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
? []
Expand Down
1 change: 1 addition & 0 deletions packages/now-next/src/utils.ts
Expand Up @@ -316,6 +316,7 @@ export type RoutesManifest = {
regex: string;
}[];
version: number;
dataRoutes?: Array<{ page: string; dataRouteRegex: string }>;
};

export async function getRoutesManifest(
Expand Down
15 changes: 15 additions & 0 deletions packages/now-next/test/fixtures/05-spr-support/now.json
Expand Up @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions packages/now-next/test/fixtures/18-ssg-fallback-support/now.json
Expand Up @@ -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,
Expand Down
@@ -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"
}
Expand Down
@@ -0,0 +1,5 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
};
164 changes: 164 additions & 0 deletions 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"
}
]
}
7 changes: 7 additions & 0 deletions 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"
}
}