diff --git a/packages/now-next/src/index.ts b/packages/now-next/src/index.ts index 52ce1725f..897c45378 100644 --- a/packages/now-next/src/index.ts +++ b/packages/now-next/src/index.ts @@ -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 diff --git a/packages/now-next/src/utils.ts b/packages/now-next/src/utils.ts index e279ae6bf..35406c250 100644 --- a/packages/now-next/src/utils.ts +++ b/packages/now-next/src/utils.ts @@ -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; diff --git a/packages/now-next/test/fixtures/07-custom-routes/next.config.js b/packages/now-next/test/fixtures/07-custom-routes/next.config.js new file mode 100644 index 000000000..8b95cce11 --- /dev/null +++ b/packages/now-next/test/fixtures/07-custom-routes/next.config.js @@ -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' + } + ] + } + } +} \ No newline at end of file diff --git a/packages/now-next/test/fixtures/07-custom-routes/now.json b/packages/now-next/test/fixtures/07-custom-routes/now.json new file mode 100644 index 000000000..2b028ad0b --- /dev/null +++ b/packages/now-next/test/fixtures/07-custom-routes/now.json @@ -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" + } + ] +} diff --git a/packages/now-next/test/fixtures/07-custom-routes/package.json b/packages/now-next/test/fixtures/07-custom-routes/package.json new file mode 100644 index 000000000..02fe0e323 --- /dev/null +++ b/packages/now-next/test/fixtures/07-custom-routes/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "next": "^9.1.4-canary.1", + "react": "^16.8.6", + "react-dom": "^16.8.6" + } +} diff --git a/packages/now-next/test/fixtures/07-custom-routes/pages/hello.js b/packages/now-next/test/fixtures/07-custom-routes/pages/hello.js new file mode 100644 index 000000000..03132b06c --- /dev/null +++ b/packages/now-next/test/fixtures/07-custom-routes/pages/hello.js @@ -0,0 +1 @@ +export default () => 'hello world!' diff --git a/packages/now-next/test/fixtures/07-custom-routes/pages/params.js b/packages/now-next/test/fixtures/07-custom-routes/pages/params.js new file mode 100644 index 000000000..0b4ed8c73 --- /dev/null +++ b/packages/now-next/test/fixtures/07-custom-routes/pages/params.js @@ -0,0 +1,10 @@ +import { useRouter } from 'next/router' + +const Page = () => { + const { query } = useRouter() + return
{JSON.stringify(query)}
+} + +Page.getInitialProps = () => ({ a: 'b' }) + +export default Page diff --git a/test/lib/deployment/test-deployment.js b/test/lib/deployment/test-deployment.js index 47fd12cb8..2795a3dc3 100644 --- a/test/lib/deployment/test-deployment.js +++ b/test/lib/deployment/test-deployment.js @@ -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);