Skip to content

Feat/nextjs migration - #1

Merged
abappi19 merged 23 commits into
mainfrom
feat/nextjs-migration
Sep 11, 2026
Merged

Feat/nextjs migration#1
abappi19 merged 23 commits into
mainfrom
feat/nextjs-migration

Conversation

@abappi19

Copy link
Copy Markdown
Member

No description provided.

The docs were a separate repository on Next and Fumadocs, mounted as a submodule and served
at /docs by GitHub as a project site. This repo is now the whole site: / is the landing page,
/docs is the docs tree, /blog and /blog/changelog sit alongside them.

Next won over Astro because it inverts the work. Porting one landing page into Next is small;
porting 29 MDX files plus Fumadocs, its search index, OG image routes and llms.txt into Astro
is not. So the docs project became the app and the landing page moved into it. Nothing about
the MDX pipeline changed.

The docs used to live in a route group that added no URL segment, which worked only because
basePath supplied /docs. They now sit under a real segment and basePath is gone, since a repo
named axonpack.github.io is served as the organisation root. Two things were wrong the moment
the docs stopped being their own repo: docsRepo pointed at axonpack/docs, so every "open in
GitHub" link would break once that repo goes, and the search dialog read the package slug from
the first path segment, which is now "docs" and matched no tag.

The nav is Radix NavigationMenu because fumadocs' own menus are click-to-open: their only hover
handler is on the mobile collapsible trigger. Search and the theme switch are still fumadocs',
so they keep talking to the same providers. Hover is still not working and is the next thing
to fix.

The 404 no longer assumes the reader wanted the docs, and the footer is ported from the Astro
site and renders on every page including that one.

Not done: the two package lists disagree. lib/packages.ts is hand-maintained and feeds the
search index, the MDX cards and the search tags; services/packages.service.ts is discovered
from npm and feeds everything else. sync-changelog.mjs is also still hardcoded to one package
while the npm discovery covers all of them.
There were two changelogs built from the same CHANGELOG.md. /blog/changelog aggregated every
library into hand-built cards, and /docs/<slug>/changelog was generated as MDX. With one library
published they showed the same releases twice, at two URLs, in two designs. The blog page is gone
and both nav surfaces point at the docs pages, which already carry the sidebar, search and table
of contents.

The header lists one entry per library rather than per release. Linking a single release would
mean mirroring fumadocs' heading-slug rule (2.5.0 becomes #250) in nav code, and the page is
newest-first, so the latest release is already at the top.

Each version heading now carries a ReleaseMeta badge row in place of a joined text line. The
heading itself stays plain markdown, because fumadocs builds the anchor and the table of contents
from real headings.

Two things were wrong in what the generator wrote. Changesets emits both "- <sha>: <text>" and a
plain "- <text>", and a changeset whose body is itself a list came out of the second shape as
"- - <text>", rendering an empty bullet around the real one. Only the sha form was handled.
BUMP_STYLES.patch also named bg-secondary and text-subtle, which do not exist, since fumadocs
prefixes its own tokens fd-, so a patch badge rendered as bare text.

The catalogue now records hasDocs and hasChangelog per package. Publishing to npm puts a library
in the catalogue, but writing its pages is a separate job: react-pretty-print is published with
no docs, so linking to its changelog would have 404d.

build runs generate first, so a fresh clone builds and the nightly cron picks up a release
published since the last commit. The site was serving 2.5.0 while the package was at 2.5.4.
It ran `bun run packages` and `bun run typecheck`, neither of which exists here: the first was
never a script in this package and the second is `check-types`. It then uploaded `dist`, which
was Astro's output directory, where Next writes `out`. Any run of this workflow would have
failed at the first of those steps.
… drawer

DocsLayout lays its children out in a named grid ("sidebar sidebar header toc toc"). This header
claimed no area, so it was auto-placed into the first column: 229px wide of a 390px phone, with
its own controls overflowing the box, and indented past the sidebar on desktop. Naming the area
is what fumadocs' own header slot does. Spanning every column instead looks right until you try
it, because the sidebar occupies columns 1 and 2 on every row and the two then overlap.

Search and the theme switch now sit in the drawer below lg, search filling the row and the theme
toggle at the end of it. At phone width the header had no room for them beside the logo, and a
search box worth typing into needs more than an icon. The shortcut hint is hidden there since a
phone has no key to press.

The drawer flattens the desktop panel's columns into one list, which dropped their titles and
left the changelog entry reading as a bare "expo-devtools" under Blog, indistinguishable from a
post. The titles now survive the flattening.
public/logo.png was missing the mark's centre dot entirely and sat on a white tile. It is now the
same 400x400 transparent source the assets/ copies already used, which is smaller than the 96x96
file it replaces because the shape is flat.

That tile was hiding something. One of the two strokes is black, so a transparent mark loses half
the letter on a dark background, which is what assets/logo-dark.png has always been for. The
header, the fumadocs nav title and the footer all rendered the light file at every theme, so they
now go through one Logo component that renders both and lets CSS pick. Doing it in CSS rather
than from the theme hook keeps it right on the first paint instead of after hydration.

assets/logo.png was already byte-identical to the source and is untouched. assets/logo-dark.png
keeps its light stroke, since replacing it with the source would paint a black mark on a dark
background.
The old lead described how the packages are built rather than what they fix. The headline now
names the problem, and the screenshot is replaced by a drawing of the panel that walks its six
tabs, so it themes with the page and weighs nothing.

The dot grid and the panel's tilt both read one pointer listener through two CSS variables.
Everything that moves sits inside prefers-reduced-motion.
The name said hero and the keyframes lived in the page's stylesheet, so rewriting the hero would
have taken the carousel with it. Both now sit beside the component, which knows nothing about
where it is mounted.

