Skip to content

Astro -> rwsdk#1061

Open
joefairburn wants to merge 55 commits intoredwoodjs:mainfrom
joefairburn:astro-to-rwsdk-v1
Open

Astro -> rwsdk#1061
joefairburn wants to merge 55 commits intoredwoodjs:mainfrom
joefairburn:astro-to-rwsdk-v1

Conversation

@joefairburn
Copy link
Contributor

Summary

Migrates the docs site from Astro/Starlight to RedwoodSDK, so the documentation itself runs on the same framework it documents.

The previous Astro site was a static build. This rewrite makes the docs a real RedwoodSDK application — server-rendered with React Server Components, deployed as a Cloudflare Worker, using the same router and patterns developers will find in the guides. fumadocs provides the documentation-specific primitives (sidebar, TOC, MDX processing, page layouts) on top of rwsdk's runtime.

Why fumadocs?

fumadocs is the best-in-class documentation framework in the React ecosystem — it powers the docs for shadcn/ui, Better Auth, and Zod. It was chosen because it provides many of the features and components that Astro/Starlight did — sidebar navigation, TOC extraction, MDX processing, docs page layouts — which significantly reduces the overhead of the migration.

It also serves as a showcase: integrating a mature, popular library like fumadocs demonstrates how straightforward it is to bring existing React ecosystem tools into an rwsdk application.

What changed

  • Astro removed entirelyastro.config.mjs, Starlight config, custom.css, content.config.ts all deleted
  • rwsdk app structureworker.tsx entry point with defineApp, Document.tsx shell, route-based rendering via render/layout/route
  • Custom sidebar — built with Base UI Collapsible for animated folder open/close, derived from fumadocs page tree
  • Theme toggle — dark/light/system with cookie persistence via server action
  • MDX compatibility layer — Vite aliases redirect old @astrojs/starlight/components imports to our shim components (Badge, Steps, Tabs, etc.) so existing MDX content works without mass-editing
  • SEO — per-page <title>, Open Graph/Twitter meta tags, robots.txt, XML sitemap
  • Security headersX-Frame-Options, CSP, X-Content-Type-Options via middleware
  • LLM-friendly docs/llms.txt (index) and /llms-full.txt (full markdown content) endpoints using fumadocs' getText('processed')

What's NOT in this PR

  • Search integration (planned follow-up)
  • Full content migration — existing MDX files are reused as-is with compatibility aliases

Test plan

  • Verify all doc pages render at localhost:5173
  • Check sidebar navigation and active state highlighting
  • Test dark/light/system theme toggle persists across page loads
  • Confirm mobile nav hamburger menu works
  • Verify /sitemap.xml, /robots.txt, /llms.txt, /llms-full.txt return correct content
  • Deploy to Cloudflare Workers and verify production build
  • Spot-check SEO meta tags in page source

Replace Astro/Starlight with a RedwoodSDK-based docs site using
content-collections for MDX processing. Adds app shell (Document,
Sidebar, pages), component stubs for MDX rendering, and Cloudflare
Workers configuration.
…ibility

@content-collections/mdx uses new Function() at runtime which is blocked
by Cloudflare Workers. Switch to @mdx-js/rollup which compiles MDX to
React components at build time via Vite.

- Add @mdx-js/rollup plugin with remark-frontmatter support
- Alias Astro imports to local stub components via Vite resolve.alias
- Move MDX stub components into components/mdx/ with barrel index
- Simplify content-collections to metadata-only (no body compilation)
- Use import.meta.glob to eagerly load MDX components in DocPage
…l icons

Remove the top bar in favor of a unified sidebar containing the logo,
search input (sticky top), scrollable navigation, and Discord/GitHub
icons (sticky bottom).
…cs-ui

