Skip to content

Commit c291b4d

Browse files
frano-mclaude
andcommitted
docs: add consumer setup for next.js 16 pages router (#933)
document the wiring consumers must add when upgrading to next.js 16: - @mui/material-nextjs AppCacheProvider in _app.tsx - documentGetInitialProps + DocumentHeadTags in _document.tsx - @emotion/server peer install - next-mdx-remote@6 blockJS: false note - turbopack opt-out (next dev/build --webpack) until vercel/next.js#82607 is fixed includes a terminology note for static-export deployments: the mui helpers reference "ssr" / "document" but the work runs at next build time, not at request time. no runtime server is involved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 959e9e9 commit c291b4d

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

README.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,134 @@
88
in the form `@databiosphere/findable-ui/lib/<path>`, where `<path>` is the path of the file within the `lib`
99
folder.
1010

11+
## Consumer setup for Next.js 16 (Pages Router, static export)
12+
13+
Consuming apps on Next.js 16 must wire up MUI's Emotion cache helpers in `_app.tsx` and `_document.tsx`. Without this wiring, the browser will report React hydration warnings on components styled by MUI + Emotion (which is most of findable-ui).
14+
15+
### Terminology note
16+
17+
This setup involves a package called `@mui/material-nextjs` and a helper called `documentGetInitialProps`. The naming uses "SSR" / "document" terminology because the package targets Next.js generally, including deployments that do run a runtime HTTP server. **It still applies to static-export deployments.** All the work described below runs at `next build` time, during the same step that generates the static `.html` files. There is no runtime server involved.
18+
19+
When the docs say "extract styles on the server," read it as "extract styles during the static HTML build step."
20+
21+
### Why this is needed
22+
23+
- findable-ui components are styled with MUI + Emotion (CSS-in-JS).
24+
- Emotion generates `css-XXXXXXX` class names by hashing styles in the order they are first encountered during the React render tree walk.
25+
- Next.js builds the static HTML by running the React tree once during `next build`, then the browser runs the same tree again during hydration. For hydration to succeed, both passes must produce the same class names.
26+
- Under Next 16 (Turbopack is the default bundler), the module evaluation order during the build can diverge from the order in the browser, so the same component can be assigned a different hash on each pass. React 19 reports this as a hydration mismatch.
27+
- The fix: capture Emotion's style cache during the build's HTML generation step and inject the resulting `<style>` tags into the static HTML. The browser then hydrates against the exact class names the build wrote.
28+
29+
### Required packages
30+
31+
Install in the consuming project:
32+
33+
```bash
34+
npm install @mui/material-nextjs @emotion/server
35+
```
36+
37+
- `@mui/material-nextjs` — MUI's official integration package; provides the cache provider and the document-head extractor
38+
- `@emotion/server` — peer dependency of the document helper; used during the build to flush Emotion styles into the HTML
39+
40+
### Opt out of Turbopack (required for now)
41+
42+
Next 16 makes Turbopack the default bundler. Turbopack + Pages Router + MUI is currently broken — Emotion class names diverge between the static HTML and the browser hydration pass, producing the exact hydration mismatch this section is meant to fix. The wiring above is still required, but it does not work under Turbopack.
43+
44+
Until the upstream fix lands ([vercel/next.js#82607](https://github.com/vercel/next.js/issues/82607)), pin every `next dev` / `next build` invocation to webpack:
45+
46+
```jsonc
47+
// package.json
48+
"scripts": {
49+
"dev": "next dev --webpack",
50+
"build": "next build --webpack"
51+
}
52+
```
53+
54+
Webpack is still fully supported in Next 16 — Turbopack was promoted to default, not "webpack removed." The deprecation timeline for the webpack fallback hasn't been published.
55+
56+
This is a Next.js / Turbopack bug, not a findable-ui one. Re-enable Turbopack when [vercel/next.js#82607](https://github.com/vercel/next.js/issues/82607) is closed.
57+
58+
### `_app.tsx`
59+
60+
Wrap the app in `AppCacheProvider`:
61+
62+
```tsx
63+
import { EmotionCache } from "@emotion/react";
64+
import { AppCacheProvider } from "@mui/material-nextjs/v16-pagesRouter";
65+
import type { AppProps } from "next/app";
66+
67+
type MyAppProps = AppProps & {
68+
emotionCache?: EmotionCache;
69+
};
70+
71+
function MyApp(props: MyAppProps): JSX.Element {
72+
const { Component, emotionCache, pageProps } = props;
73+
return (
74+
<AppCacheProvider emotionCache={emotionCache}>
75+
{/* existing theme providers and layout */}
76+
<Component {...pageProps} />
77+
</AppCacheProvider>
78+
);
79+
}
80+
```
81+
82+
### `_document.tsx`
83+
84+
Add `DocumentHeadTags` inside `<Head>` and assign `documentGetInitialProps` as the static `getInitialProps`:
85+
86+
```tsx
87+
import {
88+
documentGetInitialProps,
89+
DocumentHeadTags,
90+
DocumentHeadTagsProps,
91+
} from "@mui/material-nextjs/v16-pagesRouter";
92+
import Document, {
93+
DocumentContext,
94+
Head,
95+
Html,
96+
Main,
97+
NextScript,
98+
} from "next/document";
99+
100+
class MyDocument extends Document<DocumentHeadTagsProps> {
101+
render(): JSX.Element {
102+
return (
103+
<Html>
104+
<Head>
105+
<DocumentHeadTags {...this.props} />
106+
{/* other head content */}
107+
</Head>
108+
<body>
109+
<Main />
110+
<NextScript />
111+
</body>
112+
</Html>
113+
);
114+
}
115+
}
116+
117+
MyDocument.getInitialProps = async (ctx: DocumentContext) => {
118+
return await documentGetInitialProps(ctx);
119+
};
120+
121+
export default MyDocument;
122+
```
123+
124+
### Note on `next-mdx-remote@6`
125+
126+
Consumers using `next-mdx-remote` to render MDX content must pass `blockJS: false` to `serialize()`. Version 6 added a `blockJS: true` default that strips all JavaScript expressions from MDX during compilation — including JSX attribute expressions like `<Breadcrumbs breadcrumbs={[...]} />`. With the default, those props are silently dropped at build time and the receiving component renders with no props. Setting `blockJS: false` preserves expression-valued attributes. The narrower `blockDangerousJS: true` setting (default) still blocks `eval` / `new Function` / etc., so the safety net for actually-dangerous patterns is retained.
127+
128+
```ts
129+
import { serialize } from "next-mdx-remote/serialize";
130+
131+
const mdxSource = await serialize(content, {
132+
blockJS: false,
133+
// ...your existing options
134+
});
135+
```
136+
137+
If you use findable-ui's `buildStaticProps` helper (`@databiosphere/findable-ui/lib/utils/mdx/staticGeneration/staticProps`), `blockJS: false` is already the default — no consumer change needed.
138+
11139
## Developing findable-ui alongside a consuming app
12140

13141
Use `scripts/link.sh` to build and install a local copy of findable-ui

0 commit comments

Comments
 (0)