global.css keeps only what is the hero's: rise, drift, and the pointer-driven dots and tilt.
The copy sits over the preview now rather than beside it, and the preview is a window with a
sidebar instead of a phone: six tabs down the left, a filtered list and a detail pane on the
right, so each tab shows what it actually holds.

Sections below the fold arrive on scroll through animation-timeline, so there is no observer and
nothing runs on a page nobody scrolls. Browsers without it show the section outright.
The neutral preset ships a greyscale primary, so every text-fd-primary on the site was rendering as
plain foreground and nothing carried the brand. It is the logo's own blue now, with a lighter step
in dark mode for contrast.

The hero sits on a gradient sky with five layered ridges behind it, each arriving from its own
distance so the depth is in the entrance rather than only the scroll. Sky and ridge colours are one
set of variables per theme.
The hero is being rebuilt step by step, so page.tsx renders an empty fixed-height section while
that happens. Nothing was deleted: hero.tsx and its parts are still here and swapping the import
back restores them.

The header's border and the Libraries section's own border and tint are gone with it, so the nav,
the hero and the content below read as one surface instead of three bands.
Type, two actions and the devtools window on the page's own background. The sky, the ridges, the
dot grid, the pointer parallax and the panel tilt are gone with the components that drew them.

global.css is down to the brand primary, two scrollbar fixes and the two animations still in use.
It was rewritten rather than edited: a regex pass meant to strip the scenery rules left the file
with orphan braces, and patching around that would have been worse than working from what is
actually needed.
The sync script ran top to bottom for one hardcoded package, so it is a list and a function per
package now. Everything it already knew stays: the two Changesets bullet shapes, the three
published, pending and unknown states, and not claiming a version is unpublished when npm was
simply unreachable.

releases.generated.ts carries both packages, rewrites only when at least one answered and omits
any that did not, so a run made offline cannot wipe the versions the site knows.
The headline opens the page now. Gone from all three places it lived: the markup, the prose in
content.json and the Content type, so nothing is left pointing at a key that no longer exists.

Note that nothing above the fold names the platform any more. It survives in the install command
and in the library list below, where it is inferable rather than stated.
Docs and blog were handed SiteNav as a header slot, so the landing page's chrome rendered on every
page. Dropping the slot is enough: baseOptions already carries the title, the links and the GitHub
URL, so fumadocs' own navbar comes out with the same navigation rather than an empty bar.

Docs pages lose their top bar as a result, since fumadocs folds the links, search and theme toggle
into the sidebar above the page tree. That is its standard arrangement, not a gap.
Ten pages under Get started, Renderers, Guides and Reference, sourced from the package's own README
and its export surface rather than written from the outside.

Each renderer page carries its props table and the behaviours that would otherwise surprise you:
onCopy is what offers the copy actions at all, children start closed whatever defaultExpanded says,
an XML element opens only for a match inside it, an uncompilable pattern is no search rather than no
results, and monospace silently falls back to a proportional font on iOS.

Where the package deliberately does not do something the page says so, so a reader stops looking:
no bundled context menu, no scroller in CodeHighlight, no default primitives for React Native.

This also gives /docs/react-pretty-print/ a real index, which the library grid on the landing page
has been linking to since the package was published.
Both packages' trees were stacked in one list, so reading a react-pretty-print page meant scrolling
past every expo-devtools page first. Marking each folder as a root makes it a tab: you see the
library you are in, with a switcher to the other.

Two lines, and a third package gets the same by carrying the same key.
Pages are served under /docs, but the links were written without it, so every cross-reference in the
expo-devtools guides and reference was a 404.

The screenshots are deliberately untouched. Those really do sit at the root, in public/, so
prefixing them would have broken every image on the site to fix the links.
The card said "2,145/wk" and now says "2,145 downloads", summed from the package's first publish.

npm has no all-time endpoint and serves at most ~18 months of history, and it says nothing when it
truncates: ask for everything since 2022 and it answers for the last year and a half with a `start`
field you have to read to notice. So the count walks the timeline in 500-day chunks and adds them
up. Measured against @tanstack/react-query, one request answers 2.00bn and the walk answers 2.33bn,
so the naive version drops 330m downloads and looks entirely plausible doing it.

The range endpoint rather than the point one, because it returns a day at a time and the total is
then a sum of values you can inspect. Requests go one at a time with a pause and a retry, since this
runs per package on every build against a free API.

Credit: https://tanstack.com/blog/npm-stats-the-right-way
There were two. `lib/packages.ts` was written by hand and still listed only expo-devtools, while the
generated catalogue from npm had both, and each component read whichever it happened to import.

That is why the docs index showed one card, why search could only be filtered by expo-devtools, and
why those cards linked at /expo-devtools rather than /docs/expo-devtools.

Everything derives from the generated catalogue now, which is what the README already said this site
does: the library list is not written by hand. The hand-written file's own deletion is in ef022e3,
which is a mistake on my part, not a decision.
The sidebar said @axonpack/react-pretty-print and the cards said react-pretty-print. Both now say
React Pretty Print, along with the nav menus, the footer and the search chips.

One title() in the service, derived from the slug rather than carried as a field, because a second
hand-written name is a second thing to keep in step with the first.

It is the label, not the identity. The npm name stays wherever the npm name is the point: install
commands, npm links, and each package's own description. Search chip values stay slugs too, since
those filter by folder and renaming them would stop the filter matching anything.
@abappi19
abappi19 merged commit 2b85690 into main Sep 11, 2026
1 check failed
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