Compare commits

..

12 Commits

Author SHA1 Message Date
JJ Kasper
ff2a22023d Publish Stable
- @vercel/next@2.6.4
2020-06-08 14:56:04 -05:00
JJ Kasper
c6efc028aa Publish Canary
- vercel@19.0.2-canary.14
 - @vercel/next@2.6.4-canary.0
2020-06-08 14:48:59 -05:00
JJ Kasper
96565da1cf [next] Add shared lambdas opt-out for functions config (#4596)
As discussed this adds opting out of the shared lambdas optimization when a user adds a functions config in `now.json` or `vercel.json` since this could potentially be a breaking change. We plan to add new handling to still allow customizing this config for the combined lambdas that are created
2020-06-08 19:40:48 +00:00
Steven
afb5e7fc85 [cli] Update tests for 404.html (#4597)
Now that we updated api-deployments, we can remove this TODO and run tests agains `vercel dev` and real deployments.
2020-06-08 18:42:45 +00:00
Nathan Rajlich
34cc987be8 [cli] Make proxyPass() return a 502 if the proxying fails (#4586)
This happens, for example, with a `startDevServer()` process that
crashes (i.e. a syntax error in a Node.js API endpoint) before
responding to the HTTP request.
2020-06-08 18:02:05 +00:00
JJ Kasper
55c60d30e6 Publish Stable
- @vercel/next@2.6.3
2020-06-08 09:54:44 -05:00
Nathan Rajlich
eb993d47ac [cli] Update 502 error template for vc dev (#4583)
* Fix the "Developer Documentation" link.
 * Remove the "If you're a visitor" section - doesn't make sense for `vc dev` since there are no "visitors".
 * Don't link to `_logs` since it's not supported in `vc dev`. Instead direct the user to look at their terminal window to see error logs.
 * Link to new GH issue for non-app error 502 (I don't think this code path ever happens in `vc dev`, but might as well make it correct in case we do in the future).

<img width="1077" alt="Screen Shot 2020-06-05 at 4 15 16 PM" src="https://user-images.githubusercontent.com/71256/83929319-c7832a00-a747-11ea-9cae-b0adac97dfa5.png">
2020-06-05 23:20:33 +00:00
JJ Kasper
d2184628d1 Publish Canary
- vercel@19.0.2-canary.13
 - @vercel/go@1.1.2-canary.2
 - @vercel/next@2.6.3-canary.6
2020-06-05 14:52:45 -05:00
JJ Kasper
65f0cc6797 De-experimentalize shared lambdas optimization by default (#4519)
As discussed this de-experimentalizes the shared lambdas optimization now that we have tested it, it also bails out of the optimization when a `now.json` or `vercel.json` is detected that contains legacy routes
2020-06-05 19:51:21 +00:00
Nathan Rajlich
c628c7b58e [go] Fix typo in go-bridge import (#4578)
Introduced in 56c8af51b.
2020-06-05 13:09:26 +00:00
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
67 changed files with 423 additions and 1119 deletions

View File

@@ -0,0 +1,14 @@
# `@vercel/next` Functions Config Optimized Lambdas Opt-out
#### Why This Warning Occurred
`@vercel/next` by default now bundles pages into optimized functions, minimizing bootup time and increasing overall application throughput.
When the `functions` config is added in `now.json` or `vercel.json`, it causes conflicts with this optimization, so it is opted-out.
#### Possible Ways to Fix It
Remove the `functions` config from your `now.json` or `vercel.json` to take advantage of this optimization.
### Useful Links
- [Functions Config Documentation](https://vercel.com/docs/configuration?query=functions#project/functions)

View File

@@ -0,0 +1,16 @@
# `@vercel/next` Legacy Routes Optimized Lambdas Opt-out
#### Why This Warning Occurred
`@vercel/next` by default now bundles pages into optimized functions, minimizing bootup time and increasing overall application throughput.
When legacy `routes` are added in `now.json` or `vercel.json`, they cause conflicts with this optimization, so it is opted-out.
#### Possible Ways to Fix It
Migrate from using legacy `routes` to the new `rewrites`, `redirects`, and `headers` configurations in your `now.json` or `vercel.json` file or leverage them directly in your `next.config.js` with the built-in [custom routes support](https://github.com/zeit/next.js/issues/9081)
### Useful Links
- [Rewrites Documentation](https://vercel.com/docs/configuration?query=rewrites#project/rewrites)
- [Redirects Documentation](https://vercel.com/docs/configuration?query=rewrites#project/redirects)
- [Headers Documentation](https://vercel.com/docs/configuration?query=rewrites#project/headers)

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.14",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",
@@ -62,9 +62,9 @@
"node": ">= 10"
},
"dependencies": {
"@vercel/build-utils": "2.3.2-canary.3",
"@vercel/go": "1.1.2-canary.1",
"@vercel/next": "2.6.3-canary.5",
"@vercel/build-utils": "2.3.2-canary.4",
"@vercel/go": "1.1.2-canary.2",
"@vercel/next": "2.6.3-canary.6",
"@vercel/node": "1.6.2-canary.5",
"@vercel/python": "1.2.2-canary.2",
"@vercel/ruby": "1.2.2-canary.1",

View File

@@ -114,6 +114,7 @@ export default class DevServer {
public cwd: string;
public debug: boolean;
public output: Output;
public proxy: httpProxy;
public envConfigs: EnvConfigs;
public frameworkSlug: string | null;
public files: BuilderInputs;
@@ -124,7 +125,6 @@ export default class DevServer {
private caseSensitive: boolean;
private apiDir: string | null;
private apiExtensions: Set<string>;
private proxy: httpProxy;
private server: http.Server;
private stopping: boolean;
private buildMatches: Map<string, BuildMatch>;
@@ -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;
@@ -1362,7 +1367,7 @@ export default class DevServer {
debug(`ProxyPass: ${destUrl}`);
this.setResponseHeaders(res, nowRequestId);
return proxyPass(req, res, destUrl, this.proxy, this.output);
return proxyPass(req, res, destUrl, this, nowRequestId);
}
match = await findBuildMatch(
@@ -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) {
@@ -1475,8 +1506,8 @@ export default class DevServer {
req,
res,
`http://localhost:${this.devProcessPort}`,
this.proxy,
this.output,
this,
nowRequestId,
false
);
}
@@ -1560,7 +1591,7 @@ export default class DevServer {
req,
res,
nowRequestId,
'NO_STATUS_CODE_FROM_DEV_SERVER',
'NO_RESPONSE_FROM_FUNCTION',
502
);
return;
@@ -1595,8 +1626,8 @@ export default class DevServer {
req,
res,
`http://localhost:${port}`,
this.proxy,
this.output,
this,
nowRequestId,
false
);
} else {
@@ -1625,8 +1656,8 @@ export default class DevServer {
req,
res,
`http://localhost:${this.devProcessPort}`,
this.proxy,
this.output,
this,
nowRequestId,
false
);
}
@@ -1720,7 +1751,7 @@ export default class DevServer {
req,
res,
nowRequestId,
'NO_STATUS_CODE_FROM_LAMBDA',
'NO_RESPONSE_FROM_FUNCTION',
502
);
return;
@@ -1936,24 +1967,27 @@ function proxyPass(
req: http.IncomingMessage,
res: http.ServerResponse,
dest: string,
proxy: httpProxy,
output: Output,
devServer: DevServer,
nowRequestId: string,
ignorePath: boolean = true
): void {
return proxy.web(
return devServer.proxy.web(
req,
res,
{ target: dest, ignorePath },
(error: NodeJS.ErrnoException) => {
// If the client hangs up a socket, we do not
// want to do anything, as the client just expects
// the connection to be closed.
if (error.code === 'ECONNRESET') {
res.end();
return;
devServer.output.error(
`Failed to complete request to ${req.url}: ${error}`
);
if (!res.headersSent) {
devServer.sendError(
req,
res,
nowRequestId,
'NO_RESPONSE_FROM_FUNCTION',
502
);
}
output.error(`Failed to complete request to ${req.url}: ${error}`);
}
);
}

View File

@@ -44,22 +44,16 @@
<p>
<ul>
<li>
If you are a visitor; contact the website owner or try again later.
</li>
<li>
If you are the owner; <a href="/_logs" target="_blank">check the logs</a> for the application error.
Check the logs in your terminal window to see the application error.
</li>
</ul>
<a target="_blank" href="https://vercel.com/docs/router-status/{{= it.http_status_code }}" class="docs-link" rel="noopener noreferrer">Developer Documentation →</a>
<a target="_blank" href="https://vercel.com/docs/error/application/{{= it.error_code }}" class="docs-link" rel="noopener noreferrer">Developer Documentation →</a>
</p>
{{??}}
<p>
<ul>
<li>
If you are a visitor, please try again later.
</li>
<li>
If you are the owner, no action is needed. Our engineers have been notified.
Please open a <a target="_blank" href="https://github.com/vercel/vercel/issues/new/choose">GitHub issue</a> describing the problem you are experiencing with <code>vercel dev</code>.
</li>
</ul>
</p>

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 => {
@@ -477,7 +488,8 @@ test(
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');
await testPath(404, '/api/api', /NOT_FOUND/m);
await testPath(404, '/nothing', /Custom 404 Page/);
})
);
@@ -676,6 +688,17 @@ 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');
})
);
test(
'[vercel dev] test cleanUrls and trailingSlash serve correct content',
testFixtureStdio('test-clean-urls-trailing-slash', async testPath => {
@@ -744,6 +767,16 @@ 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');
})
);
test(
'[vercel dev] test trailingSlash false serve correct content',
testFixtureStdio('test-trailing-slash-false', async testPath => {
@@ -905,6 +938,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 +1398,3 @@ test(
await testPath(200, '/index.css', 'This is index.css');
})
);

View File

@@ -2,7 +2,7 @@ package main
import (
"net/http"
vc "github.com/vercel/go-bridge/go/bridgee"
vc "github.com/vercel/go-bridge/go/bridge"
)
func main() {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/go",
"version": "1.1.2-canary.1",
"version": "1.1.2-canary.2",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "2.6.3-canary.5",
"version": "2.6.4",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -20,6 +20,7 @@
"devDependencies": {
"@types/aws-lambda": "8.10.19",
"@types/buffer-crc32": "0.2.0",
"@types/find-up": "4.0.0",
"@types/fs-extra": "8.0.0",
"@types/next-server": "8.0.0",
"@types/resolve-from": "5.0.1",
@@ -30,6 +31,7 @@
"buffer-crc32": "0.2.13",
"escape-string-regexp": "3.0.0",
"execa": "2.0.4",
"find-up": "4.1.0",
"fs-extra": "7.0.0",
"get-port": "5.0.0",
"resolve-from": "5.0.0",

View File

@@ -70,6 +70,7 @@ import {
syncEnvVars,
validateEntrypoint,
} from './utils';
import findUp from 'find-up';
interface BuildParamsMeta {
isDev: boolean | undefined;
@@ -204,7 +205,39 @@ export const build = async ({
}> => {
validateEntrypoint(entrypoint);
const isSharedLambdas = config.sharedLambdas;
const nowJsonPath = await findUp(['now.json', 'vercel.json'], {
type: 'file',
});
let hasLegacyRoutes = false;
const hasFunctionsConfig = !!config.functions;
if (nowJsonPath) {
const nowJsonData = JSON.parse(await readFile(nowJsonPath, 'utf8'));
if (Array.isArray(nowJsonData.routes) && nowJsonData.routes.length > 0) {
hasLegacyRoutes = true;
console.warn(
`WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes in ${path.basename(
nowJsonPath
)}. http://err.sh/vercel/vercel/next-legacy-routes-optimized-lambdas`
);
}
}
if (hasFunctionsConfig) {
console.warn(
`WARNING: Your application is being opted out of "@vercel/next" optimized lambdas mode due to \`functions\` config.\nMore info: http://err.sh/vercel/vercel/next-functions-config-optimized-lambdas`
);
}
// default to true but still allow opting out with the config
const isSharedLambdas =
!hasLegacyRoutes &&
!hasFunctionsConfig &&
typeof config.sharedLambdas === 'undefined'
? true
: !!config.sharedLambdas;
// Limit for max size each lambda can be, 50 MB if no custom limit
const lambdaCompressedByteLimit = config.maxLambdaSize || 50 * 1000 * 1000;
@@ -1154,50 +1187,6 @@ export const build = async ({
);
if (isSharedLambdas) {
// since we combine the pages into lambda groups we need to merge
// the lambda options into one this means they should only configure
// lambda options for one page or one API as doing it for
// another will override it
const getMergedLambdaOptions = async (pageKeys: string[]) => {
if (pageKeys.length === 0) return {};
const lambdaOptions = await getLambdaOptionsFromFunction({
sourceFile: await getSourceFilePathFromPage({
workPath,
page: pageKeys[0],
}),
config,
});
for (const page of pageKeys) {
if (page === pageKeys[0]) continue;
const sourceFile = await getSourceFilePathFromPage({
workPath,
page,
});
const newOptions = await getLambdaOptionsFromFunction({
sourceFile,
config,
});
for (const key of Object.keys(newOptions)) {
// eslint-disable-next-line
if (typeof (newOptions as any)[key] !== 'undefined') {
// eslint-disable-next-line
(lambdaOptions as any)[key] = (newOptions as any)[key];
}
}
}
return lambdaOptions;
};
const mergedPageLambdaOptions = await getMergedLambdaOptions(
pageKeys.filter(page => !page.startsWith('api/'))
);
const mergedApiLambdaOptions = await getMergedLambdaOptions(
pageKeys.filter(page => page.startsWith('api/'))
);
const launcherPath = path.join(__dirname, 'templated-launcher-shared.js');
const launcherData = await readFile(launcherPath, 'utf8');
@@ -1313,9 +1302,6 @@ export const build = async ({
],
handler: 'now__launcher.launcher',
runtime: nodeVersion.runtime,
...(group.isApiLambda
? mergedApiLambdaOptions
: mergedPageLambdaOptions),
});
} else {
lambdas[
@@ -1328,9 +1314,6 @@ export const build = async ({
layers: pageLayers,
handler: 'now__launcher.launcher',
runtime: nodeVersion.runtime,
...(group.isApiLambda
? mergedApiLambdaOptions
: mergedPageLambdaOptions),
});
}
}

View File

@@ -1,5 +0,0 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
};

View File

@@ -1,226 +0,0 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next",
"config": {
"sharedLambdas": true
}
}
],
"probes": [
{
"path": "/lambda",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "MISS"
}
},
{
"path": "/forever",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/forever",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/another",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/another",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/blog/post-1",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/blog/post-1",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/blog/post-2",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/blog/post-2",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/blog/post-3",
"status": 200,
"mustContain": "loading..."
},
{ "delay": 2000 },
{
"path": "/blog/post-3",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE/"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-4.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/_next/data/testing-build-id/blog/post-4.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE/"
}
},
{
"path": "/blog/post-3",
"status": 200,
"mustContain": "post-3"
},
{
"path": "/blog/post-1/comment-1",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "PRERENDER"
}
},
{
"path": "/blog/post-2/comment-2",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "PRERENDER"
}
},
{
"path": "/blog/post-3/comment-3",
"status": 200,
"mustContain": "loading..."
},
{
"path": "/_next/data/testing-build-id/lambda.json",
"status": 404
},
{
"path": "/_next/data/testing-build-id/forever.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/PRERENDER|HIT/"
}
},
{ "delay": 2000 },
{
"path": "/blog/post-3/comment-3",
"status": 200,
"mustContain": "comment-3"
},
{
"path": "/_next/data/testing-build-id/forever.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/_next/data/testing-build-id/another.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE/"
}
},
{
"path": "/_next/data/testing-build-id/another2.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/_next/data/testing-build-id/another2.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-1.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE|PRERENDER/"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-1337/comment-1337.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "PRERENDER"
}
},
{
"path": "/nofallback/one",
"status": 200,
"mustContain": "one"
},
{
"path": "/nofallback/two",
"status": 200,
"mustContain": "two"
},
{
"path": "/nofallback/nope",
"status": 404,
"mustContain": "This page could not be found"
},
{
"path": "/_next/data/testing-build-id/nofallback/one.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE|PRERENDER/"
}
},
{
"path": "/_next/data/testing-build-id/nofallback/two.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE|PRERENDER/"
}
},
{
"path": "/_next/data/testing-build-id/nofallback/nope.json",
"status": 404
}
]
}

View File

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

View File

@@ -1,21 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
revalidate: 5,
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -1,21 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
revalidate: 5,
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -1,37 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticPaths() {
return {
paths: [
'/blog/post-1/comment-1',
{ params: { post: 'post-2', comment: 'comment-2' } },
'/blog/post-1337/comment-1337',
],
fallback: true,
};
}
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps({ params }) {
return {
props: {
post: params.post,
comment: params.comment,
time: new Date().getTime(),
},
revalidate: 2,
};
}
export default ({ post, comment, time }) => {
if (!post) return <p>loading...</p>;
return (
<>
<p>Post: {post}</p>
<p>Comment: {comment}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -1,37 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticPaths() {
return {
paths: ['/blog/post-1', { params: { post: 'post-2' } }],
fallback: true,
};
}
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps({ params }) {
if (params.post === 'post-10') {
await new Promise(resolve => {
setTimeout(() => resolve(), 1000);
});
}
return {
props: {
post: params.post,
time: (await import('perf_hooks')).performance.now(),
},
revalidate: 10,
};
}
export default ({ post, time }) => {
if (!post) return <p>loading...</p>;
return (
<>
<p>Post: {post}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -1,21 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
revalidate: false,
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -1 +0,0 @@
export default () => 'Hi';

View File

@@ -1,5 +0,0 @@
const Page = ({ data }) => <p>{data} world</p>;
Page.getInitialProps = () => ({ data: 'hello' });
export default Page;

View File

@@ -1,31 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getStaticPaths() {
return {
paths: ['/nofallback/one', { params: { slug: 'two' } }],
fallback: false,
};
}
// eslint-disable-next-line camelcase
export async function unstable_getStaticProps({ params }) {
return {
props: {
slug: params.slug,
time: (await import('perf_hooks')).performance.now(),
},
revalidate: 10,
};
}
export default ({ slug, time }) => {
return (
<>
<p>
Slug ({slug.length}): {slug}
</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -1,211 +0,0 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
experimental: {
async rewrites() {
return [
{
source: '/to-another',
destination: '/another/one',
},
{
source: '/nav',
destination: '/404',
},
{
source: '/hello-world',
destination: '/static/hello.txt',
},
{
source: '/',
destination: '/another',
},
{
source: '/another',
destination: '/multi-rewrites',
},
{
source: '/first',
destination: '/hello',
},
{
source: '/second',
destination: '/hello-again',
},
{
source: '/to-hello',
destination: '/hello',
},
{
source: '/blog/post-1',
destination: '/blog/post-2',
},
{
source: '/test/:path',
destination: '/:path',
},
{
source: '/test-overwrite/:something/:another',
destination: '/params/this-should-be-the-value',
},
{
source: '/params/:something',
destination: '/with-params',
},
{
source: '/query-rewrite/:section/:name',
destination: '/with-params?first=:section&second=:name',
},
{
source: '/hidden/_next/:path*',
destination: '/_next/:path*',
},
{
source: '/api-hello',
destination: '/api/hello',
},
{
source: '/api-hello-regex/(.*)',
destination: '/api/hello?name=:1',
},
{
source: '/api-hello-param/:name',
destination: '/api/hello?hello=:name',
},
{
source: '/api-dynamic-param/:name',
destination: '/api/dynamic/:name?hello=:name',
},
{
source: '/:path/post-321',
destination: '/with-params',
},
];
},
async redirects() {
return [
{
source: '/redirect/me/to-about/:lang',
destination: '/:lang/about',
permanent: false,
},
{
source: '/docs/router-status/:code',
destination: '/docs/v2/network/status-codes#:code',
statusCode: 301,
},
{
source: '/docs/github',
destination: '/docs/v2/advanced/now-for-github',
statusCode: 301,
},
{
source: '/docs/v2/advanced/:all(.*)',
destination: '/docs/v2/more/:all',
statusCode: 301,
},
{
source: '/hello/:id/another',
destination: '/blog/:id',
permanent: false,
},
{
source: '/redirect1',
destination: '/',
permanent: false,
},
{
source: '/redirect2',
destination: '/',
statusCode: 301,
},
{
source: '/redirect3',
destination: '/another',
statusCode: 302,
},
{
source: '/redirect4',
destination: '/',
permanent: true,
},
{
source: '/redir-chain1',
destination: '/redir-chain2',
statusCode: 301,
},
{
source: '/redir-chain2',
destination: '/redir-chain3',
statusCode: 302,
},
{
source: '/redir-chain3',
destination: '/',
statusCode: 303,
},
{
source: '/to-external',
destination: 'https://google.com',
permanent: false,
},
{
source: '/query-redirect/:section/:name',
destination: '/with-params?first=:section&second=:name',
permanent: false,
},
{
source: '/named-like-unnamed/:0',
destination: '/:0',
permanent: false,
},
{
source: '/redirect-override',
destination: '/thank-you-next',
permanent: false,
},
];
},
async headers() {
return [
{
source: '/add-header',
headers: [
{
key: 'x-custom-header',
value: 'hello world',
},
{
key: 'x-another-header',
value: 'hello again',
},
],
},
{
source: '/my-headers/(.*)',
headers: [
{
key: 'x-first-header',
value: 'first',
},
{
key: 'x-second-header',
value: 'second',
},
],
},
{
source: '/:path*',
headers: [
{
key: 'x-something',
value: 'applied-everywhere',
},
],
},
];
},
},
};

View File

@@ -1,258 +0,0 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next",
"config": {
"sharedLambdas": true
}
}
],
"probes": [
// should handle one-to-one rewrite successfully
{
"path": "/first",
"mustContain": "hello"
},
// should handle chained rewrites successfully
{
"path": "/",
"mustContain": "multi-rewrites"
},
// should not match dynamic route immediately after applying header
{
"path": "/blog/post-321",
"mustContain": "with-params"
},
{
"path": "/blog/post-321",
"mustNotContain": "post-321"
},
// should handle chained redirects successfully
{
"path": "/redir-chain1",
"status": 301,
"responseHeaders": {
"location": "//redir-chain2/"
},
"fetchOptions": {
"redirect": "manual"
}
},
{
"path": "/redir-chain2",
"status": 302,
"responseHeaders": {
"location": "//redir-chain3/"
},
"fetchOptions": {
"redirect": "manual"
}
},
{
"path": "/redir-chain3",
"status": 303,
"responseHeaders": {
"location": "//$/"
},
"fetchOptions": {
"redirect": "manual"
}
},
// should redirect successfully with permanent: false
{
"path": "/redirect1",
"status": 307,
"responseHeaders": {
"location": "//$/"
},
"fetchOptions": {
"redirect": "manual"
}
},
// should redirect with params successfully
{
"path": "/hello/123/another",
"status": 307,
"responseHeaders": {
"location": "//blog/123/"
},
"fetchOptions": {
"redirect": "manual"
}
},
// should redirect with hash successfully
{
"path": "/docs/router-status/500",
"status": 301,
"responseHeaders": {
"location": "/#500$/"
},
"fetchOptions": {
"redirect": "manual"
}
},
// should redirect successfully with provided statusCode
{
"path": "/redirect2",
"status": 301,
"responseHeaders": {
"location": "//$/"
},
"fetchOptions": {
"redirect": "manual"
}
},
// should server static files through a rewrite
{
"path": "/hello-world",
"mustContain": "hello world!"
},
// should rewrite with params successfully
{
"path": "/test/hello",
"mustContain": "Hello"
},
// should double redirect successfully
{
"path": "/docs/github",
"mustContain": "hi there"
},
// should allow params in query for rewrite
{
"path": "/query-rewrite/hello/world?a=b",
"mustContain": "\"a\":\"b\""
},
{
"path": "/query-rewrite/hello/world?a=b",
"mustContain": "\"section\":\"hello\""
},
{
"path": "/query-rewrite/hello/world?a=b",
"mustContain": "\"name\":\"world\""
},
{
"path": "/query-rewrite/hello/world?a=b",
"mustContain": "\"first\":\"hello\""
},
{
"path": "/query-rewrite/hello/world?a=b",
"mustContain": "\"second\":\"world\""
},
// should not allow rewrite to override page file
{
"path": "/nav",
"mustContain": "to-hello"
},
// show allow redirect to override the page
{
"path": "/redirect-override",
"status": 307,
"responseHeaders": {
"location": "//thank-you-next$/"
},
"fetchOptions": {
"redirect": "manual"
}
},
// should match a page after a rewrite
{
"path": "/to-hello",
"mustContain": "Hello"
},
// should match dynamic route after rewrite
{
"path": "/blog/post-1",
"mustContain": "post-2"
},
// should match public file after rewrite
{
"path": "/blog/data.json",
"mustContain": "\"hello\": \"world\""
},
// should match /_next file after rewrite
{
"path": "/hidden/_next/static/testing-build-id/pages/hello.js",
"mustContain": "createElement"
},
// should allow redirecting to external resource
{
"path": "/to-external",
"status": 307,
"responseHeaders": {
"location": "/google.com/"
},
"fetchOptions": {
"redirect": "manual"
}
},
// should apply headers for exact match
{
"path": "/add-header",
"responseHeaders": {
"x-custom-header": "hello world",
"x-another-header": "hello again"
}
},
// should apply headers for multi match
{
"path": "/my-headers/first",
"responseHeaders": {
"x-first-header": "first",
"x-second-header": "second"
}
},
// should handle basic api rewrite successfully
{
"path": "/api-hello",
"mustContain": "{\"query\":{}}"
},
// should handle api rewrite with param successfully
{
"path": "/api-hello-param/hello",
"mustContain": "{\"query\":{\"hello\":\"hello\",\"name\":\"hello\"}}"
},
// should handle encoded value in the pathname correctly
{
"path": "/redirect/me/to-about/%5Cgoogle.com",
"status": 307,
"responseHeaders": {
"location": "/%5Cgoogle.com/about/"
},
"fetchOptions": {
"redirect": "manual"
}
},
// should route dynamic routes at the same level to page
// lambdas correctly
{
"path": "/nested/teams/invite/hello-world",
"mustContain": "hello-world"
}
]
}

View File

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

View File

@@ -1 +0,0 @@
export default () => 'hi'

View File

@@ -1 +0,0 @@
export default async (req, res) => res.json({ query: req.query })

View File

@@ -1 +0,0 @@
export default async (req, res) => res.json({ query: req.query });

View File

@@ -1,11 +0,0 @@
import { useRouter } from 'next/router'
const Page = () => (
<>
<p>post: {useRouter().query.post}</p>
</>
)
Page.getInitialProps = () => ({ hello: 'world' })
export default Page

View File

@@ -1,10 +0,0 @@
import Link from 'next/link';
export default () => (
<>
<h3 id="hello-again">Hello again</h3>
<Link href="/nav">
<a id="to-nav">to nav</a>
</Link>
</>
);

View File

@@ -1,14 +0,0 @@
import Link from 'next/link';
const Page = () => (
<>
<h3 id="hello">Hello</h3>
<Link href="/nav">
<a id="to-nav">to nav</a>
</Link>
</>
);
Page.getInitialProps = () => ({ hello: 'world' });
export default Page;

View File

@@ -1 +0,0 @@
export default () => 'multi-rewrites';

View File

@@ -1,13 +0,0 @@
import Link from 'next/link';
export default () => (
<>
<h3 id="nav">Nav</h3>
<Link href="/hello" as="/first">
<a id="to-hello">to hello</a>
</Link>
<Link href="/hello-again" as="/second">
<a id="to-hello-again">to hello-again</a>
</Link>
</>
);

View File

@@ -1 +0,0 @@
export default () => "hello from /[slug]/[team]/[id]"

View File

@@ -1,9 +0,0 @@
function InviteCode(props) {
return <p>invite code: {props.query?.inviteCode}</p>
}
InviteCode.getInitialProps = ({ query }) => ({
query
})
export default InviteCode

View File

@@ -1,10 +0,0 @@
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

@@ -1 +0,0 @@
export default () => 'got to the page';

View File

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

View File

@@ -15,7 +15,8 @@ it(
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'standard'));
expect(output['index']).toBeDefined();
expect(output.goodbye).toBeDefined();
expect(output.goodbye).not.toBeDefined();
expect(output.__NEXT_PAGE_LAMBDA_0).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
filePath.match(/_error/)
@@ -39,7 +40,9 @@ it(
const {
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'monorepo'));
expect(output['www/index']).toBeDefined();
expect(output['www/index']).not.toBeDefined();
expect(output['www/__NEXT_PAGE_LAMBDA_0']).toBeDefined();
expect(output['www/static/test.txt']).toBeDefined();
expect(output['www/data.txt']).toBeDefined();
const filePaths = Object.keys(output);
@@ -139,8 +142,9 @@ it(
buildResult: { output },
} = await runBuildLambda(path.join(__dirname, 'serverless-config'));
expect(output.index).toBeDefined();
expect(output.goodbye).toBeDefined();
expect(output.index).not.toBeDefined();
expect(output.goodbye).not.toBeDefined();
expect(output.__NEXT_PAGE_LAMBDA_0).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
filePath.match(/_error/)
@@ -175,8 +179,9 @@ it(
path.join(__dirname, 'serverless-config-monorepo-missing')
);
expect(output['nested/index']).toBeDefined();
expect(output['nested/goodbye']).toBeDefined();
expect(output['nested/index']).not.toBeDefined();
expect(output['nested/goodbye']).not.toBeDefined();
expect(output['nested/__NEXT_PAGE_LAMBDA_0']).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
filePath.match(/_error/)
@@ -208,8 +213,9 @@ it(
path.join(__dirname, 'serverless-config-monorepo-present')
);
expect(output['nested/index']).toBeDefined();
expect(output['nested/goodbye']).toBeDefined();
expect(output['nested/index']).not.toBeDefined();
expect(output['nested/goodbye']).not.toBeDefined();
expect(output['nested/__NEXT_PAGE_LAMBDA_0']).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
filePath.match(/_error/)
@@ -275,7 +281,8 @@ it(
} = await runBuildLambda(path.join(__dirname, 'serverless-config-object'));
expect(output['index']).toBeDefined();
expect(output.goodbye).toBeDefined();
expect(output.goodbye).not.toBeDefined();
expect(output.__NEXT_PAGE_LAMBDA_0).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
filePath.match(/_error/)
@@ -309,7 +316,8 @@ it(
} = await runBuildLambda(path.join(__dirname, 'serverless-no-config'));
expect(output['index']).toBeDefined();
expect(output.goodbye).toBeDefined();
expect(output.goodbye).not.toBeDefined();
expect(output.__NEXT_PAGE_LAMBDA_0).toBeDefined();
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath =>
filePath.match(/_error/)

View File

@@ -1696,6 +1696,13 @@
dependencies:
"@types/node" "*"
"@types/find-up@4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/find-up/-/find-up-4.0.0.tgz#6b74a76670477a23f0793cfaf2dafc86df59723a"
integrity sha512-QlRNKeOPFWKisbNtKVOOGXw3AeLbkw8UmT/EyEGM6brfqpYffKBcch7f1y40NYN9O90aK2+K0xBMDJfOAsg2qg==
dependencies:
find-up "*"
"@types/fs-extra@5.0.5":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b"
@@ -5159,6 +5166,14 @@ find-cache-dir@^2.0.0:
make-dir "^2.0.0"
pkg-dir "^3.0.0"
find-up@*, find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
find-up@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
@@ -5181,14 +5196,6 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
flat-cache@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"