Compare commits

..

2 Commits

Author SHA1 Message Date
Steven
4e005274f9 Publish Canary
- @vercel/build-utils@2.3.2-canary.4
 - vercel@19.0.2-canary.12
2020-06-04 18:43:42 -04:00
Steven
482373f711 [cli][build-utils] Add support for custom 404.html for zero config deployments (#4563)
Next.js already has support for [customizing the 404 page](https://nextjs.org/docs/advanced-features/custom-error-page#customizing-the-404-page), but many other frameworks do not or they expect a 404.html in the output directory.

This PR adds support for rendering the a `404.html` page for all zero config deployments.

- Implements ch337
- Related to #3491
2020-06-04 22:36:41 +00:00
27 changed files with 311 additions and 45 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.3.2-canary.3",
"version": "2.3.2-canary.4",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",

View File

@@ -82,6 +82,7 @@ export async function detectBuilders(
defaultRoutes: Route[] | null;
redirectRoutes: Route[] | null;
rewriteRoutes: Route[] | null;
errorRoutes: Route[] | null;
}> {
const errors: ErrorResponse[] = [];
const warnings: ErrorResponse[] = [];
@@ -99,6 +100,7 @@ export async function detectBuilders(
defaultRoutes: null,
redirectRoutes: null,
rewriteRoutes: null,
errorRoutes: null,
};
}
@@ -154,6 +156,7 @@ export async function detectBuilders(
defaultRoutes: null,
redirectRoutes: null,
rewriteRoutes: null,
errorRoutes: null,
};
}
@@ -231,6 +234,7 @@ export async function detectBuilders(
redirectRoutes: null,
defaultRoutes: null,
rewriteRoutes: null,
errorRoutes: null,
};
}
@@ -272,6 +276,7 @@ export async function detectBuilders(
redirectRoutes: null,
defaultRoutes: null,
rewriteRoutes: null,
errorRoutes: null,
};
}
@@ -309,6 +314,7 @@ export async function detectBuilders(
redirectRoutes: routesResult.redirectRoutes,
defaultRoutes: routesResult.defaultRoutes,
rewriteRoutes: routesResult.rewriteRoutes,
errorRoutes: routesResult.errorRoutes,
};
}
@@ -898,10 +904,17 @@ function getRouteResult(
defaultRoutes: Route[];
redirectRoutes: Route[];
rewriteRoutes: Route[];
errorRoutes: Route[];
} {
const defaultRoutes: Route[] = [];
const redirectRoutes: Route[] = [];
const rewriteRoutes: Route[] = [];
const errorRoutes: Route[] = [];
const isNextjs =
frontendBuilder &&
((frontendBuilder.use && frontendBuilder.use.startsWith('@vercel/next')) ||
(frontendBuilder.config &&
frontendBuilder.config.framework === 'nextjs'));
if (apiRoutes && apiRoutes.length > 0) {
if (options.featHandleMiss) {
@@ -968,10 +981,21 @@ function getRouteResult(
});
}
if (options.featHandleMiss && !isNextjs) {
// Exclude Next.js to avoid overriding custom error page
// https://nextjs.org/docs/advanced-features/custom-error-page
errorRoutes.push({
status: 404,
src: '^/(?!.*api).*$',
dest: options.cleanUrls ? '/404' : '/404.html',
});
}
return {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
};
}

View File

