Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions packages/vinext/src/build/prerender-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,12 @@ function validateDiscoveredParams(

function validatePagesStaticPathsEntry(entry: StaticPathsEntry, pattern: string): StaticPathsEntry {
if (typeof entry === "string") {
if (entry.includes("?") || entry.includes("#")) {
if (
!entry.startsWith("/") ||
entry.includes("//") ||
entry.includes("?") ||
entry.includes("#")
) {
throw new Error(
`The provided path \`${entry}\` from getStaticPaths does not match the route pattern \`${pattern}\`.`,
);
Expand Down Expand Up @@ -381,10 +386,12 @@ async function collectPagesPaths(options: {
const validatedItem = validatePagesStaticPathsEntry(item, route.pattern);
let itemToNormalize = validatedItem;
let locale = options.i18n?.defaultLocale;
let explicitLocalePrefix: string | undefined;
if (options.i18n && typeof validatedItem === "string") {
const localeInfo = extractPagesStaticPathLocale(validatedItem, options.i18n);
itemToNormalize = localeInfo.url;
locale = localeInfo.locale;
explicitLocalePrefix = localeInfo.explicitLocalePrefix;
} else if (
options.i18n &&
validatedItem &&
Expand All @@ -404,7 +411,11 @@ async function collectPagesPaths(options: {
throw new Error(normalized.error);
}
const pathname = buildUrlFromParams(route.pattern, normalized.params);
addPath(paths, seen, localizePagesPath(pathname, locale, options.i18n));
addPath(
paths,
seen,
localizePagesPath(pathname, locale, options.i18n, explicitLocalePrefix),
);
}
} catch (error) {
throwDiscoveryFailure(route.pattern, error);
Expand All @@ -418,15 +429,19 @@ function localizePagesPath(
pathname: string,
locale: string | undefined,
i18n: ResolvedNextConfig["i18n"],
explicitLocalePrefix?: string,
): string {
if (explicitLocalePrefix) {
return pathname === "/" ? `/${explicitLocalePrefix}` : `/${explicitLocalePrefix}${pathname}`;
}
if (!i18n || !locale || locale === i18n.defaultLocale) return pathname;
return pathname === "/" ? `/${locale}` : `/${locale}${pathname}`;
}

function extractPagesStaticPathLocale(
url: string,
i18n: NonNullable<ResolvedNextConfig["i18n"]>,
): { locale: string; url: string } {
): { explicitLocalePrefix?: string; locale: string; url: string } {
const queryIndex = url.indexOf("?");
const pathname = queryIndex === -1 ? url : url.slice(0, queryIndex);
const query = queryIndex === -1 ? "" : url.slice(queryIndex);
Expand All @@ -437,7 +452,7 @@ function extractPagesStaticPathLocale(
: undefined;
if (!locale) return extractLocaleFromUrl(url, i18n);
const rest = `/${parts.slice(1).join("/")}`;
return { locale, url: `${rest || "/"}${query}` };
return { explicitLocalePrefix: parts[0], locale, url: `${rest || "/"}${query}` };
}

async function collectAppPaths(options: {
Expand Down Expand Up @@ -473,13 +488,16 @@ async function collectAppPaths(options: {
if (!Array.isArray(value)) {
throw new Error(`generateStaticParams must return an array for ${pattern}.`);
}
return value.map((entry) =>
validateDiscoveredParams(
return value.map((entry) => {
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
throw new Error(`generateStaticParams must return parameter objects for ${pattern}.`);
}
return validateDiscoveredParams(
{ ...params, ...(entry as Record<string, unknown>) },
pattern,
"generateStaticParams",
),
);
);
});
})();
void request.catch(() => staticParamsCache.delete(cacheKey));
staticParamsCache.set(cacheKey, request);
Expand Down
4 changes: 3 additions & 1 deletion packages/vinext/src/server/app-prerender-static-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ function isLazyStaticParamsSource(value: unknown): value is LazyStaticParamsSour
}

function isRootParams(value: unknown): value is RootParams {
return isUnknownRecord(value);
if (!isUnknownRecord(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}

/**
Expand Down
5 changes: 5 additions & 0 deletions tests/app-prerender-static-params.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ describe("createAppPrerenderStaticParamsResolver", () => {
await expect(nonObjectEntry!({ params: {} })).rejects.toThrow(
"generateStaticParams must return an array of objects",
);

const nonPlainEntry = createAppPrerenderStaticParamsResolver([() => [new Date(0)]]);
await expect(nonPlainEntry!({ params: {} })).rejects.toThrow(
"generateStaticParams must return an array of objects",
);
} finally {
if (previous === undefined) delete process.env.__VINEXT_PRERENDER_PATH_DISCOVERY;
else process.env.__VINEXT_PRERENDER_PATH_DISCOVERY = previous;
Expand Down
9 changes: 7 additions & 2 deletions tests/prerender-paths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,7 @@ describe("prerender path manifest", () => {
{ params: { slug: "bonjour" }, locale: "fr" },
"/fr/posts/string-fr",
"/FR/posts/string-fr-upper",
"/en/posts/string-en-explicit",
"/posts/string-en",
],
});
Expand Down Expand Up @@ -729,7 +730,8 @@ describe("prerender path manifest", () => {
"/posts/hello",
"/fr/posts/bonjour",
"/fr/posts/string-fr",
"/fr/posts/string-fr-upper",
"/FR/posts/string-fr-upper",
"/en/posts/string-en-explicit",
"/posts/string-en",
]);
expect(manifest?.pagesPaths).toEqual(manifest?.paths);
Expand All @@ -743,7 +745,8 @@ describe("prerender path manifest", () => {
"/docs/posts/hello/",
"/docs/fr/posts/bonjour/",
"/docs/fr/posts/string-fr/",
"/docs/fr/posts/string-fr-upper/",
"/docs/FR/posts/string-fr-upper/",
"/docs/en/posts/string-en-explicit/",
"/docs/posts/string-en/",
]);
});
Expand Down Expand Up @@ -802,6 +805,8 @@ describe("prerender path manifest", () => {
["an array dynamic param", "/posts/[slug].tsx", { params: { slug: ["a", "b"] } }],
["a scalar catch-all param", "/docs/[...parts].tsx", { params: { parts: "a" } }],
["a query-bearing string path", "/posts/[slug].tsx", "/posts/query?x=1"],
["a relative string path", "/posts/[slug].tsx", "posts/x"],
["a double-slash string path", "/posts/[slug].tsx", "/posts//x"],
])("fails path discovery for getStaticPaths entry with %s", async (_name, file, entry) => {
writeFile("package.json", JSON.stringify({ type: "module" }));
writeFile("dist/server/BUILD_ID", "build-a\n");
Expand Down
Loading