mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-10 04:22:12 +00:00
[next] Ensure all static routes have static streaming lambda path (#11259)
When there's a match for a prerender, the dynamic query parameters will
not be provided via the `x-now-route-matches`. This ensures that when
generating any static route that the static streaming lambda path is
used if it's provided, ensuring that when a page is generated via
`generateStaticParams`:
```tsx
// app/blog/[slug]/page.tsx
type Props = {
params: {
slug: string
}
}
export function generateStaticParams() {
return [{ slug: "one" }, { slug: "two" }, { slug: "three" }]
}
export default function BlogPage({ slug }: Props) {
// ...
}
```
That we use the specific routes (`/blog/one`, `/blog/two`, and
`/blog/three`) for the partial prerendering streaming path instead of
the paramatarized pathname (`/blog/[slug]`) as the rewrites won't be
matched once a match for a prerender has been found.
This additionally updates the tests to ensure that the correct pathname
is observed (this was previously silently failing).
This commit is contained in:
5
.changeset/brave-parrots-walk.md
Normal file
5
.changeset/brave-parrots-walk.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@vercel/next': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Ensure that static PPR pages have static streaming lambda paths.
|
||||||
@@ -2142,7 +2142,6 @@ export const build: BuildV2 = async ({
|
|||||||
appPathRoutesManifest,
|
appPathRoutesManifest,
|
||||||
isSharedLambdas,
|
isSharedLambdas,
|
||||||
canUsePreviewMode,
|
canUsePreviewMode,
|
||||||
omittedPrerenderRoutes,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|||||||
@@ -1229,34 +1229,31 @@ export async function serverBuild({
|
|||||||
// We want to add the `experimentalStreamingLambdaPath` to this
|
// We want to add the `experimentalStreamingLambdaPath` to this
|
||||||
// output.
|
// output.
|
||||||
experimentalStreamingLambdaPaths.set(outputName, key);
|
experimentalStreamingLambdaPaths.set(outputName, key);
|
||||||
} else {
|
}
|
||||||
// As this is an omitted page, we should generate the experimental
|
|
||||||
// partial prerendering resume route for each of these routes that
|
|
||||||
// support partial prerendering. This is because the routes that
|
|
||||||
// haven't been omitted will have rewrite rules in place to rewrite
|
|
||||||
// the original request `/blog/my-slug` to the dynamic path
|
|
||||||
// `/blog/[slug]?nxtPslug=my-slug`.
|
|
||||||
for (const [
|
|
||||||
routePathname,
|
|
||||||
{ srcRoute, experimentalPPR },
|
|
||||||
] of Object.entries(prerenderManifest.staticRoutes)) {
|
|
||||||
// If the srcRoute doesn't match or this doesn't support
|
|
||||||
// experimental partial prerendering, then we can skip this route.
|
|
||||||
if (srcRoute !== pagePathname || !experimentalPPR) continue;
|
|
||||||
|
|
||||||
// If this route is the same as the page route, then we can skip
|
// For each static route that was generated, we should generate a
|
||||||
// it, because we've already added the lambda to the output.
|
// specific partial prerendering resume route. This is because any
|
||||||
if (routePathname === pagePathname) continue;
|
// static route that is matched will not hit the rewrite rules.
|
||||||
|
for (const [
|
||||||
|
routePathname,
|
||||||
|
{ srcRoute, experimentalPPR },
|
||||||
|
] of Object.entries(prerenderManifest.staticRoutes)) {
|
||||||
|
// If the srcRoute doesn't match or this doesn't support
|
||||||
|
// experimental partial prerendering, then we can skip this route.
|
||||||
|
if (srcRoute !== pagePathname || !experimentalPPR) continue;
|
||||||
|
|
||||||
const key = getPostponeResumePathname(
|
// If this route is the same as the page route, then we can skip
|
||||||
entryDirectory,
|
// it, because we've already added the lambda to the output.
|
||||||
routePathname
|
if (routePathname === pagePathname) continue;
|
||||||
);
|
|
||||||
lambdas[key] = lambda;
|
|
||||||
|
|
||||||
outputName = path.posix.join(entryDirectory, routePathname);
|
const key = getPostponeResumePathname(
|
||||||
experimentalStreamingLambdaPaths.set(outputName, key);
|
entryDirectory,
|
||||||
}
|
routePathname
|
||||||
|
);
|
||||||
|
lambdas[key] = lambda;
|
||||||
|
|
||||||
|
outputName = path.posix.join(entryDirectory, routePathname);
|
||||||
|
experimentalStreamingLambdaPaths.set(outputName, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -1314,7 +1311,6 @@ export async function serverBuild({
|
|||||||
hasPages404: routesManifest.pages404,
|
hasPages404: routesManifest.pages404,
|
||||||
isCorrectNotFoundRoutes,
|
isCorrectNotFoundRoutes,
|
||||||
isEmptyAllowQueryForPrendered,
|
isEmptyAllowQueryForPrendered,
|
||||||
omittedPrerenderRoutes,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|||||||
@@ -1938,7 +1938,6 @@ type OnPrerenderRouteArgs = {
|
|||||||
routesManifest?: RoutesManifest;
|
routesManifest?: RoutesManifest;
|
||||||
isCorrectNotFoundRoutes?: boolean;
|
isCorrectNotFoundRoutes?: boolean;
|
||||||
isEmptyAllowQueryForPrendered?: boolean;
|
isEmptyAllowQueryForPrendered?: boolean;
|
||||||
omittedPrerenderRoutes: ReadonlySet<string>;
|
|
||||||
};
|
};
|
||||||
let prerenderGroup = 1;
|
let prerenderGroup = 1;
|
||||||
|
|
||||||
@@ -1975,7 +1974,6 @@ export const onPrerenderRoute =
|
|||||||
routesManifest,
|
routesManifest,
|
||||||
isCorrectNotFoundRoutes,
|
isCorrectNotFoundRoutes,
|
||||||
isEmptyAllowQueryForPrendered,
|
isEmptyAllowQueryForPrendered,
|
||||||
omittedPrerenderRoutes,
|
|
||||||
} = prerenderRouteArgs;
|
} = prerenderRouteArgs;
|
||||||
|
|
||||||
if (isBlocking && isFallback) {
|
if (isBlocking && isFallback) {
|
||||||
@@ -2211,12 +2209,19 @@ export const onPrerenderRoute =
|
|||||||
initialStatus = 404;
|
initialStatus = 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the route key had an `/index` suffix added, we need to note it so we
|
||||||
|
* can remove it from the output path later accurately.
|
||||||
|
*/
|
||||||
|
let addedIndexSuffix = false;
|
||||||
|
|
||||||
if (isAppPathRoute) {
|
if (isAppPathRoute) {
|
||||||
// for literal index routes we need to append an additional /index
|
// for literal index routes we need to append an additional /index
|
||||||
// due to the proxy's normalizing for /index routes
|
// due to the proxy's normalizing for /index routes
|
||||||
if (routeKey !== '/index' && routeKey.endsWith('/index')) {
|
if (routeKey !== '/index' && routeKey.endsWith('/index')) {
|
||||||
routeKey = `${routeKey}/index`;
|
routeKey = `${routeKey}/index`;
|
||||||
routeFileNoExt = routeKey;
|
routeFileNoExt = routeKey;
|
||||||
|
addedIndexSuffix = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2225,6 +2230,7 @@ export const onPrerenderRoute =
|
|||||||
if (!isAppPathRoute) {
|
if (!isAppPathRoute) {
|
||||||
outputPathPage = normalizeIndexOutput(outputPathPage, isServerMode);
|
outputPathPage = normalizeIndexOutput(outputPathPage, isServerMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputPathPageOrig = path.posix.join(
|
const outputPathPageOrig = path.posix.join(
|
||||||
entryDirectory,
|
entryDirectory,
|
||||||
origRouteFileNoExt
|
origRouteFileNoExt
|
||||||
@@ -2408,20 +2414,25 @@ export const onPrerenderRoute =
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a source route exists, and it's not listed as an omitted route,
|
// Try to get the experimental streaming lambda path for the specific
|
||||||
// then use the src route as the basis for the experimental streaming
|
// static route first, then try the srcRoute if it doesn't exist. If we
|
||||||
// lambda path. If the route doesn't have a source route or it's not
|
// can't find it at all, this constitutes an error.
|
||||||
// omitted, then use the more specific `routeKey` as the basis.
|
experimentalStreamingLambdaPath = experimentalStreamingLambdaPaths.get(
|
||||||
if (srcRoute && !omittedPrerenderRoutes.has(srcRoute)) {
|
pathnameToOutputName(entryDirectory, routeKey, addedIndexSuffix)
|
||||||
|
);
|
||||||
|
if (!experimentalStreamingLambdaPath && srcRoute) {
|
||||||
experimentalStreamingLambdaPath =
|
experimentalStreamingLambdaPath =
|
||||||
experimentalStreamingLambdaPaths.get(
|
experimentalStreamingLambdaPaths.get(
|
||||||
pathnameToOutputName(entryDirectory, srcRoute)
|
pathnameToOutputName(entryDirectory, srcRoute)
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
experimentalStreamingLambdaPath =
|
|
||||||
experimentalStreamingLambdaPaths.get(
|
if (!experimentalStreamingLambdaPath) {
|
||||||
pathnameToOutputName(entryDirectory, routeKey)
|
throw new Error(
|
||||||
);
|
`Invariant: experimentalStreamingLambdaPath is undefined for routeKey=${routeKey} and srcRoute=${
|
||||||
|
srcRoute ?? 'null'
|
||||||
|
}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2651,8 +2662,20 @@ export function getNextServerPath(nextVersion: string) {
|
|||||||
: 'next/dist/next-server/server';
|
: 'next/dist/next-server/server';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pathnameToOutputName(entryDirectory: string, pathname: string) {
|
function pathnameToOutputName(
|
||||||
if (pathname === '/') pathname = '/index';
|
entryDirectory: string,
|
||||||
|
pathname: string,
|
||||||
|
addedIndexSuffix = false
|
||||||
|
) {
|
||||||
|
if (pathname === '/') {
|
||||||
|
pathname = '/index';
|
||||||
|
}
|
||||||
|
// If the `/index` was added for a route that ended in `/index` we need to
|
||||||
|
// strip the second one off before joining it with the entryDirectory.
|
||||||
|
else if (addedIndexSuffix) {
|
||||||
|
pathname = pathname.replace(/\/index$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
return path.posix.join(entryDirectory, pathname);
|
return path.posix.join(entryDirectory, pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ export const Dynamic = ({ pathname, fallback }) => {
|
|||||||
{pathname && (
|
{pathname && (
|
||||||
<>
|
<>
|
||||||
<dt>Pathname</dt>
|
<dt>Pathname</dt>
|
||||||
<dd>{pathname}</dd>
|
{/* We're encoding this using the following format so that even if
|
||||||
|
the HTML is sent as flight data, it will still retain the same
|
||||||
|
content, and can be inspected without having to run the
|
||||||
|
javascript. */}
|
||||||
|
<dd data-pathname={`data-pathname=${pathname}`}>{pathname}</dd>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{messages.map(({ name, value }) => (
|
{messages.map(({ name, value }) => (
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
|
|||||||
const html = await res.text();
|
const html = await res.text();
|
||||||
expect(html).toContain(expected);
|
expect(html).toContain(expected);
|
||||||
expect(html).toContain('</html>');
|
expect(html).toContain('</html>');
|
||||||
|
|
||||||
|
// Validate that the loaded URL is correct.
|
||||||
|
expect(html).toContain(`data-pathname=${pathname}`);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user