@@ -868,12 +868,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, pkg, { featHandleMiss });
expect(builders![0].use).toBe('@vercel/next');
expect(errors).toBe(null);
expect(defaultRoutes).toStrictEqual([]);
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([]);
expect(errorRoutes).toStrictEqual([]);
});
it('package.json + no build + next', async () => {
@@ -888,12 +890,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, pkg, { featHandleMiss });
expect(builders![0].use).toBe('@vercel/next');
expect(errors).toBe(null);
expect(defaultRoutes).toStrictEqual([]);
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([]);
expect(errorRoutes).toStrictEqual([]);
});
it('package.json + no build', async () => {
@@ -914,12 +918,15 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, { featHandleMiss });
expect(builders).toBe(null);
expect(errors).toBe(null);
expect(defaultRoutes).toStrictEqual([]);
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([]);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('no package.json + public', async () => {
@@ -930,6 +937,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, { featHandleMiss });
expect(builders![1].use).toBe('@vercel/static');
expect(errors).toBe(null);
@@ -940,6 +948,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes!.length).toBe(1);
expect((rewriteRoutes![0] as Source).status).toBe(404);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('no package.json + no build + raw static + api', async () => {
@@ -950,6 +960,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, { featHandleMiss });
expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/users.js');
@@ -964,6 +975,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes!.length).toBe(1);
expect((rewriteRoutes![0] as Source).status).toBe(404);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('package.json + no build + root + api', async () => {
@@ -991,6 +1004,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, undefined, { featHandleMiss });
expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/[endpoint]/[id].js');
@@ -1003,6 +1017,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(rewriteRoutes!.length).toBe(2);
expect((rewriteRoutes![0] as Source).src).toBe('^/api/([^/]+)/([^/]+)$');
expect((rewriteRoutes![1] as Source).status).toBe(404);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('api + next + public', async () => {
@@ -1017,6 +1033,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, pkg, { featHandleMiss });
expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/endpoint.js');
@@ -1030,6 +1047,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes!.length).toBe(1);
expect((rewriteRoutes![0] as Source).status).toBe(404);
expect(errorRoutes).toStrictEqual([]);
});
it('api + next + raw static', async () => {
@@ -1044,6 +1062,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, pkg, { featHandleMiss });
expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/endpoint.js');
@@ -1057,6 +1076,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes!.length).toBe(1);
expect((rewriteRoutes![0] as Source).status).toBe(404);
expect(errorRoutes).toStrictEqual([]);
});
it('api + raw static', async () => {
@@ -1067,6 +1087,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, { featHandleMiss });
expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/endpoint.js');
@@ -1080,6 +1101,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes!.length).toBe(1);
expect((rewriteRoutes![0] as Source).status).toBe(404);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('api + raw static + package.json no build script', async () => {
@@ -1094,6 +1117,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, pkg, { featHandleMiss });
expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/version.js');
@@ -1107,6 +1131,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes!.length).toBe(1);
expect((rewriteRoutes![0] as Source).status).toBe(404);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('api + public', async () => {
@@ -1117,7 +1143,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
'README.md',
];
const { builders } = await detectBuilders(files, undefined, {
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
featHandleMiss,
});
expect(builders![0].use).toBe('@vercel/node');
@@ -1125,6 +1151,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('public/**/*');
expect(builders!.length).toBe(2);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('api go with test files', async () => {
@@ -1143,21 +1171,26 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
'api/src/controllers/user.module_test.go',
];
const { builders } = await detectBuilders(files, undefined, {
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
featHandleMiss,
});
expect(builders!.length).toBe(7);
expect(builders!.some(b => b.src.endsWith('_test.go'))).toBe(false);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('just public', async () => {
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
const { builders } = await detectBuilders(files, undefined, {
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
featHandleMiss,
});
expect(builders![0].src).toBe('public/**/*');
expect(builders![0].use).toBe('@vercel/static');
expect(builders!.length).toBe(1);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('next + public', async () => {
@@ -1167,10 +1200,13 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
};
const files = ['package.json', 'public/index.html', 'README.md'];
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
featHandleMiss,
});
expect(builders![0].use).toBe('@vercel/next');
expect(builders![0].src).toBe('package.json');
expect(builders!.length).toBe(1);
expect(errorRoutes!.length).toBe(0);
});
it('nuxt', async () => {
@@ -1180,10 +1216,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
};
const files = ['package.json', 'pages/index.js'];
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
featHandleMiss,
});
expect(builders![0].use).toBe('@vercel/static-build');
expect(builders![0].src).toBe('package.json');
expect(builders!.length).toBe(1);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('nuxt + tag canary', async () => {
@@ -1193,23 +1233,29 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
};
const files = ['package.json', 'pages/index.js'];
const { builders } = await detectBuilders(files, pkg, {
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
tag: 'canary',
featHandleMiss,
});
expect(builders![0].use).toBe('@vercel/static-build@canary');
expect(builders![0].src).toBe('package.json');
expect(builders!.length).toBe(1);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('package.json with no build + api', async () => {
const pkg = { dependencies: { next: '9.0.0' } };
const files = ['package.json', 'api/[endpoint].js'];
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
featHandleMiss,
});
expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/[endpoint].js');
expect(builders!.length).toBe(1);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('package.json with no build + public directory', async () => {
@@ -1328,7 +1374,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
it('many static files + one api file', async () => {
const files = Array.from({ length: 5000 }).map((_, i) => `file${i}.html`);
files.push('api/index.ts');
const { builders } = await detectBuilders(files, undefined, {
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
featHandleMiss,
});
@@ -1337,6 +1383,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(builders![0].src).toBe('api/index.ts');
expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json}');
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('functions with nextjs', async () => {
@@ -1685,6 +1733,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
@@ -1697,6 +1746,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(defaultRoutes).toStrictEqual([]);
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([]);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('Custom static output directory with api', async () => {
@@ -1711,6 +1762,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
@@ -1726,6 +1778,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(redirectRoutes).toStrictEqual([]);
expect(rewriteRoutes!.length).toBe(1);
expect((rewriteRoutes![0] as Source).status).toBe(404);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('Framework with non-package.json entrypoint', async () => {
@@ -1734,7 +1788,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
framework: 'hugo',
};
const { builders } = await detectBuilders(files, null, {
const { builders, errorRoutes } = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});
@@ -1749,6 +1803,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
},
},
]);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('No framework, only package.json', async () => {
@@ -1759,7 +1815,9 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
},
};
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
featHandleMiss,
});
expect(builders).toEqual([
{
@@ -1770,13 +1828,15 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
},
},
]);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('Framework with an API', async () => {
const files = ['config.rb', 'api/date.rb'];
const projectSettings = { framework: 'middleman' };
const { builders } = await detectBuilders(files, null, {
const { builders, errorRoutes } = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});
@@ -1798,6 +1858,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
},
},
]);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('Error for non-api functions', async () => {
@@ -1847,13 +1909,19 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
it('All static if `buildCommand` is an empty string with an `outputDirectory`', async () => {
const files = ['out/index.html'];
const projectSettings = { buildCommand: '', outputDirectory: 'out' };
const { builders, errors } = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});
const { builders, errors, errorRoutes } = await detectBuilders(
files,
null,
{
projectSettings,
featHandleMiss,
}
);
expect(errors).toBe(null);
expect(builders![0]!.use).toBe('@vercel/static');
expect(builders![0]!.src).toBe('out/**/*');
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('do not require build script when `buildCommand` is an empty string', async () => {
@@ -2029,9 +2097,13 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { defaultRoutes, rewriteRoutes } = await detectBuilders(files, null, {
featHandleMiss,
});
const { defaultRoutes, rewriteRoutes, errorRoutes } = await detectBuilders(
files,
null,
{
featHandleMiss,
}
);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
@@ -2047,6 +2119,44 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
continue: true,
},
]);
expect(errorRoutes).toStrictEqual([
{
status: 404,
src: '^/(?!.*api).*$',
dest: '/404.html',
},
]);
const pattern = new RegExp(errorRoutes![0].src!);
[
'/',
'/index.html',
'/page.html',
'/page',
'/another/index.html',
'/another/page.html',
'/another/page',
'/another/sub/index.html',
'/another/sub/page.html',
'/another/sub/page',
].forEach(file => {
expect(file).toMatch(pattern);
});
[
'/api',
'/api/',
'/api/index.html',
'/api/page.html',
'/api/page',
'/api/sub',
'/api/sub/index.html',
'/api/sub/page.html',
'/api/sub/page',
].forEach(file => {
expect(file).not.toMatch(pattern);
});
}
{
@@ -2330,6 +2440,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]);
@@ -2340,6 +2451,13 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
continue: true,
},
]);
expect(errorRoutes).toStrictEqual([
{
status: 404,
src: '^/(?!.*api).*$',
dest: '/404',
},
]);
// expected redirect should match inputs
const getLocation = createReplaceLocation(redirectRoutes);

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "19.0.2-canary.11",
"version": "19.0.2-canary.12",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",
@@ -62,7 +62,7 @@
"node": ">= 10"
},
"dependencies": {
"@vercel/build-utils": "2.3.2-canary.3",
"@vercel/build-utils": "2.3.2-canary.4",
"@vercel/go": "1.1.2-canary.1",
"@vercel/next": "2.6.3-canary.5",
"@vercel/node": "1.6.2-canary.5",

View File

@@ -581,6 +581,7 @@ export default class DevServer {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, pkg, {
tag: getDistTag(cliPkg.version) === 'canary' ? 'canary' : 'latest',
functions: config.functions,
@@ -606,20 +607,25 @@ export default class DevServer {
config.builds = config.builds || [];
config.builds.push(...builders);
const routes: Route[] = [];
const { routes: nowConfigRoutes } = config;
routes.push(...(redirectRoutes || []));
routes.push(
...appendRoutesToPhase({
routes: nowConfigRoutes,
newRoutes: rewriteRoutes,
phase: 'filesystem',
})
);
routes.push(...(defaultRoutes || []));
config.routes = routes;
}
let routes: Route[] = [];
const { routes: nowConfigRoutes } = config;
routes.push(...(redirectRoutes || []));
routes.push(
...appendRoutesToPhase({
routes: nowConfigRoutes,
newRoutes: rewriteRoutes,
phase: 'filesystem',
})
);
routes = appendRoutesToPhase({
routes,
newRoutes: errorRoutes,
phase: 'error',
});
routes.push(...(defaultRoutes || []));
config.routes = routes;
}
if (Array.isArray(config.builds)) {
@@ -1323,8 +1329,7 @@ export default class DevServer {
const handleMap = getRoutesTypes(routes);
const missRoutes = handleMap.get('miss') || [];
const hitRoutes = handleMap.get('hit') || [];
handleMap.delete('miss');
handleMap.delete('hit');
const errorRoutes = handleMap.get('error') || [];
const phases: (HandleValue | null)[] = [null, 'filesystem'];
let routeResult: RouteResult | null = null;
@@ -1431,6 +1436,32 @@ export default class DevServer {
routeResult.status = prevStatus;
}
if (!match && errorRoutes.length > 0) {
// error phase
const routeResultForError = await devRouter(
getReqUrl(routeResult),
req.method,
errorRoutes,
this,
routeResult.headers,
[],
'error'
);
const matchForError = await findBuildMatch(
this.buildMatches,
this.files,
routeResultForError.dest,
this
);
if (matchForError) {
// error phase only applies if the file was found
routeResult = routeResultForError;
match = matchForError;
}
}
statusCode = routeResult.status;
if (match) {

View File

@@ -0,0 +1 @@
.vercel

View File

@@ -0,0 +1,11 @@
import React from 'react';
function Custom404() {
return (
<main>
<h1>Custom Gatsby 404</h1>
</main>
);
}
export default Custom404;

View File

@@ -0,0 +1,3 @@
export default function Custom404() {
return <h1>Custom Next 404</h1>;
}

View File

@@ -0,0 +1 @@
!public

View File

@@ -0,0 +1 @@
Custom 404 Page

View File

@@ -0,0 +1 @@
.vercel

View File

@@ -0,0 +1,3 @@
module.exports = (_req, res) => {
res.end('Hello');
};

View File

@@ -0,0 +1 @@
Exact Custom 404

View File

@@ -0,0 +1 @@
Home Page

View File

@@ -0,0 +1 @@
Custom User 404

View File

@@ -0,0 +1,8 @@
{
"version": 2,
"routes": [
{ "src": "/exact", "status": 404, "dest": "/exact-404.html" },
{ "handle": "filesystem" },
{ "src": "/(.*)", "status": 404, "dest": "/user-404.html" }
]
}

View File

@@ -0,0 +1 @@
Custom 404 Page

View File

@@ -0,0 +1 @@
The about page

View File

@@ -0,0 +1 @@
Contact Me Subdirectory

View File

@@ -0,0 +1 @@
This is the home page

View File

@@ -0,0 +1,3 @@
{
"cleanUrls": true
}

View File

@@ -0,0 +1 @@
Custom 404 Page

View File

@@ -0,0 +1 @@
The about page

View File

@@ -0,0 +1 @@
Contact Subdirectory

View File

@@ -0,0 +1 @@
This is the home page

View File

@@ -0,0 +1,4 @@
{
"version": 2,
"trailingSlash": true
}

View File

@@ -391,6 +391,17 @@ test(
})
);
test(
'[vercel dev] validate routes that use custom 404 page',
testFixtureStdio('routes-custom-404', async testPath => {
await testPath(200, '/', 'Home Page');
await testPath(404, '/nothing', 'Custom User 404');
await testPath(404, '/exact', 'Exact Custom 404');
await testPath(200, '/api/hello', 'Hello');
await testPath(404, '/api/nothing', 'Custom User 404');
})
);
test(
'[vercel dev] handles miss after route',
testFixtureStdio('handle-miss-after-route', async testPath => {
@@ -471,14 +482,19 @@ test(
test(
'[vercel dev] should serve the public directory and api functions',
testFixtureStdio('public-and-api', async testPath => {
await testPath(200, '/', 'This is the home page');
await testPath(200, '/about.html', 'This is the about page');
await testPath(200, '/api/date', /current date/);
await testPath(200, '/api/rand', /random number/);
await testPath(200, '/api/rand.js', /random number/);
await testPath(404, '/api/api');
})
testFixtureStdio(
'public-and-api',
async testPath => {
await testPath(200, '/', 'This is the home page');
await testPath(200, '/about.html', 'This is the about page');
await testPath(200, '/api/date', /current date/);
await testPath(200, '/api/rand', /random number/);
await testPath(200, '/api/rand.js', /random number/);
await testPath(404, '/api/api', /NOT_FOUND/m);
await testPath(404, '/nothing', /Custom 404 Page/);
},
{ skipDeploy: true /** TODO: remove after prod goes live */ }
)
);
test(
@@ -676,6 +692,21 @@ test(
})
);
test(
'[vercel dev] should serve custom 404 when `cleanUrls: true`',
testFixtureStdio(
'test-clean-urls-custom-404',
async testPath => {
await testPath(200, '/', 'This is the home page');
await testPath(200, '/about', 'The about page');
await testPath(200, '/contact/me', 'Contact Me Subdirectory');
await testPath(404, '/nothing', 'Custom 404 Page');
await testPath(404, '/nothing/', 'Custom 404 Page');
},
{ skipDeploy: true /** TODO: remove after prod goes live */ }
)
);
test(
'[vercel dev] test cleanUrls and trailingSlash serve correct content',
testFixtureStdio('test-clean-urls-trailing-slash', async testPath => {
@@ -744,6 +775,20 @@ test(
})
);
test(
'[vercel dev] should serve custom 404 when `trailingSlash: true`',
testFixtureStdio(
'test-trailing-slash-custom-404',
async testPath => {
await testPath(200, '/', 'This is the home page');
await testPath(200, '/about.html', 'The about page');
await testPath(200, '/contact/', 'Contact Subdirectory');
await testPath(404, '/nothing/', 'Custom 404 Page');
},
{ skipDeploy: true /** TODO: remove after prod goes live */ }
)
);
test(
'[vercel dev] test trailingSlash false serve correct content',
testFixtureStdio('test-trailing-slash-false', async testPath => {
@@ -905,6 +950,8 @@ test(
'[vercel dev] 10-nextjs-node',
testFixtureStdio('10-nextjs-node', async testPath => {
await testPath(200, '/', /Next.js \+ Node.js API/m);
await testPath(200, '/api/date', new RegExp(new Date().getFullYear()));
await testPath(404, '/nothing', /Custom Next 404/);
})
);
@@ -1363,4 +1410,3 @@ test(
await testPath(200, '/index.css', 'This is index.css');
})
);