The nustack.dev website. One pnpm workspace, one app per section, composed into one static site at build time.
| Path | What | Serves |
|---|---|---|
landing/ |
@www/landing: Next app, home, the stack pages, spaces, about |
/ and every path no other zone claims |
docs/ |
@www/docs: Next + fumadocs, MDX in docs/content |
/docs |
blog/ |
@www/blog: Astro blog, MDX posts in blog/content, RSS, giscus comments |
/blog |
shared/ |
@www/shared: design tokens, page primitives, brand marks, nav pieces, zones, analytics |
- |
compose/ |
@www/compose: merges the builds into out/, site-wide search, dev proxy, preview server |
- |
tools/ |
docsgen: generates docs/content/reference/ from nu.inspect |
- |
Next 16 · React 19 · Tailwind 4 · TS, static export.
pnpm i
pnpm dev # whole site behind http://localhost:3000
pnpm dev:docs # one app on its own port (landing :3001, docs :3002, blog :3003)
pnpm build # every app, then compose -> out/
pnpm serve # preview out/ at http://localhost:4000, Pages-like
pnpm docs:gen # regenerate the reference docs (tools/.venv)Each app owns one path prefix (a zone) and builds on its own: its own engine, dependencies and config. Only docs depends on fumadocs, so its CSS never reaches the landing or the blog. Every app has its own root layout and stylesheet; they share the tokens from shared/design.
- Apps. A zone app builds a static
out/meant to be served under its prefix. For Next that isbasePathplusNEXT_PUBLIC_ZONEinnext.config.mjs; for Astro,baseplus the sameNEXT_PUBLIC_ZONEdefine inastro.config.mjs. The list of apps lives incompose/apps.mjs, the list of prefixes inshared/lib/zones.ts. - Links. Write hrefs as site-absolute paths (
/docs/x,/stack/nuspace).SiteLinkfrom the shared kit keeps links inside the current zone client-side and turns links into another zone into plain<a>, since that zone is a different build. Docs plugs the same resolver into fumadocs throughdocs/components/provider.tsx. - Theme. next-themes everywhere, same
themekey and.darkclass, so the chosen theme carries across zones. The blog also sets the class from an inline head script, since its pages are static HTML. - Search. compose runs Pagefind over the merged HTML, so the shared
SearchDialogsearches every zone. Pages opt in withdata-pagefind-body. Docs also keeps fumadocs' own search. In dev,/pagefindis served from the lastpnpm build.
pnpm build builds every app, then compose/build.mjs copies landing to out/ and every other app to out/<zone>, merges the apps' sitemaps into /sitemap.xml, copies the docs' llms.txt to the root and indexes everything with Pagefind.
pnpm dev starts every app's dev server and a proxy on :3000 that routes by prefix, HMR included.
Apps import it by path: @www/shared/components/page, @www/shared/lib/zones, @www/shared/design/tokens.css. It ships as TS source: each Next app compiles it (transpilePackages), the blog through Vite. The site chrome (nav, footer) and the stack registries it lists (lib/stack) live here, so every zone renders the same nav. The kit is written against next/link and next/navigation; the blog aliases both to small shims in blog/src/shims. Anything that names one app's own pages or copy stays in that app.
Push to main runs .github/workflows/deploy.yml: build every app, compose, publish out/ with GitHub Pages from this repo at nustack.dev. Work happens on dev; deploy by fast-forwarding main to dev.