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 NEXT_DATA_MIDDLEWARE_RESOLVING_VERSION = 'v12.1.7-canary.33';
|
||||
const EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION = 'v12.2.0';
|
||||
const CORRECTED_MANIFESTS_VERSION = 'v12.2.0';
|
||||
|
||||
export async function serverBuild({
|
||||
dynamicPages,
|
||||
@@ -146,6 +147,10 @@ export async function serverBuild({
|
||||
nextVersion,
|
||||
CORRECT_MIDDLEWARE_ORDER_VERSION
|
||||
);
|
||||
const isCorrectManifests = semver.gte(
|
||||
nextVersion,
|
||||
CORRECTED_MANIFESTS_VERSION
|
||||
);
|
||||
let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')];
|
||||
|
||||
if (lambdaPageKeys.length === 0) {
|
||||
@@ -409,7 +414,7 @@ export async function serverBuild({
|
||||
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);
|
||||
lstatSema.release();
|
||||
|
||||
@@ -676,18 +681,80 @@ export async function serverBuild({
|
||||
);
|
||||
|
||||
for (const group of combinedGroups) {
|
||||
const lambda = await createLambdaFromPseudoLayers({
|
||||
files: launcherFiles,
|
||||
layers: [
|
||||
group.pseudoLayer,
|
||||
[...group.pages, ...internalPages].reduce((prev, page) => {
|
||||
const groupPageFiles: { [key: string]: PseudoFile } = {};
|
||||
|
||||
for (const page of [...group.pages, ...internalPages]) {
|
||||
const pageFileName = path.normalize(
|
||||
path.relative(baseDir, lambdaPages[page].fsPath)
|
||||
);
|
||||
prev[pageFileName] = compressedPages[page];
|
||||
return prev;
|
||||
}, {} as { [key: string]: PseudoFile }),
|
||||
],
|
||||
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({
|
||||
files: {
|
||||
...launcherFiles,
|
||||
...updatedManifestFiles,
|
||||
},
|
||||
layers: [group.pseudoLayer, groupPageFiles],
|
||||
handler: path.join(
|
||||
path.relative(
|
||||
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 { glob, getWriteableDirectory } = require('@vercel/build-utils');
|
||||
const { glob } = require('@vercel/build-utils');
|
||||
|
||||
function runAnalyze(wrapper, context) {
|
||||
if (wrapper.analyze) {
|
||||
@@ -37,7 +39,13 @@ async function runBuildLambda(inputPath) {
|
||||
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({
|
||||
files: inputFiles,
|
||||
entrypoint,
|
||||
|
||||
Reference in New Issue
Block a user