Replace @content-collections and custom sidebar/layout with fumadocs ecosystem:
- Add fumadocs-core, fumadocs-mdx, fumadocs-ui dependencies
- Create source.config.ts and source loader for fumadocs-mdx
- Add meta.json files for sidebar page ordering
- Create custom FrameworkProvider for RedwoodSDK compatibility
- Wrap app with fumadocs RootProvider (theme disabled for CSP)
- Add scan-entries file for RedwoodSDK directive discovery
- Rewrite DocPage to use DocsLayout/DocsPage/DocsBody
- Adapt MDX components to fumadocs equivalents (Callout, Cards, Tabs, Steps)
- Fix code block language identifiers in email docs
- Remove numbered file prefixes (ordering via meta.json)
- Delete unused Sidebar.tsx, sidebar.ts, content-collections.ts, Code.tsx
… open/close

Replace manual useState toggle with @base-ui/react Collapsible component
for sidebar folders. Add height and opacity transitions, chevron rotation
via group data attributes, and switch className conditionals to clsx.
…c logos

Switch Discord and GitHub icons to inline SVGs, removing the lucide-react
dependency. Use theme-aware logo images from the public folder.
…w shim

Replace fumadocs DocsLayout component with a plain grid layout since the
custom Sidebar handles all navigation. Remove the scroll-into-view-noop
workaround and its Vite alias as it's no longer needed.
- Remove patch-history.ts (unnecessary — rwsdk re-renders full RSC tree on navigation)
- Remove fumadocs-scan-entries.ts workaround, use forceClientPaths in vite config instead
- Switch DocPage to standard fumadocs page.data.body pattern
- Use fumadocs DocsLayout component in DocsLayoutWrapper
- Use navigate() from rwsdk/client instead of createElement("a").click()
- Add prefetch support to Link component
- Fix non-null assertions on requestInfo with proper guards
- Align Referrer-Policy with documented recommendation (strict-origin-when-cross-origin)
- Add Secure flag to theme cookie
- Add except() error handler before middleware in worker
- Add OG/Twitter meta tags and favicon to Document
- Add error handlers to initClient for better debugging
… docs

- Add X-Frame-Options: DENY header to match documented recommendation
- Remove unused Code compatibility shim from MDX barrel export
- Remove unused Code imports from database.mdx and realtime.mdx
- Fix RouteMiddleware import path in security.mdx (rwsdk/router not rwsdk/worker)
- Replace legacy rwsdk() API with defineApp() pattern in security.mdx
… Analytics

Match the old Astro docs head 1:1 — adds canonical URLs, og:locale,
og:site_name, og:url, richer OG/Twitter descriptions, dynamic sitemap.xml
route, robots.txt, Simple Analytics tracking, and updates CSP accordingly.
Avoid documentation copy changes on this branch.
Enable the fumadocs lastModified plugin to inject git-based timestamps,
and render EditOnGitHub + PageLastUpdate components on every doc page.
Add MobileNav component using Base UI Dialog for responsive navigation.
Fix forceClientPaths with extglob patterns to exclude framework-specific
files importing uninstalled optional peer deps (next, waku, react-router,
@tanstack/react-router). Exclude mdx/mdx.server from forceClientPaths to
fix CodeBlock 'use' error caused by registerClientReference receiving a
plain object instead of a React component. Add directiveScanBlocklist to
prevent re-adding excluded files.
Consolidate base-ui and fumadocs-ui patterns into a single entry and
update comments to reflect that the issue is build-time MDX imports
rather than import.meta.glob visibility.
…, and polish UI

- Add /llms.txt and /llms-full.txt routes for LLM-friendly documentation
- Enable includeProcessedMarkdown in fumadocs source config
- Remove ErrorBoundary (incompatible with rwsdk SSR) and EditOnGitHub/PageLastUpdate
- Fix SSR pathname context for correct active sidebar highlighting
- Add per-page SEO meta tags (og:title, twitter:title, etc.)
- Improve light mode support for Badge component
- Validate YouTube embed IDs, fix sidebar keys, harden CSP for data: URIs
- Rename Provider to AppProviders, remove unused links.ts
@peterp
Copy link
Member

