Skip to content

Serve the docs under /docs, with the rest of pelican.dev on the Hub - #239

Draft
alexevladgabriel wants to merge 12 commits into
feat/fumadocs-nextfrom
feat/docs-under-docs
Draft

alexevladgabriel wants to merge 12 commits into
feat/fumadocs-nextfrom
feat/docs-under-docs

Conversation

@alexevladgabriel

@alexevladgabriel alexevladgabriel commented Nov 14, 2026 •

Copy link
Copy Markdown
Member

Stacked on feat/fumadocs-next: this PR only holds the change from "Next.js serves all of pelican.dev" to "Next.js serves the docs under /docs, and the Hub serves everything else".

Why

pelican.dev becomes one site. The Hub (Laravel) serves the homepage, /plugins, /eggs, /sponsor and the signed-in pages; this build serves only /docs/*, /llms*.txt and the install scripts. A router in front of the domain (a Cloudflare Worker, sketched in router/README.md) sends each path to the right place.

What changes

  • Everything loads from /docs/:
    • Assets use assetPrefix: '/docs'. The static export still writes out/_next/, so netlify.toml rewrites /docs/_next/* to it.
    • The search index is at /docs/search.json, Markdown copies at /docs/<page>.md, share images at /docs/og/..., and the logo and favicon at /docs/img/.
    • The old paths get 301s, for sites that link them.
    • There's a /docs/sitemap.xml for the docs, and robots.txt lists it next to the root sitemap.
  • Shared with the Hub:
    • ui/tokens.css (the V12 colours, fonts and dark tokens) and ui/nav.json (header, footer and call to action, with the Hub's paths as site-relative links), published at /docs/ui/v1/.
    • The docs bar and phone menu read their links from nav.json.
  • Checks:
    • scripts/check-paths.mjs runs after the build. It fails if a file lands outside out/docs/ (apart from the five root files, Netlify's own and the interim landing pages, each marked "remove after the switch"), or if a docs page loads anything from outside /docs/.
    • scripts/check-content.mjs runs before the build and in CI. It parses each page as MDX and fails on anything that would run code: import/export, {expressions} other than comments, components that mdx-components.tsx doesn't give pages, spread props, on* handlers, attribute values in braces that aren't literal data, and javascript:, vbscript: or data: URLs (quoted, in braces or in Markdown links). scripts/check-content.test.mjs (27 cases) runs in CI too.
  • Local router: router/dev-server.mjs (port 8020, no dependencies) serves /docs/* from out/ and proxies everything else to a local Hub, so the whole domain can be tried locally.
  • Dark theme by default, to match the Hub.

Interim

Until the router is in front of the domain, Netlify still serves all of pelican.dev. So the landing pages stay in this build, and netlify.toml sends /plugins and /sponsor to where they live today. check-paths lists each of those as interim.

Verification

On d05472d, after merging current main into both this branch and feat/fumadocs-next:

  • check-content: 35 pages pass; its 27 test cases pass (on a28ee17).
  • next build: 120 static pages.
  • check-paths: ok.
  • main's new docs (the health endpoint in panel/optional-config, and the Panel to Wings minimum version table in versioning) are in the built pages.

Not checked

  • The Cloudflare Worker itself. It's only sketched in router/README.md, and nothing here deploys it.
  • The production switch, where the Hub takes over every path but /docs. That's a DNS and Hub deploy step, not part of this repo.
  • Netlify's deploy preview of this branch, which should appear on this PR once Netlify builds it.

Note

Serve docs site under /docs and add local router, content checks, and shared UI endpoints

  • Restructures the static export so all docs content lives under /docs/: routes move to app/docs/, assets move to public/docs/img/, and Next.js assetPrefix is set to /docs in next.config.mjs. Netlify rewrites and redirects handle the new paths in netlify.toml.
  • Adds a local dev router (router/dev-server.mjs) that serves docs paths from the static export and proxies the rest of the site to the Hub.
  • Adds build-time validation: scripts/check-content.mjs blocks raw import/export statements and script tags in MDX, and scripts/check-paths.mjs verifies exported files land in the right locations. Both run via prebuild/postbuild in package.json.
  • Splits sitemaps: the root sitemap keeps only landing pages and a new app/docs/sitemap.ts generates /docs/sitemap.xml. Shared nav data and design tokens are centralized in ui/nav.json and ui/tokens.css, served at /docs/ui/v1/nav.json and /docs/ui/v1/tokens.css.
  • Behavioral Change: the site defaults to dark theme, search index moves to /docs/search.json, docs OG images move to /docs/og/, and all old logo/favicon/OG/search paths rely on Netlify redirects. The docs sitemap uses git last-modified dates for page freshness.
📊 Macroscope summarized d05472d. 29 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

pelican.dev will route only /docs/*, /llms*.txt and the install scripts to
this build; the Hub serves every other path.

- assetPrefix '/docs': pages load /docs/_next/...; the static export still
  writes out/_next/, so netlify.toml rewrites /docs/_next/* to it
- search index: app/api/search -> /docs/search.json (search client points there)
- Markdown copies: app/md -> app/docs/md (still served at /docs/<page>.md)
- docs share images: /og/docs/... -> /docs/og/...
- logo and favicon: /img/ -> /docs/img/
- /docs/sitemap.xml for the docs; /sitemap.xml keeps the landing pages
  (interim) and robots.txt lists both
- 301s from the old paths that other sites may link
- ui/tokens.css: the V12 custom properties, font variables and dark tokens,
  moved out of home.css and global.css (both now read this one file)
- ui/nav.json: header items, footer columns and the call to action, with the
  Hub's paths (/plugins, /eggs, /sponsor) as site-relative links
- both published at /docs/ui/v1/ for the Hub to copy
- the docs bar and phone menu read their links from nav.json; while Netlify
  still serves the whole domain, netlify.toml sends /plugins and /sponsor
  where they live today (interim)
- scripts/check-paths.mjs (postbuild, check:paths): every file in out/ must
  be under docs/, one of the five root files, Netlify's, or on the INTERIM
  list (landing pages, each marked remove after the switch); docs pages must
  not load scripts, styles, images or share images from outside /docs/
- scripts/check-content.mjs (prebuild, check:content, CI): fails on MDX
  import/export or <script in content/, skipping code blocks
router/dev-server.mjs (port 8020, no dependencies) serves /docs/* and the
five root files from out/ with Netlify's /docs/_next and .md mappings, and
proxies everything else to the Hub. router/README.md lists the paths and
sketches the Cloudflare Worker.
README.md: keep this branch's Next.js/Fumadocs instructions and take main's
new intro with the Panel and Wings repo links (without "created with
Docusaurus"). main's health endpoint and Wings version policy edits applied
to the moved pages in content/docs/ without conflicts.
@coderabbitai

coderabbitai Bot commented Nov 14, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Comment thread scripts/check-content.mjs Resolved
… <script

The content check only looked for import/export and <script, but MDX also
compiles {expressions} and {attribute values} into the site's JavaScript, so a
page with {(() => { while (true) {} })()} passed and would hang the build.

Each page is now parsed as MDX and checked against an allowlist: no
import/export, no {expression} except comments, only the components
mdx-components.tsx gives pages and a short list of HTML tags, no spread props,
on* handlers or dangerouslySetInnerHTML, attribute {values} only as literal data,
and no javascript:/vbscript:/data: URLs in JSX or Markdown links. All 35 pages
pass. scripts/check-content.test.mjs (pnpm test:content, in CI) covers 17 pages
that must fail and 7 that must pass.

An indented import/export is not blocked: @mdx-js/mdx 3 compiles it as a
paragraph of text, so the old comment saying otherwise was wrong.
Comment thread scripts/check-content.mjs Resolved
A braced value like href={'javascript:alert(1)'} passed as literal data
without its string being checked. Every string in literal data, nested
ones included, now goes through the same URL check as quoted values.
Kept this branch's scripts (check-content, check-paths, preview:site)
with bun run, regenerated bun.lock with the MDX parser packages, and
switched the remaining pnpm commands. Adds the Preview build workflow:
it publishes the built /docs to preview/docs-under-docs for the Hub's
preview deployment.
Comment thread router/dev-server.mjs Resolved
…hort

hubRes.pipe(res) leaves res open if the Hub's response ends early, so the
browser waited until it timed out. stream.pipeline closes it (and stops
the Hub's response if the browser leaves).
Comment thread router/dev-server.mjs Resolved
createReadStream(full).pipe(res) left read errors unhandled, so a file
that couldn't be opened after stat() killed the router and with it the
Hub proxy. stream.pipeline ends that one response instead. The request
body now goes to the Hub through pipeline too, so a browser that aborts
an upload can't crash it either.

This branch has not been deployed

No deployments
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