This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
The documentation site for the FullStackHero .NET starter kit, served at fullstackhero.net.
It is not the starter kit itself — only its docs. Built with Astro 6, Tailwind 4, MDX, and
deployed as a Cloudflare Worker that wraps a static build.
npm run dev # astro dev on http://localhost:4321 (hot-reload of MDX)
npm run build # astro build && pagefind --site dist (produces dist/)
npm run preview # serve dist/ locally
npm run check # astro check — type + content-schema validation- There is no test suite.
npm run checkis the closest thing to a CI gate — run it after touching.astro,.ts, layouts, or content schema. - Search (Pagefind) only works after
npm run build. Indev, the search modal reports "index not available" — this is expected, not a bug.
npx wrangler deploy # deploy worker + dist assets
npx wrangler types # regenerates worker-configuration.d.ts (gitignored)
npx wrangler d1 execute fsh-docs-views --file ./migrations/0001_init.sql --remote.dev.vars holds DEDUPE_SALT for local worker runs (gitignored). D1 (DB) and KV (DEDUPE)
binding IDs live in wrangler.toml.
This is the single most important thing to understand.
- Astro builds fully static to
dist/. There is no SSR adapter inastro.config.mjs— every page, including thellms-full.txtendpoint, is prerendered at build time. src/worker.tsis a standalone Cloudflare Worker (not an Astro adapter). It servesdist/through theASSETSbinding and adds one dynamic surface:/api/views/*. Static assets win over the worker by path, so/docs/**,/_astro/**, etc. never invoke worker code.
Consequences:
src/worker.tsis excluded fromtsconfig.jsonand typed against@cloudflare/workers-types, not Astro types.npm run checkdoes not validate it. Edit it carefully.- The page-view counter (
/api/views) writes to D1 (viewstable, seemigrations/0001_init.sql) and dedups per IP+UA via KV with a 1h TTL. Slugs are whitelisted to/docs/*only. - Anything dynamic must go through the worker; you cannot add an SSR Astro route and expect it to run — the build is static.
- One content collection,
docs, defined insrc/content.config.ts. A glob loader pulls every**/*.mdxundersrc/content/docs/. The Zod schema there is the source of truth for frontmatter (title,description,sidebar.{label,order,hidden},pageType,seo.*). - Routing is filesystem-driven through
src/pages/docs/[...slug].astro:index.mdx→/docs/<section>/index.mdx→ section overview at/docs/<section>/<section>/page.mdx→/docs/<section>/page/
- The sidebar is built in
src/helpers/sidebar.ts, iteratingsrc/content/docs/_sections.ts. This array defines section order and display labels. A directory not registered in_sections.tswill not appear in the sidebar even though its pages are still routable. When you add a new top-level docs section, register it there. sidebar.tsnormalizesentry.iddefensively because the id shape differs between Astro 5 and the Astro 6 glob loader (with/without.mdx, with/without/index). Preserve that logic.
BaseLayout.astro <head>: SEO meta, OG/Twitter, JSON-LD graph, fonts, theme bootstrap, GA
├── DocsLayout.astro Header + filesystem sidebar + ToC + sponsor card + mobile sheet + views beacon
└── MarketingLayout.astro landing-page chrome
- All pages route through
BaseLayout. It emits the site-wide JSON-LD@graph(Organization + WebSite + SoftwareApplication) and acceptsadditionalSchemasfor per-page structured data. [...slug].astrocomposes per-page SEO + aTechArticle+BreadcrumbListJSON-LD and passes them down viaadditionalSchemas. Section overview pages intentionally use the section label as the final breadcrumb, not the page title.- Google Analytics lives in
src/components/shell/Analytics.astro, rendered once inBaseLayout's<head>. It runs in dev too.
- Frontmatter shape is enforced by the schema in
src/content.config.ts—checkfails the build on violations.sidebar.orderis ascending (lower = higher in the list); ties break alphabetically. - Only components registered in
src/components/mdx.tsare usable inside MDX:Callout,CategoryIndex,CodeGroup,Screenshot,SectionIndex. To expose a new one in MDX, add it there. - The docs
<article>carriesdata-pagefind-body, which scopes the search index to docs content only.
astro.config.mjssitemap.serialize()assigns per-pathpriority/changefreqby URL prefix.public/robots.txtexplicitly allow-lists AI + search crawlers (GPTBot, ClaudeBot, PerplexityBot, …).public/llms.txtis the hand-maintained summary;src/pages/llms-full.txt.tsgenerates/llms-full.txtby concatenating the full MDX corpus at build time.public/also holds_headers,_redirects, favicons, and OG image as static passthroughs.
- Tailwind 4 via the Vite plugin (no
tailwind.config.js). Tokens, prose, and code-block themes insrc/styles/*.cssare forked verbatim fromcodewithmukesh/blog— preserve the provenance comments when editing. - Brand colors are split deliberately: primary is
#15803d(green-700); the brighter brand green#16a34alives in--primary-softfor accents/gradients. Don't collapse the two. - Code-block rendering is Expressive Code. Its full config is in
ec.config.mjs(function-valued options can't be inlined intoastro.config.mjs); the theme ishouston.theme.json.
- Inline
<script is:inline>(e.g. theme bootstrap, Analytics) is shipped verbatim — Astro does not bundle or typecheck it. Bundled client logic uses<script>import '...'</script>. - The site uses view transitions, so interactive scripts must be idempotent and re-bind on
astro:page-load/astro:after-swap(seeviews-counter.ts, the mobile sheet inDocsLayout.astro). Follow that pattern for any new client behavior or it breaks on navigation.
- Formatting is Prettier with
prettier-plugin-astro+prettier-plugin-tailwindcss(Tailwind class sorting is automatic). No standalone config file — plugin defaults apply. - TypeScript extends
astro/tsconfigs/strict; React JSX is enabled for.tsxislands. superpowers/is gitignored and not part of the site.