peterp commented Feb 23, 2026

Duuude this is incredible! Thank you!

Wire up full-text search using Fumadocs' createFromSource with Orama.
The search index is built server-side on the Worker and queried via
/api/search. DefaultSearchDialog provides Cmd/Ctrl+K hotkey, result
grouping by page, and keyboard navigation. Search trigger buttons
are added to both desktop sidebar and mobile nav.
Replace fumadocs search layer (which depends on @radix-ui/react-dialog
and next-themes) with native Orama indexing on the Worker and a custom
@base-ui/react Dialog on the client.

- Rewrite /api/search to build Orama index from source.getPages()
  structured data (page, heading, and content entries)
- Replace SearchDialog with own SearchProvider context, Cmd+K listener,
  and fetch-based search with debouncing
- Update provider to wrap children in SearchProvider instead of passing
  search config to fumadocs RootProvider
- Update Sidebar to use useSearch from our context
…ialog crash

The fumadocs RootProvider automatically wraps children in its own
SearchProvider (which lazy-loads DefaultSearchDialog depending on
@radix-ui/react-dialog) unless search.enabled is explicitly false.
This was silently breaking the render tree and preventing our custom
SearchProvider context from working.
Replace manual Dialog + useState approach with base-ui's Autocomplete
component inside a Dialog, following the COSS command palette pattern:

- Autocomplete.Root with open, inline, filter={null} (server-side
  search), autoHighlight="always", keepHighlight
- Autocomplete.Input for the search field
- Autocomplete.List with render function for results
- Autocomplete.Item with onClick for navigation
- Autocomplete.Empty for loading/empty states
- Dialog.Viewport for proper centering
Drop the broken context-based approach. SearchCommand now follows the
COSS command palette pattern: Dialog.Root wraps Dialog.Trigger (the
search button itself) + Dialog.Portal (the popup), exactly like
MobileNav already does. No external context or provider needed.

- SearchCommand is fully self-contained: own state, trigger, portal
- Desktop sidebar gets enableShortcut for Cmd+K
- Remove SearchProvider from AppProviders (no longer needed)
- Add fumadocs keyframe animations (fd-dialog-in/out) to search popup
- Add backdrop blur to search dialog overlay
- Extract base-ui ScrollArea component to components/ui/
- Replace fumadocs ScrollArea with base-ui ScrollArea in Sidebar
- Match search input sizing to sidebar button
- Add mono font to Esc button and fix ⌘ glyph vertical alignment
…em navigation

Switch to @orama/plugin-qps for proximity-based ranking, return truncated
body content for text results with heading breadcrumbs, add match
highlighting, and fix Autocomplete item click navigation.
Remove debug console.log, fix unused variable, use non-global regex
for HighlightMatch test(), and run prettier across all changed files.
Consolidate duplicated icon button class strings into buttonVariants()
helper and move 7 inline SVG icon definitions into a shared Icon.tsx
module, reducing boilerplate across Sidebar, SearchDialog,
CopyMarkdownButton, and ThemeToggle.
Use <Button variant="ghost" /> via Base UI's render prop for
Dialog.Trigger and Dialog.Close, and directly for CopyMarkdownButton.
Keeps buttonVariants() export for <a> elements.
Button now accepts a render prop to swap the underlying element,
matching Base UI's convention. Sidebar links use
<Button render={<a />} /> instead of buttonVariants().
Replace hand-rolled render prop logic with Base UI's Button component
which provides render prop support, prop merging, and accessibility
out of the box.
…av from right

Move the logo and search icon into the always-visible sticky header on
mobile, and change the navigation drawer to slide in from right-to-left
instead of left-to-right.
Replace stateful Base UI Switch with a plain button driven by Tailwind
dark: variants. Set the dark class server-side via Sec-CH-Prefers-Color-Scheme
client hint, falling back to a blocking script only when the preference
is unknown. Remove data-theme attribute and initialTheme prop drilling.
@peterp
Copy link
Member

