[now-next] Add loading of custom routes (#3279)

This adds loading of `redirects` and `rewrites` from the `routes-manifest` in supported Next.js versions
This commit is contained in:
JJ Kasper
2019-11-09 23:17:58 -08:00
committed by Leo Lamprecht
parent a211c648ed
commit 5fc9b7d36d
8 changed files with 206 additions and 16 deletions

View File

@@ -1,4 +1,5 @@
import { ChildProcess, fork } from 'child_process';
import url from 'url'
import {
pathExists,
readFile,
@@ -54,8 +55,14 @@ import {
syncEnvVars,
validateEntrypoint,
getSourceFilePathFromPage,
getRoutesManifest,
} from './utils';
import {
convertRedirects,
convertRewrites
} from '@now/routing-utils/dist/superstatic'
interface BuildParamsMeta {
isDev: boolean | undefined;
env?: EnvConfig;
@@ -825,10 +832,14 @@ export const build = async ({
let dynamicPrefix = path.join('/', entryDirectory);
dynamicPrefix = dynamicPrefix === '/' ? '' : dynamicPrefix;
const routesManifest = await getRoutesManifest(entryPath, realNextVersion)
const dynamicRoutes = await getDynamicRoutes(
entryPath,
entryDirectory,
dynamicPages
dynamicPages,
false,
routesManifest
).then(arr =>
arr.map(route => {
// make sure .html is added to dest for now until
@@ -841,6 +852,26 @@ export const build = async ({
})
);
const rewrites: Route[] = []
const redirects: Route[] = []
if (routesManifest) {
switch(routesManifest.version) {
case 1: {
redirects.push(...convertRedirects(routesManifest.redirects))
rewrites.push(...convertRewrites(routesManifest.rewrites))
break
}
default: {
// update MIN_ROUTES_MANIFEST_VERSION in ./utils.ts
throw new Error(
'This version of `@now/next` does not support the version of Next.js you are trying to deploy.\n' +
'Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
);
}
}
}
return {
output: {
...publicDirectoryFiles,
@@ -852,6 +883,8 @@ export const build = async ({
...staticDirectoryFiles,
},
routes: [
...redirects,
...rewrites,
// Static exported pages (.html rewrites)
...exportedPageRoutes,
// Before we handle static files we need to set proper caching headers

View File

@@ -1,6 +1,7 @@
import zlib from 'zlib';
import path from 'path';
import fs from 'fs-extra';
import semver from 'semver';
import { ZipFile } from 'yazl';
import crc32 from 'buffer-crc32';
import { Sema } from 'async-sema';
@@ -291,16 +292,39 @@ async function getRoutes(
return routes;
}
export async function getDynamicRoutes(
entryPath: string,
entryDirectory: string,
dynamicPages: string[],
isDev?: boolean
): Promise<{ src: string; dest: string }[]> {
if (!dynamicPages.length) {
return [];
export type Rewrite = {
source: string,
destination: string,
}
export type Redirect = Rewrite & {
statusCode?: number
}
type RoutesManifestRegex = {
regex: string,
regexKeys: string[]
}
export type RoutesManifest = {
redirects: (Redirect & RoutesManifestRegex)[],
rewrites: (Rewrite & RoutesManifestRegex)[],
dynamicRoutes: {
page: string,
regex: string,
}[],
version: number
}
export async function getRoutesManifest(
entryPath: string,
nextVersion?: string,
): Promise< RoutesManifest | undefined> {
const shouldHaveManifest = (
nextVersion && semver.gte(nextVersion, '9.1.4-canary.0')
)
if (!shouldHaveManifest) return
const pathRoutesManifest = path.join(
entryPath,
'.next',
@@ -311,11 +335,30 @@ export async function getDynamicRoutes(
.then(() => true)
.catch(() => false);
if (hasRoutesManifest) {
const routesManifest = await fs.readJSON(pathRoutesManifest);
if (shouldHaveManifest && !hasRoutesManifest) {
throw new Error(
`A routes-manifest.json couldn't be found. This could be due to a failure during the build`
)
}
const routesManifest: RoutesManifest = require(pathRoutesManifest)
return routesManifest
}
export async function getDynamicRoutes(
entryPath: string,
entryDirectory: string,
dynamicPages: string[],
isDev?: boolean,
routesManifest?: RoutesManifest
): Promise<{ src: string; dest: string }[]> {
if (!dynamicPages.length) {
return [];
}
if (routesManifest) {
switch (routesManifest.version) {
case 0:
case 1: {
return routesManifest.dynamicRoutes.map(
({ page, regex }: { page: string; regex: string }) => {
@@ -327,6 +370,7 @@ export async function getDynamicRoutes(
);
}
default: {
// update MIN_ROUTES_MANIFEST_VERSION
throw new Error(
'This version of `@now/next` does not support the version of Next.js you are trying to deploy.\n' +
'Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
@@ -338,9 +382,6 @@ export async function getDynamicRoutes(
// FALLBACK:
// When `routes-manifest.json` does not exist (old Next.js versions), we'll try to
// require the methods we need from Next.js' internals.
//
// TODO: implement this branch behind a Next.js version check so we don't "fallback"
// to this behavior blindly.
let getRouteRegex:
| ((pageName: string) => { re: RegExp })
| undefined = undefined;

View File

@@ -0,0 +1,42 @@
module.exports = {
experimental: {
async redirects() {
return [
{
source: '/redir1',
destination: '/redir2',
statusCode: 301
},
{
source: '/redir2',
destination: '/hello',
statusCode: 307
},
{
source: '/redir/:path',
destination: '/:path'
}
]
},
async rewrites() {
return [
{
source: '/rewrite1',
destination: '/rewrite2'
},
{
source: '/rewrite2',
destination: '/hello'
},
{
source: '/rewrite/:first/:second',
destination: '/rewrite-2/hello/:second'
},
{
source: '/rewrite-2/:first/:second',
destination: '/params'
}
]
}
}
}

View File

@@ -0,0 +1,52 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{
"path": "/redir1",
"fetchOptions": {
"redirect": "manual"
},
"status": 301,
"responseHeaders": {
"location": "/redir2/"
}
},
{
"path": "/redir2",
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "/hello/"
}
},
{
"path": "/redir/hello",
"fetchOptions": {
"redirect": "manual"
},
"responseHeadersCo": {
"location": "/hello/"
},
"status": 307
},
{
"path": "/rewrite1",
"mustContain": "hello world!"
},
{
"path": "/rewrite2",
"mustContain": "hello world!"
},
{
"path": "/rewrite/THIS_SHOULD_BE_GONE/another",
"mustContain": "hello"
},
{
"path": "/rewrite/THIS_SHOULD_BE_GONE/another",
"mustContain": "another"
}
]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "^9.1.4-canary.1",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1 @@
export default () => 'hello world!'

View File

@@ -0,0 +1,10 @@
import { useRouter } from 'next/router'
const Page = () => {
const { query } = useRouter()
return <p>{JSON.stringify(query)}</p>
}
Page.getInitialProps = () => ({ a: 'b' })
export default Page

View File

@@ -89,7 +89,11 @@ async function testDeployment (
continue;
}
const probeUrl = `https://${deploymentUrl}${probe.path}`;
const fetchOpts = { method: probe.method, headers: { ...probe.headers } };
const fetchOpts = {
...probe.fetchOptions,
method: probe.method,
headers: { ...probe.headers },
};
if (probe.body) {
fetchOpts.headers['content-type'] = 'application/json';
fetchOpts.body = JSON.stringify(probe.body);