fix: serve the public routes the router declares, not the slugs the build saw
The serving contract enumerated every public path the bundled fixture happened to contain, and the generated nginx published exactly those as `location =` blocks. A record published after the build — the entire point of having a backend — answered 404 at the edge before the SPA was ever asked, and no amount of correct routing inside the bundle could recover it. Twenty-seven frozen paths, and any twenty-eighth was unreachable. The route contract already declares which paths exist; the catalog only decides which of them currently resolve, and that is the SPA's call rather than the web server's. So the contract now emits one regex per registered Public route, derived from the router, the way the Studio half has always worked. A parameter matches one segment and never a slash, so /cases/a/b stays a 404 instead of quietly rendering a case page. The catch-all route is dropped rather than translated: serving index.html for every unmatched URL would turn an edge 404 into a soft 200 and hide broken links from crawlers and from us. Verified against a built image: /cases/a-brand-new-slug now answers 200 while /nope and /cases/a/b still answer 404. schemaVersion goes to 2 because the field changed shape, not just contents — a consumer reading publicSpaPaths would otherwise see an absent key rather than a version it can refuse.
This commit is contained in:
@@ -27,7 +27,7 @@ const DIST = "dist";
|
||||
const OUT = path.join(DIST, "nginx.conf");
|
||||
|
||||
type ServingContract = Readonly<{
|
||||
publicSpaPaths: readonly string[];
|
||||
publicSpaPathPatterns: readonly string[];
|
||||
studioPathPrefix: string;
|
||||
studioSpaPathPatterns: readonly string[];
|
||||
notFound: Readonly<{ status: number; contentType: string; body: string }>;
|
||||
@@ -115,9 +115,12 @@ async function main(): Promise<void> {
|
||||
.replace(/^charset=/i, "")
|
||||
.toLowerCase();
|
||||
|
||||
const publicLocations = contract.publicSpaPaths
|
||||
// Regex locations now, matching the Studio half: the contract declares which
|
||||
// paths the router serves, not which ones the fixture happened to contain, so
|
||||
// a record published after this build is served instead of 404ed at the edge.
|
||||
const publicLocations = contract.publicSpaPathPatterns
|
||||
.map(
|
||||
(pathname) => ` location = ${basePath}${exactLocation(pathname)} {
|
||||
(pattern: string) => ` location ~ ^${basePath}${studioRegex(pattern).slice(1)} {
|
||||
${secure}
|
||||
add_header Cache-Control "${indexCache}" always;
|
||||
try_files /index.html =404;
|
||||
@@ -220,7 +223,7 @@ ${studioLocations}
|
||||
|
||||
await writeFile(OUT, conf, "utf8");
|
||||
process.stdout.write(
|
||||
`nginx config: ${OUT} (${contract.publicSpaPaths.length} public routes, ` +
|
||||
`nginx config: ${OUT} (${contract.publicSpaPathPatterns.length} public routes, ` +
|
||||
`${contract.studioSpaPathPatterns.length} studio patterns)\n`,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user