perf(docs): stop shipping translated docs markdown in the serverless function - #3387
Conversation
…tches The share route's fix left three siblings with the same shape: a useState initializer reading sessionStorage, so the first client render disagrees with the server's, React discards the hydrated tree, and the page re-renders from scratch - blank for a returning viewer. - clips embed.$shareId: the embedded player, same saved-password read - clips + slides access-request.approve: the saved approval token Each starts from the value the server can also compute and adopts the stored one after mount.
…function
docs-content.ts globs core/docs/content/locales/*/*.md{,x} with query:"?raw",
so Vite emits one lazy chunk per translated file - 1251 of them. Netlify's
nodeBundler:"none" + includedFiles:["**"] ships every one and the function
unzips all of it on each cold start.
It can never serve them: every localized docs page is prerendered, so the CDN
answers those URLs from the publish directory and the render function is never
invoked. The globs stay - their KEYS still drive localizedDocKey and the
locale-availability enumeration, so hreflang and the locale switcher are
unaffected.
Measured: docs server function 51MB -> 30MB, 1856 -> 615 chunks, with all 193
English markdown chunks intact.
The pruner refuses to delete a chunk it cannot prove is prerendered, and skips
redirect and draft slugs, so a page the function is still the only renderer for
aborts the build instead of 500ing in production.
This comment has been minimized.
This comment has been minimized.
|
Here's a visual recap of what changed: Open the full interactive recap |
There was a problem hiding this comment.
Builder reviewed your changes and found 1 potential issue 🟡
Review Details
Code Review Summary
PR #3387 adds a post-build pruner that removes locale-only translated Markdown chunks from emitted docs serverless functions, while retaining the glob keys used for locale availability and hreflang behavior. It also defers sessionStorage reads in Clips and Slides approval/embed routes until after mount to avoid SSR hydration mismatches. The overall build strategy is sound: attribution is based on the emitted Vite loader map, English/shared chunks are protected, and the script refuses to prune when a static page cannot be proven.
Risk assessment: Standard (build/runtime behavior and user-facing route state changes).
Key Findings
- 🟡 MEDIUM: Draft detection only inspects localized frontmatter; canonical English drafts can make the script abort despite their translations not being prerendered.
- 🟢 The hydration changes preserve URL-token precedence and make unavailable storage visible as the locked/token-required state.
- 🟢 Tests cover locale-only attribution, shared chunks, and missing/present prerender outputs.
🧪 Browser testing: Will run after this review (PR touches UI code)
| // A redirect slug never renders its own page, and a draft is not | ||
| // prerendered; both keep their chunk rather than gamble on the 301/500. | ||
| if ( | ||
| keys.some((key) => redirectSlugs.has(slugFromKey(key)) || isDraft(key)) |
There was a problem hiding this comment.
🟡 Draft check misses canonical drafts when the translation lacks frontmatter
buildPrerenderPaths excludes every locale page whose canonical document is a draft, but this pruner checks only the localized source file. If the English source is marked draft: true while a translation lacks matching frontmatter, its page is not prerendered and this code still adds the chunk to provenKeys, causing the build to abort instead of retaining the chunk. Derive draft status from the canonical source slug as the prerenderer does, and add coverage for this mismatch.
Five cuts, each measured on the netlify preset: - @xterm/* is stubbed out of the SSR graph by default in core, not repeated in sixteen vite configs. It is only reachable through a React.lazy boundary whose module body guards on typeof window === undefined, so the server can never import it and the chunk was pure unpack weight everywhere. - formatExtensionHtml (and its slides twin) load prettier/standalone plus the four plugins the HTML printer reaches. prettier's main entry import()s all 13 parser plugins, so bundlers inlined ~3.5MB of flow/typescript/yaml/markdown parsers to format HTML. - docs stubs the tiptap/ProseMirror/assistant-ui editor stack: it never server-renders the agent sidebar or resource editor. lowlight and yjs stay real - both genuinely run on the server. - the docs corpus glob map is declared once instead of twice, so Rollup stops emitting two byte-identical copies of a 1447-entry lazy-import map. Measured: calendar 46.7MB -> 21.1MB, docs 51MB -> 26MB (76.8MB at the start of this work).
The background and integration-recovery clones overwrite url.pathname unconditionally before delegating to main.mjs, so they can never route to the SSR page/asset handlers they inherited from the server bundle. Netlify zips and uploads every function separately, so that island was paid for on every deploy. The pruner walks the clone's real import graph - including rolldown's backtick dynamic imports, which a quote-only scan under-reports by tens of MB - and refuses to prune anything when a relative dynamic import cannot be resolved statically. It also asserts the entry actually rewrites url.pathname, which caught this change being wired into the recurring-jobs emit by mistake. Measured on calendar: total upload 42.2MB -> 35.8MB.
There was a problem hiding this comment.
Builder reviewed your changes — no new findings
Review Details
Incremental Code Review Summary
The latest PR head expands the payload optimization beyond locale pruning: docs now share one lazy glob-loader map, the docs build stubs client-only editor dependencies, core defaults selected xterm packages to SSR stubs, and HTML formatting uses Prettier standalone with the required plugins. The existing Clips/Slides hydration fix remains unchanged. The approach is sound, and the returned review validation found no new confirmed regressions: localization/pruning tests, core Vite tests, and a standalone HTML-formatting smoke test passed.
Risk assessment: Standard. The previously reported canonical-draft pruning issue remains open and was intentionally not reposted. No new actionable findings were identified in this incremental review.
🧪 Browser testing: Will run after this review (PR touches UI code)

51MB → 30MB on the docs server function (1856 → 615 chunks).
docs-content.tsglobscore/docs/content/locales/*/*.md{,x}withquery: "?raw", so Vite emits one lazy chunk per translated file — 1251 of them. Netlify'snodeBundler: "none"+includedFiles: ["**"]ships every one, and the function unzips all of it on every cold start.It can never serve them. Every localized docs page is prerendered, so the CDN answers those URLs from the publish directory and the render function is never invoked for them. Measured: 124 prerendered pages per locale against 125 sources, the single gap being the
databaseredirect slug.The globs stay — their keys still drive
localizedDocKeyand the locale-availability enumeration, so hreflang and the locale switcher are unaffected. All 193 English markdown chunks remain.Safety: the pruner attributes chunks by parsing the glob map Vite emitted into the bundle itself, so it cannot drift from what was actually built. It skips redirect and draft slugs, and aborts the build if any chunk it would delete has no prerendered page — a translated doc the function is still the only renderer for fails loudly rather than 500ing in production. 4 tests cover locale-only attribution, the shared-with-English case, and both sides of the prerender assertion.
Found via a 42-agent teardown of why a docs site needs a 50MB function.
🤖 Generated with Claude Code