mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-07 21:07:46 +00:00
[next] Ensure manifests are specific to the included pages (#8172)
### Related Issues
This updates to filter the `routes-manifest` and `pages-manifest` to only include entries for the pages that are being included in the specific serverless function. This fixes the case where multiple dynamic routes could match a path but one is configured with `fallback: false` so shouldn't match when executing for a different dynamic route.
A regression test for this specific scenario has been added in the `00-mixed-dynamic-routes` fixture.
### 📋 Checklist
<!--
Please keep your PR as a Draft until the checklist is complete
-->
#### Tests
- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`
#### Code Review
- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
This commit is contained in:
@@ -57,6 +57,7 @@ const CORRECT_NOT_FOUND_ROUTES_VERSION = 'v12.0.1';
|
|||||||
const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29';
|
const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29';
|
||||||
const NEXT_DATA_MIDDLEWARE_RESOLVING_VERSION = 'v12.1.7-canary.33';
|
const NEXT_DATA_MIDDLEWARE_RESOLVING_VERSION = 'v12.1.7-canary.33';
|
||||||
const EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION = 'v12.2.0';
|
const EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION = 'v12.2.0';
|
||||||
|
const CORRECTED_MANIFESTS_VERSION = 'v12.2.0';
|
||||||
|
|
||||||
export async function serverBuild({
|
export async function serverBuild({
|
||||||
dynamicPages,
|
dynamicPages,
|
||||||
@@ -146,6 +147,10 @@ export async function serverBuild({
|
|||||||
nextVersion,
|
nextVersion,
|
||||||
CORRECT_MIDDLEWARE_ORDER_VERSION
|
CORRECT_MIDDLEWARE_ORDER_VERSION
|
||||||
);
|
);
|
||||||
|
const isCorrectManifests = semver.gte(
|
||||||
|
nextVersion,
|
||||||
|
CORRECTED_MANIFESTS_VERSION
|
||||||
|
);
|
||||||
let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')];
|
let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')];
|
||||||
|
|
||||||
if (lambdaPageKeys.length === 0) {
|
if (lambdaPageKeys.length === 0) {
|
||||||
@@ -409,7 +414,7 @@ export async function serverBuild({
|
|||||||
fsPath = path.join(requiredServerFilesManifest.appDir, file);
|
fsPath = path.join(requiredServerFilesManifest.appDir, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
const relativePath = path.join(path.relative(baseDir, fsPath));
|
const relativePath = path.relative(baseDir, fsPath);
|
||||||
const { mode } = await fs.lstat(fsPath);
|
const { mode } = await fs.lstat(fsPath);
|
||||||
lstatSema.release();
|
lstatSema.release();
|
||||||
|
|
||||||
@@ -676,18 +681,80 @@ export async function serverBuild({
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const group of combinedGroups) {
|
for (const group of combinedGroups) {
|
||||||
|
const groupPageFiles: { [key: string]: PseudoFile } = {};
|
||||||
|
|
||||||
|
for (const page of [...group.pages, ...internalPages]) {
|
||||||
|
const pageFileName = path.normalize(
|
||||||
|
path.relative(baseDir, lambdaPages[page].fsPath)
|
||||||
|
);
|
||||||
|
groupPageFiles[pageFileName] = compressedPages[page];
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedManifestFiles: { [name: string]: FileBlob } = {};
|
||||||
|
|
||||||
|
if (isCorrectManifests) {
|
||||||
|
// filter dynamic routes to only the included dynamic routes
|
||||||
|
// in this specific serverless function so that we don't
|
||||||
|
// accidentally match a dynamic route while resolving that
|
||||||
|
// is not actually in this specific serverless function
|
||||||
|
for (const manifest of [
|
||||||
|
'routes-manifest.json',
|
||||||
|
'server/pages-manifest.json',
|
||||||
|
] as const) {
|
||||||
|
const fsPath = path.join(entryPath, outputDirectory, manifest);
|
||||||
|
|
||||||
|
const relativePath = path.relative(baseDir, fsPath);
|
||||||
|
delete group.pseudoLayer[relativePath];
|
||||||
|
|
||||||
|
const manifestData = await fs.readJSON(fsPath);
|
||||||
|
const normalizedPages = new Set(
|
||||||
|
group.pages.map(page => {
|
||||||
|
page = `/${page.replace(/\.js$/, '')}`;
|
||||||
|
if (page === '/index') page = '/';
|
||||||
|
return page;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (manifest) {
|
||||||
|
case 'routes-manifest.json': {
|
||||||
|
const filterItem = (item: { page: string }) =>
|
||||||
|
normalizedPages.has(item.page);
|
||||||
|
|
||||||
|
manifestData.dynamicRoutes =
|
||||||
|
manifestData.dynamicRoutes?.filter(filterItem);
|
||||||
|
manifestData.staticRoutes =
|
||||||
|
manifestData.staticRoutes?.filter(filterItem);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'server/pages-manifest.json': {
|
||||||
|
for (const key of Object.keys(manifestData)) {
|
||||||
|
if (isDynamicRoute(key) && !normalizedPages.has(key)) {
|
||||||
|
delete manifestData[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new NowBuildError({
|
||||||
|
message: `Unexpected manifest value ${manifest}, please contact support if this continues`,
|
||||||
|
code: 'NEXT_MANIFEST_INVARIANT',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedManifestFiles[relativePath] = new FileBlob({
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(manifestData),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const lambda = await createLambdaFromPseudoLayers({
|
const lambda = await createLambdaFromPseudoLayers({
|
||||||
files: launcherFiles,
|
files: {
|
||||||
layers: [
|
...launcherFiles,
|
||||||
group.pseudoLayer,
|
...updatedManifestFiles,
|
||||||
[...group.pages, ...internalPages].reduce((prev, page) => {
|
},
|
||||||
const pageFileName = path.normalize(
|
layers: [group.pseudoLayer, groupPageFiles],
|
||||||
path.relative(baseDir, lambdaPages[page].fsPath)
|
|
||||||
);
|
|
||||||
prev[pageFileName] = compressedPages[page];
|
|
||||||
return prev;
|
|
||||||
}, {} as { [key: string]: PseudoFile }),
|
|
||||||
],
|
|
||||||
handler: path.join(
|
handler: path.join(
|
||||||
path.relative(
|
path.relative(
|
||||||
baseDir,
|
baseDir,
|
||||||
|
|||||||
8
packages/next/test/fixtures/00-mixed-dynamic-routes/index.test.js
vendored
Normal file
8
packages/next/test/fixtures/00-mixed-dynamic-routes/index.test.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const { deployAndTest } = require('../../utils');
|
||||||
|
|
||||||
|
describe(`${__dirname.split(path.sep).pop()}`, () => {
|
||||||
|
it('should deploy and pass probe checks', async () => {
|
||||||
|
await deployAndTest(__dirname);
|
||||||
|
});
|
||||||
|
});
|
||||||
13
packages/next/test/fixtures/00-mixed-dynamic-routes/package.json
vendored
Normal file
13
packages/next/test/fixtures/00-mixed-dynamic-routes/package.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"next": "canary",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
packages/next/test/fixtures/00-mixed-dynamic-routes/pages/[...slug].js
vendored
Normal file
17
packages/next/test/fixtures/00-mixed-dynamic-routes/pages/[...slug].js
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export default function Page(props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>/[...slug] page</p>
|
||||||
|
<p>{JSON.stringify(props)}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getServerSideProps = async ({ params }) => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
params,
|
||||||
|
page: '[...slug]',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
29
packages/next/test/fixtures/00-mixed-dynamic-routes/pages/[slug].js
vendored
Normal file
29
packages/next/test/fixtures/00-mixed-dynamic-routes/pages/[slug].js
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export default function Page(props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>/[slug] page</p>
|
||||||
|
<p>{JSON.stringify(props)}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticProps = async ({ params }) => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
params,
|
||||||
|
page: '[slug]',
|
||||||
|
now: Date.now(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStaticPaths = async () => {
|
||||||
|
return {
|
||||||
|
paths: [
|
||||||
|
{ params: { slug: 'static-1' } },
|
||||||
|
{ params: { slug: 'static-2' } },
|
||||||
|
{ params: { slug: 'static-3' } },
|
||||||
|
],
|
||||||
|
fallback: false,
|
||||||
|
};
|
||||||
|
};
|
||||||
17
packages/next/test/fixtures/00-mixed-dynamic-routes/pages/index.js
vendored
Normal file
17
packages/next/test/fixtures/00-mixed-dynamic-routes/pages/index.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export default function Page(props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>index page</p>
|
||||||
|
<p>{JSON.stringify(props)}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticProps = async () => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
page: 'index',
|
||||||
|
now: Date.now(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
36
packages/next/test/fixtures/00-mixed-dynamic-routes/vercel.json
vendored
Normal file
36
packages/next/test/fixtures/00-mixed-dynamic-routes/vercel.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "package.json", "use": "@vercel/next" }],
|
||||||
|
"probes": [
|
||||||
|
{
|
||||||
|
"path": "/",
|
||||||
|
"status": 200,
|
||||||
|
"mustContain": "index page"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/static-1",
|
||||||
|
"status": 200,
|
||||||
|
"mustContain": "[slug] page"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/static-2",
|
||||||
|
"status": 200,
|
||||||
|
"mustContain": "[slug] page"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/dynamic-1",
|
||||||
|
"status": 200,
|
||||||
|
"mustContain": "[...slug] page"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/dynamic-2",
|
||||||
|
"status": 200,
|
||||||
|
"mustContain": "[...slug] page"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/long/dynamic",
|
||||||
|
"status": 200,
|
||||||
|
"mustContain": "[...slug] page"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { glob, getWriteableDirectory } = require('@vercel/build-utils');
|
const { glob } = require('@vercel/build-utils');
|
||||||
|
|
||||||
function runAnalyze(wrapper, context) {
|
function runAnalyze(wrapper, context) {
|
||||||
if (wrapper.analyze) {
|
if (wrapper.analyze) {
|
||||||
@@ -37,7 +39,13 @@ async function runBuildLambda(inputPath) {
|
|||||||
config: build.config,
|
config: build.config,
|
||||||
});
|
});
|
||||||
|
|
||||||
const workPath = await fs.realpath(await getWriteableDirectory());
|
const workPath = path.join(
|
||||||
|
os.tmpdir(),
|
||||||
|
`vercel-${Date.now()}-${Math.floor(Math.random() * 100)}`
|
||||||
|
);
|
||||||
|
await fs.ensureDir(workPath);
|
||||||
|
console.log('building in', workPath);
|
||||||
|
|
||||||
const buildResult = await wrapper.build({
|
const buildResult = await wrapper.build({
|
||||||
files: inputFiles,
|
files: inputFiles,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
|
|||||||
Reference in New Issue
Block a user