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,
|
||||
isSharedLambdas,
|
||||
canUsePreviewMode,
|
||||
omittedPrerenderRoutes,
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
|
||||
@@ -1229,34 +1229,31 @@ export async function serverBuild({
|
||||
// We want to add the `experimentalStreamingLambdaPath` to this
|
||||
// output.
|
||||
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
|
||||
// it, because we've already added the lambda to the output.
|
||||
if (routePathname === pagePathname) continue;
|
||||
// For each static route that was generated, we should generate a
|
||||
// specific partial prerendering resume route. This is because any
|
||||
// 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(
|
||||
entryDirectory,
|
||||
routePathname
|
||||
);
|
||||
lambdas[key] = lambda;
|
||||
// If this route is the same as the page route, then we can skip
|
||||
// it, because we've already added the lambda to the output.
|
||||
if (routePathname === pagePathname) continue;
|
||||
|
||||
outputName = path.posix.join(entryDirectory, routePathname);
|
||||
experimentalStreamingLambdaPaths.set(outputName, key);
|
||||
}
|
||||
const key = getPostponeResumePathname(
|
||||
entryDirectory,
|
||||
routePathname
|
||||
);
|
||||
lambdas[key] = lambda;
|
||||
|
||||
outputName = path.posix.join(entryDirectory, routePathname);
|
||||
experimentalStreamingLambdaPaths.set(outputName, key);
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -1314,7 +1311,6 @@ export async function serverBuild({
|
||||
hasPages404: routesManifest.pages404,
|
||||
isCorrectNotFoundRoutes,
|
||||
isEmptyAllowQueryForPrendered,
|
||||
omittedPrerenderRoutes,
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
|
||||
@@ -1938,7 +1938,6 @@ type OnPrerenderRouteArgs = {
|
||||
routesManifest?: RoutesManifest;
|
||||
isCorrectNotFoundRoutes?: boolean;
|
||||
isEmptyAllowQueryForPrendered?: boolean;
|
||||
omittedPrerenderRoutes: ReadonlySet<string>;
|
||||
};
|
||||
let prerenderGroup = 1;
|
||||
|
||||
@@ -1975,7 +1974,6 @@ export const onPrerenderRoute =
|
||||
routesManifest,
|
||||
isCorrectNotFoundRoutes,
|
||||
isEmptyAllowQueryForPrendered,
|
||||
omittedPrerenderRoutes,
|
||||
} = prerenderRouteArgs;
|
||||
|
||||
if (isBlocking && isFallback) {
|
||||
@@ -2211,12 +2209,19 @@ export const onPrerenderRoute =
|
||||
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) {
|
||||
// for literal index routes we need to append an additional /index
|
||||
// due to the proxy's normalizing for /index routes
|
||||
if (routeKey !== '/index' && routeKey.endsWith('/index')) {
|
||||
routeKey = `${routeKey}/index`;
|
||||
routeFileNoExt = routeKey;
|
||||
addedIndexSuffix = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2225,6 +2230,7 @@ export const onPrerenderRoute =
|
||||
if (!isAppPathRoute) {
|
||||
outputPathPage = normalizeIndexOutput(outputPathPage, isServerMode);
|
||||
}
|
||||
|
||||
const outputPathPageOrig = path.posix.join(
|
||||
entryDirectory,
|
||||
origRouteFileNoExt
|
||||
@@ -2408,20 +2414,25 @@ export const onPrerenderRoute =
|
||||
);
|
||||
}
|
||||
|
||||
// If a source route exists, and it's not listed as an omitted route,
|
||||
// then use the src route as the basis for the experimental streaming
|
||||
// lambda path. If the route doesn't have a source route or it's not
|
||||
// omitted, then use the more specific `routeKey` as the basis.
|
||||
if (srcRoute && !omittedPrerenderRoutes.has(srcRoute)) {
|
||||
// Try to get the experimental streaming lambda path for the specific
|
||||
// static route first, then try the srcRoute if it doesn't exist. If we
|
||||
// can't find it at all, this constitutes an error.
|
||||
experimentalStreamingLambdaPath = experimentalStreamingLambdaPaths.get(
|
||||
pathnameToOutputName(entryDirectory, routeKey, addedIndexSuffix)
|
||||
);
|
||||
if (!experimentalStreamingLambdaPath && srcRoute) {
|
||||
experimentalStreamingLambdaPath =
|
||||
experimentalStreamingLambdaPaths.get(
|
||||
pathnameToOutputName(entryDirectory, srcRoute)
|
||||
);
|
||||
} else {
|
||||
experimentalStreamingLambdaPath =
|
||||
experimentalStreamingLambdaPaths.get(
|
||||
pathnameToOutputName(entryDirectory, routeKey)
|
||||
);
|
||||
}
|
||||
|
||||
if (!experimentalStreamingLambdaPath) {
|
||||
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';
|
||||
}
|
||||
|
||||
export function pathnameToOutputName(entryDirectory: string, pathname: string) {
|
||||
if (pathname === '/') pathname = '/index';
|
||||
function pathnameToOutputName(
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,11 @@ export const Dynamic = ({ pathname, fallback }) => {
|
||||
{pathname && (
|
||||
<>
|
||||
<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 }) => (
|
||||
|
||||
@@ -59,6 +59,9 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
|
||||
const html = await res.text();
|
||||
expect(html).toContain(expected);
|
||||
expect(html).toContain('</html>');
|
||||
|
||||
// Validate that the loaded URL is correct.
|
||||
expect(html).toContain(`data-pathname=${pathname}`);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user