fix(docs): prefix base-path assets in the dev server

The dev server mounts the site at basePath "/docs/" but never runs the
prefixing that the production build performs in
scripts/finalize-base-path.mjs. Any asset referenced as a literal
root-absolute URL therefore 404s locally: icons configured in docs.json
(navigation pages[].icon) and the brand logo in press.config.tsx.

Markdown images are unaffected, because the asset pipeline rewrites
those itself ("/docs/public/..." in dev, "/docs/assets/<hash>..." in the
build), which is why broken icons and working screenshots appeared on
the same page.

Add a serve-only Vite middleware that rewrites the unprefixed forms to
the "/docs/..." mount point. The rule set deliberately mirrors
finalize-base-path.mjs exactly, so a URL that breaks locally also breaks
in production rather than being masked by the dev server.

Verified: 60 images across four pages in three locales load with no
broken requests (the guide page alone went from 17 broken to 0),
tsc --noEmit passes, and npm run build still succeeds with the static
suite at 31/31 and no changes to generated tracked files.
This commit is contained in:
Hyu
2026-09-29 15:47:55 +08:00
parent 147c3d8deb
commit e458699b64
+40 -2
View File
@@ -1,8 +1,46 @@
import tailwindcss from "@tailwindcss/vite";
import press from "fumapress/vite";
import { fumadocsMdx } from "fumadocs-mdx/vite";
import { defineConfig } from "vite";
import { defineConfig, type Plugin } from "vite";
/**
* Mirror the base-path prefixing that the production build applies.
*
* `npm run build` finishes with `scripts/finalize-base-path.mjs`, which rewrites
* root-absolute asset URLs in the generated output so they resolve under the
* docs mount point inside langbot-landing-page (https://langbot.app/docs/...).
*
* The dev server serves the same content under `basePath: "/docs/"` but never
* runs that step, so those URLs 404 locally. Only assets that survive as literal
* root-absolute strings are affected: icons referenced from `docs.json` and the
* brand logo in `press.config.tsx`. Markdown images are unaffected because the
* asset pipeline rewrites them itself.
*
* The rule set intentionally mirrors `scripts/finalize-base-path.mjs` exactly, so
* a URL that is broken locally is broken in production too, and vice versa.
*/
const prefixBasePathAssets = (): Plugin => ({
name: "docs-dev-base-path-assets",
apply: "serve",
configureServer(server) {
server.middlewares.use((req, _res, next) => {
const url = req.url ?? "";
const isUnprefixedAsset =
url.startsWith("/images/") ||
url.startsWith("/favicon.ico") ||
url.startsWith("/langbot-logo.png") ||
url.startsWith("/rss.xml");
if (isUnprefixedAsset) req.url = `/docs${url}`;
next();
});
},
});
export default defineConfig({
plugins: [press({ basePath: "/docs/" }), fumadocsMdx(), tailwindcss()],
plugins: [
prefixBasePathAssets(),
press({ basePath: "/docs/" }),
fumadocsMdx(),
tailwindcss(),
],
});