Skip to content

Commit e1679c0

Browse files
earayuclaude
andauthored
perf(web): re-enable Turbopack dev — drop dead MDX JS plugin chain (#1780)
`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>
1 parent e7eef9a commit e1679c0

2 files changed

Lines changed: 16 additions & 55 deletions

File tree

web/next.config.ts

Lines changed: 15 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
import createMDXPlugin from '@next/mdx';
2-
import { h } from 'hastscript';
32
import type { NextConfig } from 'next';
43
import createNextIntlPlugin from 'next-intl/plugin';
5-
import remarkDirective from 'remark-directive';
6-
import remarkFrontmatter from 'remark-frontmatter';
7-
import remarkGfm from 'remark-gfm';
8-
import remarkGithubAdmonitionsToDirectives from 'remark-github-admonitions-to-directives';
9-
import remarkHeaderId from 'remark-heading-id';
10-
import { visit } from 'unist-util-visit';
11-
// import remarkMdxFrontmatter from "remark-mdx-frontmatter";
12-
// import rehypeToc from '@jsdevtools/rehype-toc';
13-
import rehypeHighlight from 'rehype-highlight';
14-
15-
// import rehypeHighlightLines from 'rehype-highlight-code-lines';
164

175
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
186

@@ -57,51 +45,24 @@ const withNextIntl = createNextIntlPlugin({
5745
/**
5846
* https://nextjs.org/docs/app/guides/mdx
5947
* https://github.com/vercel/next.js/issues/71819#issuecomment-2461802968
48+
*
49+
* No JS remark/rehype plugins are wired here on purpose: there are
50+
* currently zero `.mdx` pages in the tree, so any plugin chain would be
51+
* dead code at compile time. Runtime markdown rendering goes through
52+
* `web/src/components/markdown.tsx`, which keeps its own React-side
53+
* `react-markdown` plugin pipeline (gfm, highlight, directives,
54+
* frontmatter, headerId).
55+
*
56+
* Keeping the chain empty makes the loader options worker-serializable,
57+
* which is what `next dev --turbopack` requires (custom inline closures
58+
* break the Turbopack worker pool with "loader options are not
59+
* serializable"). If `.mdx` pages are introduced later, prefer
60+
* `experimental.mdxRs: true` (Rust-based MDX) for Turbopack
61+
* compatibility, or extract any JS plugins into their own modules so
62+
* each one can be passed as an importable reference.
6063
*/
6164
const withNextMDX = createMDXPlugin({
62-
// Add markdown plugins here, as desired
6365
extension: /\.(md|mdx)$/,
64-
options: {
65-
remarkPlugins: [
66-
remarkGfm,
67-
remarkFrontmatter,
68-
// remarkMdxFrontmatter,
69-
remarkGithubAdmonitionsToDirectives,
70-
remarkDirective,
71-
() => {
72-
return (tree) => {
73-
visit(tree, (node) => {
74-
if (node.type === 'containerDirective') {
75-
const data = node.data || (node.data = {});
76-
const tagName = 'div';
77-
data.hName = tagName;
78-
data.hProperties = h(tagName, {
79-
...node.attributes,
80-
class: node.name,
81-
}).properties;
82-
}
83-
});
84-
};
85-
},
86-
[
87-
remarkHeaderId,
88-
{
89-
defaults: true,
90-
},
91-
],
92-
],
93-
rehypePlugins: [
94-
rehypeHighlight,
95-
// rehypeHighlightLines,
96-
// [
97-
// rehypeToc,
98-
// {
99-
// // position: 'beforebegin', // "beforebegin" | "afterbegin" | "beforeend" | "afterend"
100-
// headings: ['h2', 'h3', 'h4', 'h5', 'h6'],
101-
// },
102-
// ],
103-
],
104-
},
10566
});
10667

10768
export default withNextMDX(withNextIntl(nextConfig));

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"api:build": "yarn api:v2:types",
77
"api:v2:types": "mkdir -p ./src/api-v2 && openapi-typescript ../openapi.public.json -o ./src/api-v2/schema.d.ts",
88
"build": "yarn install && yarn i18n:sync && next build && ts-node scripts/build.mjs",
9-
"dev": "cp ./deploy/env.local.template ./.env && yarn install && yarn i18n:sync && next dev",
9+
"dev": "cp ./deploy/env.local.template ./.env && yarn install && yarn i18n:sync && next dev --turbopack",
1010
"i18n:sync": "node scripts/i18n-watch.mjs",
1111
"lint": "next lint",
1212
"prettier": "prettier --cache --write ./ && yarn lint",

0 commit comments

Comments
 (0)