From e458699b647f00970c3f9e199b47405d8dd82e89 Mon Sep 17 00:00:00 2001 From: Hyu Date: Tue, 29 Sep 2026 15:47:55 +0800 Subject: [PATCH] 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/..." 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. --- vite.config.ts | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 49683b9e9..6e618e172 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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(), + ], });