Skip to content

chore: upgrade to Nextra 4 + Next 15 + React 19 (App Router)#43

Merged
zxch3n merged 5 commits into
mainfrom
chore/upgrade-nextra-and-nextjs
Apr 28, 2026
Merged

chore: upgrade to Nextra 4 + Next 15 + React 19 (App Router)#43
zxch3n merged 5 commits into
mainfrom
chore/upgrade-nextra-and-nextjs

Conversation

@lodystage
Copy link
Copy Markdown
Contributor

@lodystage lodystage Bot commented Apr 27, 2026

Summary

  • Migrates the docs site from Nextra 3 / Next 14 / Pages Router to Nextra 4.6 / Next 15.5 / React 19. Nextra 4 only ships an App Router runtime, so this is a forced framework migration, not a drop-in version bump.
  • pages/content/, new app/[[...mdxPath]]/page.tsx catch-all renders MDX through Nextra's importPage.
  • theme.config.jsx + pages/_app.tsx collapsed into app/layout.tsx (Layout/Navbar/Footer as JSX props, head → Metadata API; GA / Clarity / Umami stay as <Script>).

What changed

  • Routing: pages/api/raw-mdx.tsapp/api/raw-mdx/route.ts; _meta.js updated for Nextra 4's stricter Zod schema (theme.layout: "raw""full" + sidebar/toc/footer: false, dropped collapsible).
  • Blog / changelog index: rewritten to use getPageMap + normalizePages (replaces the removed getPagesUnderRoute).
  • CSS: imports nextra-theme-docs/style-prefixed.css so the theme's Tailwind v4 layers don't clash with this project's Tailwind v3 PostCSS pipeline. tailwind.config.js scans content/ + app/, with a safelist for responsive grid-cols variants Nextra's MDX loader otherwise misses.
  • Components: LanguageSelector switched from next/router to next/navigation; components touching window/state marked "use client" (richtextDemo, video, landing Footer, etc.).
  • Types: React 19 dropped the global JSX namespace; custom-types.d.ts re-aliases it so existing JSX.Element annotations keep type-checking. tsconfig.json now uses moduleResolution: "bundler".
  • Build glue: next.config.mjs drops theme/themeConfig, simplifies the SVG rule for Webpack 5, keeps WASM + redirects. gen-rss.js reads from content/ instead of pages/.

Test plan

  • pnpm build (with NODE_OPTIONS=--max-old-space-size=8192) — generates 72 static pages cleanly
  • pnpm start + Playwright screenshots of 28 routes at desktop and mobile (landing, docs/tutorial, docs/concepts, docs/api/js, blog index + posts, changelog, about) — routing, navigation, sidebar, and visual layout match the previous build
  • Pre-existing 404s (/docs/api, /docs/advanced, /blog/v1) still 404 — no accidental new routing
  • Reviewer: spot-check sidebar collapse, dark/light toggle, language dropdown, and blog/changelog ordering on a deploy preview
  • Reviewer: confirm pnpm test (Deno code-block runner) is unaffected

Notes for reviewers

  • Dev mode (pnpm dev) needs NODE_OPTIONS=--max-old-space-size=8192: the catch-all route globs every content/*.mdx, which transitively pulls in loro-crdt's WASM via the richtext demo; production build is unaffected.
  • Tailwind v3 was kept (rather than upgrading to v4) because the landing page has substantial custom Tailwind v3 styling. The style-prefixed.css import is Nextra's documented escape hatch for v3 consumers (nextra#3935).

Migrate the docs site from Nextra 3 / Next 14 / Pages Router to the
current Nextra 4 / Next 15 / React 19 stack. Nextra 4 only ships an
App Router runtime, so this is a forced framework migration, not a
drop-in version bump.

Key changes:
- pages/ moved to content/; new app/[[...mdxPath]]/page.tsx catch-all
  uses Nextra's importPage to render MDX through the docs theme wrapper
- app/layout.tsx replaces theme.config.jsx and pages/_app.tsx —
  Layout/Navbar/Footer are configured as JSX, head tags become
  Next.js Metadata API entries, GA / Clarity / Umami stay as <Script>
- pages/api/raw-mdx.ts ported to app/api/raw-mdx/route.ts (route handler)
- nextra-theme-docs/style-prefixed.css imported instead of style.css so
  the theme's Tailwind v4 layers don't clash with this project's
  Tailwind v3 PostCSS pipeline
- _meta.js entries updated for Nextra 4's stricter Zod schema:
  theme.layout "raw" → "full" (plus sidebar/toc/footer false on the
  landing page) and the unsupported `collapsible` key dropped
- content/blog.mdx and content/changelog.mdx use getPageMap +
  normalizePages instead of the removed getPagesUnderRoute
- LanguageSelector switched from next/router to next/navigation;
  components touching window/state marked "use client" (richtextDemo,
  video, Footer landing, etc.)
- React 19 dropped the global JSX namespace; custom-types.d.ts re-aliases
  it so existing JSX.Element annotations keep type-checking
- next.config.mjs drops the theme/themeConfig keys, simplifies the SVG
  rule for Webpack 5, keeps WASM + redirects
- tailwind.config.js scans content/ + app/, plus a safelist for
  responsive grid-cols variants Nextra's MDX loader was missing
- gen-rss.js reads from content/ instead of pages/

Verified visually with Playwright screenshots of 28 routes at desktop
+ mobile against the previous build; routing, navigation, and layout
match the original.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 27, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loro-docs 96d531e Commit Preview URL

Branch Preview URL
Apr 28 2026, 02:00 AM

zxch3n added 4 commits April 27, 2026 20:26
…yout

Pixel-diff against the pre-migration screenshots surfaced two regressions
introduced by the Nextra 3 → 4 jump:

1. **MDX heading/code styles flattened.** Tailwind v3's preflight emits
   `h1,h2,h3,h4,h5,h6 { font-size: inherit; font-weight: inherit }` outside
   any `@layer`, and unlayered rules outrank Nextra 4's layered
   `.x:text-4xl` / `.x:font-bold` defaults from `style-prefixed.css`. Result:
   every heading on every docs page rendered at 16px / weight 400. Fix:
   disable Tailwind v3's preflight via `corePlugins.preflight = false` and
   re-add only the box-sizing + button reset bits the landing page actually
   needs, scoped to `.landing-page-root`.

2. **/blog, /changelog, /about showed the docs sidebar + a centered
   "Copy page" button + center-aligned headings.** In Nextra 3 these were
   plain centred pages; Nextra 4 always renders the docs sidebar for nested
   page-type routes and `theme.sidebar = false` in `_meta.js` does not
   propagate through the catch-all once we collapse `content/blog.mdx` into
   `content/blog/index.mdx`. Fix: a small `RouteBodyClass` client component
   sets `<body data-layout="plain">` for these routes, and `style.css` has
   targeted overrides that hide the sidebar / TOC / Copy-page button and
   left-align headings on those pages only. Docs pages (`data-layout="docs"`)
   are untouched.

Also moved `content/blog.mdx` → `content/blog/index.mdx` and
`content/changelog.mdx` → `content/changelog/index.mdx` to remove the
file/folder ambiguity that made Nextra's normalize-pages assign
`activeType: "doc"` to the route.

Verified with Playwright across 28 routes (desktop + mobile): headings,
code blocks, sidebar visibility, copy-page button, footer, and overall
layout match the original Nextra 3 build.
A second pass over the screenshots surfaced two more regressions:

1. **Individual blog/changelog posts lost sidebar + TOC.** The previous
   commit hid the docs sidebar on every `/blog/*` and `/changelog/*` route,
   but the Nextra 3 build only stripped them on the index pages — child
   posts kept the standard docs layout with a post-list sidebar and an
   "On This Page" TOC. Fix: tighten `RouteBodyClass`'s plain-layout regex
   to match `/blog`, `/changelog`, `/about` exactly (no children), and
   drop the `theme.sidebar=false` overrides from `content/_meta.js` for
   blog/changelog so per-post `_meta.js` entries can re-enable TOC.

2. **Docs pages showed Nextra 4's "Copy page" button.** The original
   Nextra 3 layout never had this. Disabled globally via
   `<Layout copyPageButton={false}>`.

Also constrains article max-width to 720px on plain-layout pages so
`/about` content matches the narrower column the Nextra 3 page-type
routes used.
Cloudflare Pages CI ran the default `pnpm run build` with Node's stock
~2GB heap and OOM'd during `next build` (Mark-Compact thrashing). The
Nextra 4 catch-all route triples the per-route memory cost vs Nextra 3
because the same MDX module graph is built for every entry under
`/[[...mdxPath]]`, which pushed the total over the limit.

Wrap both `gen-rss.js` and `next build` (plus `next dev`) with
`NODE_OPTIONS=--max-old-space-size=8192` so CI hosts and contributors
get the same headroom that local builds already had.
The Cloudflare Pages deploy step (`npx wrangler versions upload`) expects
`.open-next/worker.js` to exist after the build, but our `pnpm build`
only ran `next build` + `next-sitemap` — it never invoked
`opennextjs-cloudflare build`, so the worker bundle was missing and CF
failed with `The entry-point file at ".open-next/worker.js" was not
found.`

- Chain `opennextjs-cloudflare build --skipNextBuild` into `postbuild`
  so the same `pnpm build` produces the worker.
- Bump `@opennextjs/cloudflare` 1.13.0 → 1.19.4 (1.13 doesn't recognise
  Next 15.5 / WASM correctly; 1.19 is the first release that pins
  `next >=15.5.15`).
- Bump `wrangler` 4.49 → 4.84 to satisfy the new peer requirement.
- Add `output: "standalone"` to `next.config.mjs` — OpenNext 1.19 reads
  the standalone trace to bundle the server function.
@zxch3n zxch3n merged commit 309f71c into main Apr 28, 2026
2 checks passed
zxch3n added a commit that referenced this pull request Apr 28, 2026
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