Conversation
Replace the Nextra 2 / Pages Router config with a Nextra 4 scaffold: next.config.mjs (ESM, plausible proxy + redirects.js + .md rewrite preserved), Tailwind v4 CSS-first globals.css (blue palette + primaryHue as CSS vars), @tailwindcss/postcss, and pinned next@15 + zod@4.3.0 per the migration spike. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Port every theme.config.jsx option into app/layout.jsx (AnimatedLogo navbar, projectLink, Slack chatIcon, v3 Banner, footer, forcedTheme light, metadata title template + OG + verification), add the [[...mdxPath]] catch-all with frontmatter->metadata and per-docs .md alternate, the app/api/md route handler, and mdx-components.jsx h1 override. Port _app.jsx hash-redirect logic to a 'use client' HashRedirects component and move middleware.js to the app root. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Add content/ with a home stub, root/docs _meta.js, and four sample docs pages (plain, Tabs+Callout, and two real pages with NextSeo->frontmatter fixes) to prove rendering. Move src/pages to _port/pages (staging, outside the build) and point the Makefile sync + gitignore at content/docs. Full content sweep is the next phase; sample docs are force-added over the content/docs ignore rule. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
16 tasks
…MDX v3 blank lines) Convert Tab -> Tabs.Tab and Card -> Cards.Card across all docs MDX (v4 renamed these to sub-components of Tabs/Cards). Remove next-seo imports and <NextSeo> tags, moving their description/title into YAML frontmatter (MDX v3 reads frontmatter natively; page title otherwise falls back to the first heading). Add a blank line between the last import and content where MDX v3's acorn parser requires it. Fix relative imports into website/src/components/ (the docs/ -> content/docs/ move sits one directory level shallower under website/ than the old src/pages/docs/ did). Extract the interactive costs calculator out of serverless-costs.mdx into a 'use client' component (website/src/components/CostsCalculator.jsx) since inline `export function` in MDX is not supported by Nextra v4. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Nextra v4 expects _meta.js exporting a default object instead of _meta.json; JSON is valid as the object literal body, so this is a mechanical wrap. These get synced into website/content/docs/ by the Makefile. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Follow-up to the _meta.js conversion: delete the old .json files now that their .js replacements are in place. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
website/content/docs/_meta.js (the full ~30-entry sidebar config, including separators) now comes entirely from the synced+converted docs/_meta.js instead of Phase A's 5-entry placeholder. Root content/_meta.js keeps the remaining _port/pages/_meta.json entries (news, cloud, support, credits, 404, sentry, xray) as commented-out Phase C notes so the nav doesn't link to pages that don't exist yet. All 86 docs/ files (67 mdx + 3 md + 13 _meta.js + images) now sync into website/content/docs via `make content/docs`, and `npm run build` produces 70 /docs/* routes, matching every /docs/* URL in the current live sitemap (https://bref.sh/sitemap.xml). Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Home invocations counter and credits sponsors/contributors become async Server Components fetching directly (getBrefInvocations/getSponsors/getContributors), replacing v2 getStaticProps + useData. Fetches keep their try/catch fallbacks so builds succeed without AWS/GitHub credentials. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Extract the hooks-driven page body into a 'use client' BrefCloud component and mark the interactive pricing/how-it-works subcomponents 'use client'. Recreate the six /cloud/* routes (case-studies, faq, features, hero-bg, how-it-works, pricing) that the v2 Pages Router exposed per-component, to preserve URL parity. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Extract the useState-driven landing pages into 'use client' SentryPage/XRayPage components; docs/terms stay as MDX content routes. The X-Ray changelog (a React page with getStaticProps in v2) becomes an async Server Component fetching getReleases, rendered via a thin MDX wrapper. Page images live with their components; docs screenshots live beside the content routes. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
News index + three articles (typesetting:article, ArticleHeader) and the support page port as content pages; 404 becomes app/not-found.jsx. Set dynamicParams=false on the catch-all so unknown URLs render the custom 404 boundary. Complete content/_meta.js with all top-nav and hidden pages, and delete the _port staging tree now that every page lives under content/ or app/. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Pagefind previously indexed all 89 built pages including marketing pages
(home, cloud, news, etc.). Restrict indexing to /docs/* via a route-based
--glob pattern so newly added marketing pages aren't silently swept into
search results without needing per-page annotations.
Also fixes the /api/md route leaking a raw JSX comment ({/* ... */}) from
content/docs/deploy/aws-cdk.mdx into the crawler-facing Markdown output,
since aws-cdk.mdx's content was recently inlined with a maintainer note
written as a JSX comment.
Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
… fallback, /docs.md route, dead redirects cleanup - Fix broken /docs/.md alternate link emitted on the /docs landing page (only emit for actual docs pages with mdxPath.length > 1) - Harden the markdown route against path traversal by rejecting resolved paths outside content/docs - Fall back to .md source files when .mdx doesn't exist (fixes 3 docs pages that 404 on their markdown endpoint) - Add /docs.md rewrite and serve content/docs/index.mdx for the index route (route folder converted to optional catch-all) - Fix dead /docs/web-apps(/index) redirect (was keyed with a hash that never reaches the server) and add /docs/web-apps redirect - Filter hash-anchor entries out of next.config redirects() since the server never sees the hash; they remain in redirects.js for the client-side HashRedirects component Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
- D1: add explicit leading-tight to hero <h1>s (home + cloud) so the multi-line headings no longer collapse/overlap under the theme's competing line-height rules. - D2: add @source '../content/docs' to globals.css so Tailwind v4 scans the gitignored docs content dir again, restoring doc-only utilities like the maturity-matrix bg-green/yellow/red-400 dots. - D3: set copyPage: false on all full-layout marketing pages (home, cloud + its /cloud/* fragment routes, support, sentry, xray) so the docs "Copy page" button no longer leaks onto marketing pages. - D4: give credits the same full-layout theme block as other marketing pages so it renders without docs sidebar/TOC/copy-page/feedback chrome. D5 (news dashboard image) verified as a screenshot lazy-load timing artifact, not a real bug - the image loads correctly given time. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
…ecision Maintainer accepted these two v2 look-and-feel restorations while leaving the rest of the v4 defaults (content width, callout colors, external-link arrows, sidebar active style) as-is: - Search placeholder: "Search docs" instead of v4's default "Search documentation…" - Code blocks: restore the light-blue background tint instead of v4's neutral one Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
…ne-height The previous leading-tight fix was a no-op. The actual bug: because the raw JSX <h1> in content/index.mdx (an .mdx file) had its text content split across multiple source lines, MDX's remark processing wrapped it in a <p class="x:leading-7"> child, whose 28px line-height overrode the h1's own leading and collapsed the two-line heading onto itself. Fix: put the h1's full text content on a single source line so MDX treats it as inline phrasing content instead of a block-level paragraph, avoiding the injected <p> entirely. Measured before claiming success: h1 now has no child <p>, and its bounding box height is exactly N x its computed line-height on both desktop (120px = 2 x 60px) and mobile (135px = 3 x 45px). The Bref Cloud hero (BrefCloud.jsx) was checked too but never had this bug — it's a plain .jsx component, not MDX, so its multi-line JSX text was never paragraph-wrapped. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Pages whose _meta.js sets theme.layout: 'full' (home, cloud, support, credits, sentry, xray) must render edge-to-edge like on production. In Nextra v4, layout: 'full' only drops the TOC reservation; the theme's MDX wrapper still constrains content to --nextra-content-width (90rem). Resolve the active layout the same way the theme does (normalizePages) in the catch-all route and tag full pages with data-full-layout. globals.css then overrides the documented --nextra-content-width variable on that container (100% = viewport-wide) and removes the article's horizontal padding. Navbar/footer keep their 90rem inner width since they render outside this container. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
- hero-bg.jsx: SVG stop-color -> stopColor (JSX camelCase, invalid DOM prop)
- features.jsx: add key={tier.id} to tiers.map() list children
Swept /, /cloud, /docs, a docs page, /support, /credits with playwright-cli;
no other React key/prop/DOM warnings found.
Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Nextra's <Head> injects a :root block for --nextra-bg and --nextra-primary-*
that overrides globals.css, so these must be set via Head props, not CSS:
- backgroundColor={{ light: '#fff', dark: '#111' }}: page background is pure
white instead of Nextra's default grey rgb(250,250,250). Also paint <body>
white via the same --nextra-bg var (Head only paints <html>).
- color={{ hue: 202, saturation: 80, lightness: 57 }}: primary is Bref
blue-500 hsl(202,80%,57%) = #3AA9E9, not Nextra's default hue-212 blue. Docs
links and the active sidebar item now use the Bref palette. Removed the dead
--nextra-primary-* :root override in globals.css (Head's block won anyway).
Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Nextra v4 no longer collapses a `X.mdx` page next to a sibling `X/` folder into one link (as v2 did): it renders an expandable folder with the page duplicated as a child (Setup > Setup), or a dead button header for folders that do have children (Deployment > Deployment, Docker, CDK). Fix the Nextra v4 way: move each `X.mdx` to `X/index.mdx` with `asIndexPage: true` frontmatter. normalize.js then absorbs the index page into the folder node (folder route == index route), so the folder header is the page link with no duplicate child; folders whose only other children are `display: hidden` (setup, monitoring) render as a single flat link like prod. Applied to all 7 page+folder pairs with nested pages: setup, deploy, runtimes, case-studies, local-development, monitoring, use-cases/http. Routes are unchanged (/docs/X), so markdown links and inbound links stay as -is; only webpack imports (component + image ES imports) get the extra ../. content/docs is a build-time copy (gitignored); sync via `make`/sync-directory. Claude-Session: https://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migrates the website from Nextra 2 (Pages Router) to Nextra 4 (App Router). Part of #2130.
This first phase stands up a green-building v4 skeleton; content is ported incrementally in later phases.
Phase checklist
@tailwindcss/postcss+ forms), zod@4.3.0 override, pagefind. Removed next-seo, Algolia DocSearch, autoprefixer.app/layout.jsx(all theme.config options ported),app/[[...mdxPath]]catch-all,mdx-components.jsx,app/api/md/[...slug]route handler.%s – Bref, absolute home title, frontmatter description, OG/social-card, twitter, google-site-verification, per-docs.mdalternate link._app.jsxclient redirect logic →'use client'HashRedirectscomponent.@themeblue palette,--nextra-primary-hue: 202deg).npm run buildpasses; sitemap + pagefind postbuild chain runs.docs/MDX; MDX v3 blank-line fixes; fixed relative imports intowebsite/src/components/; extracted the interactive costs calculator to a'use client'component._meta.json→_meta.jsfor all 13docs/files.docs/(67 mdx + 3 md + assets) now syncs intowebsite/content/docs; build produces 70/docs/*routes, matching every/docs/*URL in the current live sitemap.'use client'components; page bodies stay server. 404 →app/not-found.jsxwithdynamicParams=falseso unknown URLs hit the custom boundary.bref.shsitemap exactly — 91/91 URLs, zero missing, zero extra (the six accidental/cloud/*component routes were recreated for parity).website/_port/deleted; every page now lives undercontent/orapp/.pagefind --glob "{docs.html,docs/**/*.html}"— route-based scoping so new marketing pages can'''t leak into search without needing per-page annotations. Verified 70/70 indexed fragment URLs are all/docs/*.next start):.htmlstrip redirects,redirects.jspath + hash entries (hash entries confirmed via static analysis —HashRedirects.jsxshares the sameredirects.jsobject, so it inherently covers exactly the hash-bearing keys since non-hash ones are already resolved server-side),Accept: text/markdownnegotiation,/docs/*.mdroutes (incl. nested/docs/deploy/aws-cdk.md), sitemap (91/91 URLs, correct host, no localhost//api/_pagefindleakage), robots.txt (matches live), Plausible proxy + provider wiring (matches live config), and trailing-slash behavior (matches live)./api/mdroute leaked a raw JSX comment fromcontent/docs/deploy/aws-cdk.mdx(recently inlined) into crawler-facing Markdown output — now stripped.Review fixes
/docslanding page emitting a broken/docs/.mdalternate link (guarded to real docs pages only)/api/md) against path traversal by rejecting resolved paths outsidecontent/docs.mdsource files when.mdxdoesn't exist (fixes 3 pages that 404'd on their markdown endpoint)/docs.mdsupport (serves the docs index through the markdown route; route converted to an optional catch-all)/docs/web-apps(/index)redirect (was keyed with a hash that never reaches the server) and added a working/docs/web-appsredirectnext.configredirects()since the server never sees the hash; they remain inredirects.jsfor the client-sideHashRedirectscomponenthttps://claude.ai/code/session_01Q293dCZ4iL2Qys9rcSGwcR
Visual fixes
<h1>line-height collapse causing overlapping text on home + Bref Cloud (desktop and mobile)content/docs, which dropped doc-only utility classes (e.g. maturity-matrix colored dots)/cloud/*fragment routes, support, sentry, xray)