Skip to content

perf(web): re-enable Turbopack dev — drop dead MDX JS plugin chain#1780

Merged
earayu merged 1 commit into
mainfrom
fix/web-dev-enable-turbopack
Apr 28, 2026
Merged

perf(web): re-enable Turbopack dev — drop dead MDX JS plugin chain#1780
earayu merged 1 commit into
mainfrom
fix/web-dev-enable-turbopack

Conversation

@earayu
Copy link
Copy Markdown
Collaborator

@earayu earayu commented Apr 28, 2026

Summary

next dev --turbopack is 10–50× faster than the Webpack default for HMR + per-route first-compile. PR #1667 disabled it because the inline containerDirective remark plugin in next.config.ts was a closure that Turbopack's worker pool cannot serialize. Empirically the entire JS plugin chain has the same problem — even after the inline closure is hoisted to its own module, next dev --turbopack still aborts with:

Error: loader /node_modules/@next/mdx/mdx-js-loader.js for match "#next-mdx" does not have serializable options.

The unusual trade-off here: this plugin chain is dead code at compile time. There are zero .mdx pages in web/src/** (verified via find + grep). All runtime markdown rendering happens through web/src/components/markdown.tsx, which keeps its own React-side react-markdown pipeline (gfm, highlight, directives, frontmatter, headerId) — that path is untouched.

So: drop the JS plugin chain from createMDXPlugin. The withNextMDX wrapper is kept (so mdx-components.tsx stays wired) and pageExtensions keeps 'mdx' (so the route resolver still recognises the extension). When .mdx pages are actually introduced, the recommended path is experimental.mdxRs: true (Rust-based, Turbopack-compatible) — comment in next.config.ts updated.

Re-enable the flag in web/package.json:dev. Local verification: ./node_modules/.bin/next dev --turbopack reaches "Ready" in 1.5 s with zero loader-serialization errors; yarn tsc --noEmit reports zero errors.

§K.12 invariant cross-check

Largely n/a (frontend dev tooling). Direct hit:

4-pattern pre-check matrix

  • Pattern 1 v1 (.mdx page consumers): `find web/src -name '.mdx'` returns zero. `rg "from '[^']\.mdx'" web/src` returns zero. Confirms the JS plugin chain has no live consumers.
  • Pattern 1 v2 (runtime markdown plugin chain): unchanged in components/markdown.tsx. Removing JS plugins from next.config.ts does not affect document rendering — react-markdown imports its own copies of remarkGfm / rehypeHighlight / etc. directly.
  • Pattern 2 (response-shape change list): n/a, this PR does not touch any backend or generated schema.
  • Pattern 3 (additive helpers): n/a, dev-tooling only.

simple-stable directive 4-guardrail

  • feat/frontend #1 不无限扩范围 — 2 files / +16 / -55. Did not extract plugins to modules (would be future-only dead code given zero .mdx files); did not switch to experimental.mdxRs (kept current MDX setup empty-but-present so future adopters re-add plugins explicitly).
  • feat: auth bearer token support #2 尽快上线 — earayu2 directive: edit code → instant reload, no added manual burden. Turbopack ready in 1.5 s, HMR kicks in automatically; dev script still runs yarn install && yarn i18n:sync so engineers who pull dependency updates do not need any extra command.
  • feat: api test #3 简单稳定 — fewer plugins is fewer moving parts. The runtime markdown path keeps the production-tested chain.
  • fix: upload token #4 私有化部署免维护 — operator-invisible; production builds unaffected (zero .mdx files means next build output is identical before/after).

Test plan

  • next dev --turbopack reaches "Ready in 1477ms" cleanly (no loader-serialization error)
  • yarn tsc --noEmit → 0 errors
  • find web/src -name '*.mdx' → 0 files (confirms plugin chain was dead code)
  • rg \"from '[^']*\\.mdx'\" web/src → 0 imports (confirms no consumer)
  • Manual: pull this branch + yarn dev + verify HMR feels instant on edit (deferred to reviewer-side; my session has no live dev stack running)

🤖 Generated with Claude Code

`next dev --turbopack` is 10–50× faster than the Webpack default for
HMR + per-route first-compile. PR #1667 disabled it because the inline
remark `containerDirective` plugin in `next.config.ts` was a closure
that Turbopack's worker pool cannot serialize. Empirically the entire
JS plugin chain has the same problem — even after the inline closure
is hoisted to its own module, `next dev --turbopack` still aborts with

  Error: loader /node_modules/@next/mdx/mdx-js-loader.js for match
  "#next-mdx" does not have serializable options.

The trade-off here is unusual: this plugin chain is **dead code at
compile time**. There are zero `.mdx` pages in `web/src/**` (verified
via `find` + `grep`). All runtime markdown rendering happens through
`web/src/components/markdown.tsx`, which keeps its own React-side
`react-markdown` pipeline (gfm, highlight, directives, frontmatter,
headerId) — that path is untouched.

So: drop the JS plugin chain from `createMDXPlugin`. The `withNextMDX`
wrapper is kept (so `mdx-components.tsx` stays wired) and
`pageExtensions` keeps `'mdx'` (so the route resolver still recognises
the extension). When `.mdx` pages are actually introduced, the
recommended path is `experimental.mdxRs: true` (Rust-based, Turbopack-
compatible) — see updated comment in `next.config.ts` for context.

Re-enable the flag in `web/package.json:dev`. Local verification:
`./node_modules/.bin/next dev --turbopack` reaches "Ready" in 1.5 s
with zero loader-serialization errors; `yarn tsc --noEmit` reports
zero errors.

§K.12 invariant cross-check
============================
Largely n/a (frontend dev tooling). Direct hit:
- #12 (grep-zero LightRAG) — `rg -i lightrag web/src/` returns zero
  hits both before and after.

4-pattern pre-check matrix
==========================
- Pattern 1 v1 (`.mdx` page consumers): `find web/src -name '*.mdx'`
  returns zero. `rg "from '[^']*\\.mdx'" web/src` returns zero.
  Confirms the JS plugin chain has no live consumers.
- Pattern 1 v2 (runtime markdown plugin chain): unchanged in
  `components/markdown.tsx`. Removing JS plugins from `next.config.ts`
  does not affect document rendering — react-markdown imports its own
  copy of remarkGfm / rehypeHighlight / etc. directly.
- Pattern 2 (response-shape change list): n/a, this PR does not touch
  any backend or generated schema.
- Pattern 3 (additive helpers): n/a, dev-tooling only.

simple-stable directive 4-guardrail
===================================
- #1 不无限扩范围 — 2 files / +16 / -55. Did not extract plugins to
  modules (would be future-only dead code given zero `.mdx` files);
  did not switch to `experimental.mdxRs` (kept current MDX setup
  empty-but-present so future adopters re-add plugins explicitly).
- #2 尽快上线 — earayu2 directive: edit code → instant reload, no
  added manual burden. Turbopack ready in 1.5 s, HMR kicks in
  automatically; `dev` script still runs `yarn install && yarn
  i18n:sync` so engineers who pull dependency updates do not need
  any extra command.
- #3 简单稳定 — fewer plugins is fewer moving parts. The runtime
  markdown path keeps the production-tested chain.
- #4 私有化部署免维护 — operator-invisible; production builds
  unaffected (zero `.mdx` files means `next build` output is identical
  before/after).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@earayu earayu merged commit e1679c0 into main Apr 28, 2026
10 checks passed
@earayu earayu deleted the fix/web-dev-enable-turbopack branch April 28, 2026 06:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant