Top level
src/
├── lib/ # Shared code ($lib alias)
│ ├── auth/ # Auth helpers (error mapping, redirect guards)
│ ├── components/ # Svelte components (docs/, auth/, icons/, MDSvex/)
│ ├── config/ # plans.ts (Stripe), docs.ts (documentation registry)
│ ├── server/ # Server-only modules (Stripe client, service-role Supabase)
│ ├── posts/ # Blog posts (.md, mdsvex)
│ └── docs/ # Documentation pages (.md, mdsvex) — this site
├── routes/
│ ├── api/stripe/ # checkout, portal, webhook endpoints
│ ├── (app)/ # Authenticated shell: dashboard + auth flows
│ └── (marketing)/ # Public: home, pricing, blog, contact, legal, /docs
├── app.html # Shell with pre-hydration theme bootstrap
└── hooks.server.ts # Supabase session + route guards
supabase/migrations/ # SQL migrations (subscriptions table + RLS)
tests/smoke.spec.ts # Playwright e2e suite Route groups
SvelteKit route groups (app) and (marketing) share layouts without
affecting URLs:
(marketing)— public pages. Anything here is crawlable marketing surface: home, pricing, blog, contact, terms, privacy, and/docs.(app)— the authenticated shell./app/dashboard, billing, profile, plus the auth pages (/auth/*) which live in this group because they share its layout rather than the marketing chrome.
Server-only boundary
Anything under src/lib/server/ can only be imported by server code —
SvelteKit enforces this at build time. Two modules live there:
stripe.ts— lazily constructs the Stripe client fromSTRIPE_SECRET_KEYsupabase-admin.ts— service-role Supabase client used by webhooks to write subscription rows despite RLS
Configuration modules
src/lib/config/plans.ts— plan definitions consumed by pricing UI, checkout, and the webhook’s price→plan mappingsrc/lib/config/docs.ts— the documentation registry driving this section
Conventions
- One validation point for sessions:
hooks.server.tsvalidates each request once; every load readslocals.session/locals.user. - Config-driven pricing: plans exist in one file; checkout falls back to inline Stripe prices when no Price IDs are configured.
- Honest degradation: missing optional keys (Stripe, Resend) produce clear banners or dev-mode notices instead of fake success.