Skip to content

Move the docs from Nextra to Fumadocs - #1250

Merged
sdserranog merged 28 commits into
mainfrom
fumadocs-migration
Oct 19, 2026
Merged

sdserranog merged 28 commits into
mainfrom
fumadocs-migration

Conversation

@sdserranog

@sdserranog sdserranog commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

What this is

This PR moves docs.arcade.dev from Nextra to Fumadocs. It changes the framework only. Readers get the same pages, URLs, SEO and look as today.

Preview: https://docs-5707m0t2b-arcade-ai.vercel.app

This started as an experiment. It's ready to merge, but it's up for discussion.

Why

How it was done

  • Content: app/en/**/page.mdx moved to content/docs/**.mdx (178 pages). Each _meta.tsx became a meta.json (48 files). A one-time script did the conversion and is not part of this PR.
  • Components: the Nextra MDX components we use (callouts, tabs, steps, cards and others) have small drop-in versions in components/mdx, so page content did not need rewriting.
  • Toolkit pages: still generated from toolkit-docs-generator/data, same as today.
  • URLs: everything stays under /en/..., and redirects.ts is identical to production.
  • Tooling: bun instead of pnpm. The CI workflows were updated to match. llms.txt generation and the toolkit generator run on Node, as before.
  • Kept in sync: everything merged to main up to docs(agent-plugin): add install guides for the Arcade agent plugin #1175 is included, so this branch is not behind main.

What we tested

Everything below was checked against the live docs.arcade.dev.

  • Sitemap: the same 301 URLs.
  • Every page in the sitemap: title, description, social share tags, canonical link, structured data (JSON-LD) and H1. No differences.
  • robots.txt and llms.txt: byte-for-byte identical.
  • Redirects: all 216 rules give the same result.
  • Visuals: full-page screenshots compared with production, and the gaps fixed. New pages from main (Helm, Tool Recommendation, PagerDuty) were checked by eye.
  • CI behavior: the llms.txt workflow was run in a shallow clone, the way CI checks out the code. It produces the same file as production.
  • Repo checks: lint, typecheck, build and 901 tests (76 files) pass.

Expected differences:

  • Internal links: they point straight at /en/... instead of going through a redirect.
  • "Edit this page" links: they point to the new content/docs paths.
  • Glossary: the fix for nested glossary terms now works on "end-user". It doesn't take effect under Nextra.
  • Previous and Next on toolkit pages: Next now goes to the page that actually follows (Gmail goes to Nimble). Nextra skipped one page there.

How to review

  • Skim: open the preview and click around. It should feel exactly like production.
  • The code: most of the diff is content files moving. The interesting parts are source.config.ts, lib/source.ts, components/mdx and app/en/[[...slug]].
  • Rollback: reverting this PR brings back the Nextra site.

Not in this PR

  • Visual changes: none. A redesign that matches arcade.dev can come as a separate PR.
  • Search: it still uses Algolia.

🤖 Generated with Claude Code


Note

High Risk
Major docs-site architecture change (framework, routing, content paths, and CI/package manager) affecting every page; parity was tested but regressions would impact SEO, links, and build pipelines broadly.

Overview
Replaces Nextra with Fumadocs for docs.arcade.dev while keeping URLs, redirects, and reader-facing behavior aligned with production.

Content & navigation: MDX moves from app/en/**/page.mdx to content/docs/**; Nextra _meta.tsx sidebars become folder-level meta.json. Workflows, pre-commit hooks, Vale, redirect checks, Algolia reindex triggers, and llms.txt paths all target content/docs instead of app/en.

App shell: Root layout drops Nextra’s Layout/Navbar/pageMap in favor of Fumadocs sourcing (lib/source.ts, source.config.ts) and custom chrome (components/chrome/, app/en/layout.tsx). A single app/en/[[...slug]]/page.tsx renders MDX from the Fumadocs loader and falls back to generated toolkit routes via lib/toolkit-route.tsx, replacing per-category toolkit page.mdx stubs. Adds /llms.mdx/[[...slug]] for processed Markdown export (copy-page flow). Shared UI moves from app/_components to components/; toolkit APIs import from lib/.

Styling & MDX: app/global.css replaces Nextra theme CSS with Fumadocs presets plus large “parity” blocks so MDX components, toolkit pages, and chrome match the old site.

Tooling: pnpm → bun (bun.lock, bunfig.toml); CI/Makefile/Husky/VSCODE/docs updated. Toolkit sidebar sync now emits meta.json files. Removes .i18n-cache; ignores Fumadocs .source output.

Reviewed by Cursor Bugbot for commit 9f3d9ee. Bugbot is set up for automated code reviews on this repo. Configure here.

sdserranog and others added 25 commits October 6, 2026 11:07
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ests and CI to Fumadocs

- 170 MDX pages + 37 meta.json converted from app/en (bodies byte-identical, verified by scripts/migration/verify-content.ts)
- Nextra-compat MDX components (Callout, Tabs with index-based persistence, Steps, Cards, pre with Terminal header, Mermaid)
- toolkit (MCP server) pages served from JSON through the docs catch-all (124 routes) + generated sidebar meta.json
- scripts, 72 test files, husky hooks, GitHub workflows ported to bun
- llms.mdx per-page markdown route, Orama search, sitemap, redirects, security headers
- update-internal-links: skip locale-root redirects (empty path matched every link/backtick pair)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- codemod prefixes 860 root-relative content links with /en so clicks no
  longer bounce through the proxy locale redirect (verifier updated to match)
- migrate-content gains --content-only and emits hidden-page markers and
  formatted meta.json so a re-run leaves a clean tree
- 404 page, MCP client grid and landing page point at current URLs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- notebook layout: top navbar with logo, search, Sign Up / Dashboard, GitHub
  and Discord (single GitHub icon), sidebar sections open by default
- Docs Home icon (lucide 1.x renamed Home -> House)
- Nextra-style coloured callouts; terminal traffic lights no longer overridden
  by shiki span styles; description no longer duplicated under the title
- toolkit pages: single breadcrumb, content clear of the fixed TOC panel
- page components rendered inside the prose body keep their own heading
  spacing; toolkit catalog designation and behavior-flag order restored
  (the linter sorts object keys, so display order is now explicit)
- Edit on GitHub moves under the table of contents

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- llms.txt generation skips (with a notice) when OPENAI_API_KEY is unset
- Algolia crawler sync runs only when ALGOLIA_CRAWLER_ID is configured
- scheduled toolkit-docs generation and example-date updates run only in
  the upstream repo or when triggered manually
- schema validation: an empty file list no longer produces a bare "|" that
  made the validator fail with "No files found" (latent upstream bug)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…freshness schema

- /:locale redirect scoped to (en|es|pt-BR): it was sending /llms.txt,
  /robots.txt and /sitemap.xml to /<file>/home (404)
- proxy matcher lets /specs/*.yaml through
- verify-content reads scripts/migration/overrides/*.json so intentional
  moves, deletions, enhanced pages and new hub pages can be declared per owner
- frontmatter gains lastVerified / verifyEvery for the freshness badge

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The migration was taken from ArcadeAI/docs at 2ba3f56. This ports the
twelve commits made there since, up to ab83fe0.

- New Private Registry page under Governance, with its four images,
  its menu entry, and the links to it from the governance overview and
  the two MCP gateway pages (#1225, #1230, #1231).
- Dashboard menu label "Connected apps" is now "OAuth providers" on 36
  auth provider pages (#1226).
- Slack: reactions:write added to the scope list (#1224).
- Toolkit data refreshed for 83 files (#1219), and the generator pages
  public tools at the API's 1,000 cap (#1228).
- llms.txt: summaries come from Claude Haiku 4.5, placeholder
  descriptions are retried, and the file is not rewritten when only the
  header would change; workflows read ANTHROPIC_API_KEY (#1229).
- Design system 7.11.0, and the update workflow waits for the requested
  version to appear on npm (#1223, #1233).

The Next.js and transitive security bumps (#1235, #1240) needed no
change: the lockfile here already had the patched versions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A side-by-side comparison with docs.arcade.dev showed the migration had
the same content and routes but a different look. This ports
production's own layout and components instead of approximating them.

- Chrome (components/chrome): navbar, sidebar with its footer row,
  mobile menu, breadcrumb, title row with the single "Copy page" button,
  table of contents, last updated date, previous/next links, footer band
  and 404 page, with production's sizes, colours and behaviour.
- Content: Nextra's element renderers ported for headings, links, lists,
  tables, tabs (every panel in the HTML, synced and remembered), code
  blocks, steps, callouts, cards, details and image zoom. Heading ids
  come from one github-slugger per page, as in production.
- Toolkit, catalog and home pages: production's typography, borders,
  legend text and "Copy page" request.
- Search is production's component on react-instantsearch.
- Head metadata, sidebar tree and sitemap are equal on all 294 pages.
- /en and /es redirect through the proxy with a 307, as in production.
- Packages shared with production are pinned to its versions (React,
  lucide-react, ui-kit, mermaid, motion, posthog and others). Biome and
  ultracite stay on the versions this repo's config is written for.
- Upstream #1242 (Entra group access permissions) ported.

Not copied on purpose: production lets the toolkit article run 16px
under the right-hand panel at 1280-1440px, which clips text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every one of the 294 pages was screenshotted top to bottom on
docs.arcade.dev and on this build, in dark desktop, dark phone and light
desktop, and compared pixel by pixel.

- Code fences: Nextra's line-highlight meta is parsed in full, so
  `{1-5} {16}` and `{9-18, 20, 24}` shade the same lines as production.
- Table of contents: inline code in an entry renders with the content's
  own code component.
- Toolkit pages: the article has production's exact width again, so
  every line breaks in the same place.
- "Last updated on": imported pages carry the date production shows for
  them (data/upstream-last-modified.json); a page edited after the
  import uses its own git date. Dates are formatted in UTC.

Result: 289 of 294 pages are pixel-identical in dark desktop and 290 on
phone. The rest are the two PostHog survey pages (no key locally), one
recompressed image and Cloudflare's e-mail protection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Next.js 16.4.0, React 19.3.0, Fumadocs 16.17, Tailwind 4.3.3,
TypeScript 7, Vitest 5, lucide-react 1.x, mermaid 12, motion 14,
ui-kit 0.21, zod 4.6, posthog, algolia and the rest. Changelogs for
every major jump were checked against how this repo uses the package.

- Mermaid 12 defaults to the ELK layout and the "neo" look; the diagram
  component asks for dagre and "classic", which the diagrams were
  written for.
- The style-review scripts named a retired Claude model; they use
  claude-sonnet-5-5 now.

No other code needed changing: type check, lint, 884 tests and the
production build pass. Against production, 172 of 294 pages stay
pixel-identical on desktop; about 120 differ by at most 0.03% of
pixels (redrawn lucide icons), and the glossary diagrams by 0.7%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The content migration script defaulted to a path on one developer machine.
It now requires --source or MIGRATION_SOURCE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tree of this commit is the migrated site: the same content, routes,
redirects, look and CI as main at a3a53e8, built on Fumadocs with bun
instead of Nextra with pnpm, and with every package on its latest
version.

The second parent carries the migration's own history. This merge takes
that tree as is, so the diff against main is the whole migration.

Checked against docs.arcade.dev before the package upgrade: all 294
pages present, 214 redirects equal, and 289 of 294 pages pixel-identical
top to bottom on desktop. After the upgrade, the differences are a few
redrawn icons and the glossary diagrams.

Not verified locally: search results, the Feedback tab and the two
survey forms (they need production keys), and the API reference page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop scripts/migration and MIGRATION_PLAN.md along with their package
scripts and references.

The toolkit generator CLI and the sidebar sync crashed under Bun: Bun
resolves the design system bun export condition, which points at
sources the package does not ship. Run both through the pinned tsx, as
production does, and add Node to the workflow. The nine integration
meta.json files now carry the key order the sync writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/generate-llmstxt.ts crashed under Bun for the same reason as the
toolkit generator: it imports design system metadata, and Bun resolves
the package bun export condition to sources that are not shipped. Run it
through the pinned tsx and add Node to the llms.txt workflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Revert the package upgrades so the pull request only swaps the docs
framework: every package production also uses is back at production
version, including vite.

Restore two files production has that the migration dropped: the
curate-toolkit-docs skill (paths updated for the new layout) and
components.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use production lint versions and config (Biome 2.3.2, ultracite 6.1.0)
so files production already has are not reformatted, and put
production text back wherever the migration only needed to move a file
or update its import paths. Each restored file was kept only if
typecheck, lint, the test suite and the rendered HTML of every page
were unaffected.

Also restore production Node version (22), its schema validation
workflow and its .gitignore, and split three functions in new code that
production complexity rule rejects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The changelog agent still wrote to app/en/references/changelog/page.mdx,
which no longer exists, and the Examples page told contributors to edit
the old file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Curation import lines, and the toolkit data generated from them, named
modules that no longer exist (app/_components and nextra/components).
They now name the current components, with the curation hashes
refreshed through the generator hash function.

Also correct components.json paths, drop the i18n cache entry from
.cursorignore, and reword comments that described the previous setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions

- Carry the security pins from pnpm-workspace.yaml into package.json.
- Exempt @arcadeai/design-system from the release-age guard so the
  update workflow can install a release right after it is published.
- Restore the crawler selectors: the pages keep the same class names.
- Remove job conditions and the build directory option that production
  does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…factor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports the seven commits merged into ArcadeAI/docs since a3a53e8.

- Tool Recommendation section under MCP Gateways, with its glossary
  entry (#1204).
- Helm: values reference synced from the released chart, internal
  networking, IPv6/dual-stack and container registry guides, and the
  Arcade Deploy on your own cluster page (#1237, #1241, #1244). The sync
  script writes the page to content/docs and the raw chart copies to
  content/helm-chart, outside the docs source; its workflow runs on bun.
- Google auth provider: the Drive picker's JavaScript origin (#1247).
- The changelog page and its generator are gone; the menu, home page
  and llms.txt link to releases.arcade.dev (#1243).
- PagerDuty integration guide sections (#1248).
- Glossary terms no longer nest inside a longer term, and the sidebar
  wraps long labels at spaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Oct 19, 2026 9:31pm UTC

Request Review

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread components/chrome/page.tsx
@sdserranog
sdserranog marked this pull request as ready for review October 19, 2026 20:53
Toolkit pages are not part of the previous/next sequence. When one was
open, Next skipped the real page right after it (Gmail went to Tavily
instead of Nimble). Previous and Next now point at the pages on either
side of it. Nextra had the same skip on production.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports the agent plugin install guides (#1175): the new Agent Plugin
section under Get started, the links to it from the MCP client pages,
the llms.txt entries and the Vale vocabulary.

Two things the new pages needed:
- Nextra's Code and Table (with Table.Tr, Table.Th, Table.Td) are
  available in every page, as the What's included page uses them.
- The page heading gets the same curly quotes, dashes and ellipses as the
  body, as it did when the heading was part of the MDX.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0d47732. Configure here.

>
{innerChildren(toolkitPage)}
</DocsPageShell>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toolkit pages show duplicate breadcrumbs

Medium Severity

The catch-all unwraps ToolkitRoutePage with innerChildren, which drops the DocsPage settings that turn off the chrome breadcrumb. DocsPageShell then keeps its default breadcrumb on, so the sidebar trail renders above the in-page BreadcrumbBar on every toolkit page.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0d47732. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two breadcrumbs is right, but production shows the same thing on every toolkit page today: the Nextra sidebar trail above the toolkit's own BreadcrumbBar (checked side by side on Gmail). This PR keeps the site identical to production, so I kept it and made it explicit in 9f3d9ee (breadcrumb passed on purpose, with a comment). Removing the duplicate is a good follow-up after the migration.

Comment thread .husky/pre-commit Outdated
# --- Check Meta Keys (when _meta.tsx files are changed) ---
STAGED_META=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '_meta\.tsx$' || true)
# --- Check Meta Keys (when content/docs meta.json files are changed) ---
STAGED_META=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '^content/docs/.*/meta\.json$' || true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Root meta.json skips pre-commit check

Low Severity

The staged-file grep requires a directory after content/docs/, so content/docs/meta.json never trips the hook. That file is the top-level sidebar. CI does not run check-meta, so a bad pages list there can be committed without the validator the hook is meant to run.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0d47732. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, fixed in 9f3d9ee. The hook now matches content/docs/meta.json too. Tested by staging a root meta.json with a bad entry: the hook runs check-meta and blocks it, where the old pattern skipped it.

The hook's pattern needed a folder after content/docs/, so the top-level
sidebar file never reached check-meta. It does now.

The catch-all also passes the toolkit pages' breadcrumb explicitly, with
a note that the sidebar trail above the toolkit's own one matches
production.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sdserranog
sdserranog merged commit 17b24d3 into main Oct 19, 2026
10 checks passed
@sdserranog
sdserranog deleted the fumadocs-migration branch October 19, 2026 21:48

This branch was successfully deployed

1 active deployment
Preview — 9f3d9eef Deployed Oct 19, 2026 by vercel[bot]
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.

3 participants