peterp commented Feb 26, 2026

This is progressing wonderfully!

…ions

Replace Expressive Code meta syntax (mark, ins, del, word highlights)
with native Shiki inline annotations across 18 MDX files. Add minimal
custom transformer for features without native equivalents: collapse,
withOutput, and footnotes (--- delimited code block footers). Remove
frame="none" support (unnecessary with Shiki/fumadocs).
…migration issues

- Implement withOutput in custom Shiki transformer: splits code blocks on
  first empty line into command (with $ prompt) and output sections, with
  copy-to-clipboard only copying the command
- Fix Response.redirect word highlight (Shiki splits across tokens) by
  using line highlight instead
- Fix initClientNavigation() word highlight by dropping parentheses
- Remove > prefix from storage.mdx withOutput block (broke bash tokenizer)
- Add nativeButton={false} to Button components rendering as <a> elements
Map the warm parchment/charcoal light mode and "Zip" dark mode palette
from the main site onto fumadocs CSS variables. Add Inter and JetBrains
Mono via Google Fonts with CSP allowlisting.
…ine numbers

- Remove Playfair Display, use Inter for all text
- Remove duplicate @tailwindcss/typography plugin conflicting with fumadocs
- Fix search highlight using stale text-brand-orange token
- Use baige for light mode card/secondary backgrounds
- Use dark-panel for dark mode cards, dark-secondary for callout accents
- Remove box-shadow from code blocks and callouts
- Right-align code block line numbers
- Tighten hr spacing to 1em
…ports

- Replace all 51 Astro package imports (@astrojs/starlight/components,
  starlight-package-managers, astro-embed) across 35 MDX files with
  direct imports from @/app/components/mdx
- Merge duplicate import lines where files had multiple Astro imports
- Remove Astro alias entries from vite.config.mts
- Add @/ path alias to vite config (mirrors tsconfig, needed since
  rwsdk doesn't use vite-tsconfig-paths)
- Add module-stub.ts to alias unused fumadocs optional peer deps
  (flexsearch, @takumi-rs/image-response) that crash rwsdk's client
  barrel when eagerly loaded
- Update comments to better explain forceClientPaths and directiveScanBlocklist
Match the previous Astro docs behavior where code blocks for
js, javascript, ts, typescript, css, tsx, and jsx automatically
show line numbers without needing explicit `lineNumbers` meta.
… fixes

Add unit tests for parseRanges, parseMeta, and parseInlineMarkdown.
Refactor parseMeta to use simpler matching, export testable functions,
remove unused Image Optimization sidebar entry, and fix realtime link.
…rn statements

- Add nonce attribute to dark-mode inline script so it passes CSP in production
- Replace hardcoded localhost:4321 URL with relative path in sending-email guide
- Add missing return statements to WelcomeEmail code examples in email-templates guide
…ge styling

Add `experimental` frontmatter field to schema and render a caution badge
inline with the page title when set. Fix Badge component to accept `type`
prop (used in MDX) as alias for `variant`, update styling with more x-padding,
less border radius, and vertical alignment.
@peterp
Copy link
Member

peterp commented Mar 21, 2026

@joefairburn This looks like it's almost ready to merge!?

@joefairburn joefairburn marked this pull request as ready for review March 21, 2026 21:29
@joefairburn
Copy link
Contributor Author

@peterp - yep, it's in a good place and it's definitely feeling like an improvement over the current docs. always more we can do, but it can be iterated on!

@peterp peterp closed this Mar 21, 2026
@peterp peterp reopened this Mar 21, 2026
@peterp
Copy link
Member

peterp commented Mar 21, 2026

This looks ready to merge! Gonna test it out locally and then I'll ship it, thanks so much for doing thsi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants