Migrate documentation from VuePress 2 to Fumadocs - #144
Conversation
VuePress 2 → Fumadocs: before / afterAll figures measured on the same machine (Apple silicon, 8 build workers), cold caches, Build
The old build spent 96 % of its time rendering pages one at a time. The new one prerenders 1253 routes
What a reader actually downloadsNeither the old nor the new nginx config had gzip enabled. This PR turns it on.
Raw page size did grow (137 KB → 294 KB) because Fumadocs inlines the navigation tree and the RSC Disk / image footprint — this got worse
The output growth is Next's per-route RSC prefetch payloads (4374 Open decision: cutting this needs the Fumadocs root folder feature so the sidebar renders only Tooling
Behaviour changes worth knowing
Fixed in passing
|
Content preservation: verificationEvery Each side is reduced to a canonical form that strips only the syntax this migration was supposed to Inventory
Two paths changed deliberately: Automated comparison
Code blocks are the strongest signal for this project — every Every flagged difference, classifiedProse — 69 pages
Headings — 6 pages
The Reproducing thisgit worktree add /tmp/old-site main
cd /tmp/old-site && npm ci --legacy-peer-deps
node verify-content.mjs /tmp/old-site/guide ./content/docsConclusionNo page was lost, added, or silently rewritten. Every difference the checker found is either a
Not addressed hereThree duplicate pages predating this migration were left untouched:
|
VuePress 2 never shipped a stable release; the site was pinned to 2.0.0-rc.31. This moves the docs to Fumadocs 16 on Next.js 16, exported as a static build so the existing nginx container still serves it. Every one of the 624 page URLs is preserved. Function pages live under a `content/docs/(functions)/` folder group, which nests them under a "Functions" heading in the sidebar without appearing in the URL, so `/Member/kick` stays `/Member/kick`. Content: - 624 .md -> .mdx, with an H1 -> frontmatter `title` pass - `::: tip|warning|danger|info` -> <Callout>, `::: details` -> <Accordion> - Vue attribute bindings, unclosed void tags, bare braces and stray `<` fixed for MDX's stricter parser - @discord-message-components/vue (unmaintained since 2022) replaced with @skyra/discord-components-react; these are web components and render client-side only Build pipeline: - the three VuePress markdown replacers are now remark plugins (cooldown sections, $function auto-linking, cached remote images) - sidebar.js weight ordering translated into 38 meta.json files - search moves to Fumadocs' static Orama index, still fed the Badge-derived tags the old parseTags.js supplied - docs-pages.json is regenerated post-build, keeping its published shape Also fixed along the way: - $modal/$channel/$poll/$button auto-linked to Trigger pages instead of the functions, because the old name->path scan was last-wins - three pages with unclosed fences or duplicated container openers that were already rendering incorrectly - nginx served everything uncompressed; gzip cuts a page from 294 KB to 28 KB and the search index from 8.6 MB to 1.4 MB - "/" 301-redirected to "/index" Tooling moves from npm to pnpm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
next/image requires intrinsic dimensions. remarkImage supplies them for local files by rewriting the path into a static import, but external URLs are only sized by fetching them at build time, which source.config.ts disables because many linked Discord CDN attachments are dead. Those nodes keep a plain string src with no width or height. next/image validates this only outside production, so `next build` rendered the images while `next dev` returned a 500 on all 70 pages holding one -- and production was shipping <img> tags with no dimensions, causing layout shift on all 260 remote images. Render those as a plain <img>, which is what the VuePress site served for them anyway. Separately, `collapsible: false` is unusable in fumadocs 16.14.0: the docs layout passes className as a render-prop function, which the shared SidebarFolderTrigger forwards to a bare <div> when a folder isn't collapsible. React drops non-string className values, so the Other and Legal folder headers rendered with no styling at all. defaultOpen alone keeps them expanded and correctly styled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9257ae5 to
ea61d44
Compare
Add shiki numbers
Some changes
VuePress 2 never shipped a stable release; the site was pinned to 2.0.0-rc.31. This moves the docs to Fumadocs 16 on Next.js 16, exported as a static build so the existing nginx container still serves it.
Every one of the 624 page URLs is preserved. Function pages live under a
content/docs/(functions)/folder group, which nests them under a "Functions" heading in the sidebar without appearing in the URL, so/Member/kickstays/Member/kick.Content:
titlepass::: tip|warning|danger|info-> ,::: details-><fixed for MDX's stricter parserBuild pipeline:
Also fixed along the way:
Tooling moves from npm to pnpm.