Skip to content

Migrate website from Nextra v2 to Nextra v4 #2130

Description

@mnapoli

Upgrade website/ from Nextra 2.13 to Nextra 4. This is 3 stacked migrations: Nextra 2→4 API, Next.js Pages Router→App Router (v4 is App Router only, theme.config.jsx no longer exists), Tailwind 3→4 (required by nextra-theme-docs v4).

Site inventory: 86 MDX pages, 15 _meta.json, ~50 files importing Nextra components, ~44 files using next-seo, 140-line theme.config.jsx, custom marketing pages (home, /cloud/*, /news, sponsors/testimonials via GitHub API), Algolia DocSearch, next-sitemap, next-plausible proxy, middleware, redirects.js, /api/md rewrite (Markdown for AI crawlers), link-checker/. Note: docs source lives in top-level docs/, synced into website/src/pages/docs/ by the website Makefile.

Full Phase 0 migration map: see audit comment.

Decisions

  • Search: replace Algolia DocSearch with Pagefind (v4 default), docs-only index (exclude marketing pages). Reversible via custom search prop on theme Layout. Accepted losses: search analytics, typo tolerance, no search in next dev.
  • Docs design: adopt v4 default look, no pixel-matching. Matthieu reviews side-by-side screenshots before merge; big differences not acceptable.
  • Redirects: hard requirement. Any URL change must be flagged + get a redirect. Verify by crawling all current prod sitemap URLs against new build → assert 200-or-redirect. Hash-fragment redirects (~30, client-side) must be reimplemented in App Router.
  • Remote MDX: removed via prep PR Inline AWS CDK docs instead of fetching them remotely #2131 (inlined constructs README; future updates manual).

Plan

  • Prep PR Inline AWS CDK docs instead of fetching them remotely #2131: inline constructs README, drop nextra/remote — merged
  • Phase 0 — audit: migration map
  • Phase 1 — spike: all 6 unknowns answered, working build — spike report. Scaffold reusable: branch worktree-agent-a61252823a9d0f300, website/spike/, commit 5ce77963
  • Decision: fresh v4 scaffold + port content (in-place would leave repo unbuildable throughout; scaffold doesn't survive the v2→v4 cut). Reuse the spike skeleton
  • Phase A — fresh v4 scaffold in website/ (PR Migrate website to Nextra 4 #2132, build green): app/layout with full theme.config port, catch-all route + metadata, Tailwind v4 + brand palette, hash-redirect client component, middleware (moved to website root), .md route handler, Makefile sync retargeted to content/docs. Old pages staged in website/_port/pages/
  • Phase B — bulk docs content: 67 source docs swept, 13+2 _meta converted, full sync active, build green. URL parity: 70/70 live /docs/* URLs present. Extras: fixed icon import paths broken by the dir move (5 files), extracted serverless-costs calculator to a 'use client' component. Note: some internal .mdx-suffixed links 404 — pre-existing on live prod, not a regression (follow-up candidate)
  • Phase C — marketing/custom pages: all ported, build green (94 pages). Sitemap parity 91/91 vs live, 0 missing 0 extra. All routes curl-verified 200. Notes: /cloud/* per-component routes were accidental v2 pages, recreated as thin wrappers for parity (candidate to remove+redirect later); credits data now revalidates hourly instead of build-only (harmless); changelog kept as MDX + async Server Component
  • Phase D — Pagefind docs-only via CLI glob (70/70 index entries are /docs/*); plumbing verified: .html+path redirects 308-correct, hash-redirect coverage confirmed by static analysis, .md+content negotiation pass, sitemap 91/91 + robots byte-identical to live, Plausible proxy config identical, trailing-slash identical to live. Bug fixed: JSX comments leaked into .md crawler output
  • Phase E — verification: zero regressions vs prod. 91/91 sitemap URLs, 71/72 redirects + 5 .html pattern (1 failure pre-existing on prod), 66/70 .md endpoints (4 failures pre-existing on prod), browser tests 5/5 (hash redirects, docs-only search, calculator/pricing/tabs), 24 side-by-side screenshots captured
  • Review fixes (commit e4acf3f): independent code review found 0 high-severity issues; applied 6 fixes — /docs broken .md alternate link (only true regression), path-traversal hardening in md route, .md-source fallback (3 pages now work, 404 on prod), /docs.md now serves markdown (404 on prod), revived dead /docs/web-apps* redirects (404 on prod), removed 41 dead hash entries from server redirects
  • Matthieu's visual review round 1: new build looks different from prod → full visual audit of all 12 screenshot pairs: 5 defects, 9 drift items
  • Visual fixes (commits 5dfa175, 67c6372, bc2d3b1): hero H1 fix — real root cause was MDX wrapping multi-line JSX heading text in a docs <p> (leading-7 overriding h1 leading), fixed by inlining the text; Tailwind @source for gitignored content/docs (restored docs-only utility classes); copyPage:false on all marketing pages; credits page full-layout theme block; news image confirmed non-bug (lazy-load timing). Drift restorations per Matthieu: blue-tinted code blocks, "Search docs" placeholder; all other v4 look changes accepted (content width, green callouts, ↗ links, sidebar pill)
  • Full-bleed marketing pages (commit 834ae93): layout:'full' in v4 only drops the TOC column — the theme MDX wrapper still caps content at --nextra-content-width (90rem). Fix the Nextra way: resolve active layout via normalizePages in the catch-all route, tag full pages with data-full-layout, override the documented --nextra-content-width var to 100% + zero article padding on those pages only. Measured at 1920px: home/cloud heroes = 1920, navbar inner + docs unchanged at 1440
  • Round-3 fixes (commits 37c1076, aae06d5): React console errors on /cloud (stop-colorstopColor, missing key in tiers map; sweep of 6 pages + 43 map sites found no others); white background everywhere via <Head backgroundColor={{light:'#fff'}}>; docs blues were Nextra's default primary (hue 212) injected by <Head> overriding globals.css — fixed with <Head color={{hue:202,saturation:80,lightness:57}}> = Bref #3AA9E9 (dead CSS-var override removed)
  • Sidebar page+folder duplicates (commit 3ad5ecc): v4 no longer collapses X.mdx + sibling X/ into one link — 7 sections showed a duplicated nested child (setup, deploy, runtimes, case-studies, local-development, monitoring, use-cases/http). Fixed the v4 way: moved each to X/index.mdx with asIndexPage: true. Routes unchanged, all URLs 200, folder headers now clickable links, 94 pages
  • Matthieu: visual review round 2 + merge decision on PR Migrate website to Nextra 4 #2132

Known gotchas

  • Pin next@15, NOT 16 — Nextra 4.6.1 + Next 16 500s on every route (Layout children bug)
  • Override zod to 4.3.0 — zod 4.4.x breaks nextra-theme-docs 4.6.1 safeParse
  • theme.layout:'raw' removed in v4 → layout:'full' + sidebar/toc/breadcrumb/pagination:false
  • layout:'full' does NOT make pages full-bleed — content stays capped at --nextra-content-width (90rem); override the var per-page (see full-bleed fix above)
  • Home page title needs title:{absolute} in generateMetadata, else double "– Bref"
  • Prefer per-fetch { next: { revalidate } } over route-level export const revalidate
  • MDX v3 needs a blank line after imports, else acorn parse errors
  • export function inside MDX breaks component resolution → extract to .jsx files
  • MDX has no HTML comments — use {/* ... */}
  • nextra-theme-docs v4 CSS uses Tailwind v4 @layer → requires Tailwind 4 + @tailwindcss/postcss
  • _meta.js uses export default; separators are { type: 'separator' }
  • primaryHue is gone → CSS var --nextra-primary-hue

Follow-up candidates (pre-existing, not migration scope)

  • Some internal .mdx-suffixed links in docs 404 (already broken on live prod)
  • link-checker crashes on a literal http://localhost:8000/ example URL in docs prose
  • 22 Markdown images in docs/ have empty alt text (![](...)) — Next.js dev overlay flags them as a11y errors (dev-only, build unaffected); fix = write descriptive alts

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions