mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 12:57:46 +00:00
[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:
@@ -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
|
||||
|
||||
@@ -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,15 +292,38 @@ async function getRoutes(
|
||||
return routes;
|
||||
}
|
||||
|
||||
export async function getDynamicRoutes(
|
||||
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,
|
||||
entryDirectory: string,
|
||||
dynamicPages: string[],
|
||||
isDev?: boolean
|
||||
): Promise<{ src: string; dest: string }[]> {
|
||||
if (!dynamicPages.length) {
|
||||
return [];
|
||||
}
|
||||
nextVersion?: string,
|
||||
): Promise< RoutesManifest | undefined> {
|
||||
const shouldHaveManifest = (
|
||||
nextVersion && semver.gte(nextVersion, '9.1.4-canary.0')
|
||||
)
|
||||
if (!shouldHaveManifest) return
|
||||
|
||||
const pathRoutesManifest = path.join(
|
||||
entryPath,
|
||||
@@ -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;
|
||||
|
||||
42
packages/now-next/test/fixtures/07-custom-routes/next.config.js
vendored
Normal file
42
packages/now-next/test/fixtures/07-custom-routes/next.config.js
vendored
Normal 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'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
52
packages/now-next/test/fixtures/07-custom-routes/now.json
vendored
Normal file
52
packages/now-next/test/fixtures/07-custom-routes/now.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
7
packages/now-next/test/fixtures/07-custom-routes/package.json
vendored
Normal file
7
packages/now-next/test/fixtures/07-custom-routes/package.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "^9.1.4-canary.1",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
}
|
||||
}
|
||||
1
packages/now-next/test/fixtures/07-custom-routes/pages/hello.js
vendored
Normal file
1
packages/now-next/test/fixtures/07-custom-routes/pages/hello.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default () => 'hello world!'
|
||||
10
packages/now-next/test/fixtures/07-custom-routes/pages/params.js
vendored
Normal file
10
packages/now-next/test/fixtures/07-custom-routes/pages/params.js
vendored
Normal 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
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user