Project Structure
What each directory owns, and where to make changes
Last updated October 21, 2026
This page is a map. When you cannot find something, look here first, then read the code.
Directory map
src/
├── routes/ File routes. `{-$locale}` is the optional locale prefix
├── config/ Site configuration
│ ├── website.ts Global switches: name, logo, module and feature flags
│ ├── navigation.ts Menu types and filtering rules
│ ├── navbar-config.ts Navbar
│ ├── footer-config.ts Footer
│ └── settings.ts Dashboard sections
├── components/
│ ├── sections/ Reusable content blocks (Hero / Features / Pricing…)
│ ├── marketing/ Page-level assembly (LandingPage / MarketingLayout / Navbar)
│ ├── docs/ Docs layout, sidebar, search
│ ├── dashboard/ Dashboard shell
│ ├── layout/ Cross-page pieces (Logo / locale switcher / user menu)
│ ├── blog/ auth/ contact/ mdx/ theme/ icons/
│ └── ui/ shadcn upstream originals, never edited locally
├── auth/ Authentication (Better Auth)
├── cache/ KV cache and rate limiting
├── contact/ Contact form delivery
├── db/ Drizzle schema and migrations
├── env/ Two-layer env validation
├── lib/ Pure logic: locale, seo, module-access, rate-limit…
├── locale/ Paraglide output and middleware
├── mail/ newsletter/ notification/ payment/ storage/
│ Service modules: provider registry + interface
├── styles/ globals.css (design tokens), mdx.css
├── server.ts Custom server entry (CSP nonce, env injection)
└── router.tsx Router instance
content/ Docs / blog / changelog / legal (Markdown)
messages/ zh.json / en.json UI copy
public/docs/screenshots/ UI screenshots referenced from docs
scripts/ Build and validation scripts
tests/ unit / components / workers
e2e/ PlaywrightWhere to change things
| Want to change | Go to |
|---|---|
| Site name, logo, module switches | src/config/website.ts |
| Home / about / pricing copy | The route component, e.g. src/routes/{-$locale}/index.tsx |
| Button labels and UI copy | messages/zh.json, messages/en.json |
| Navbar and footer links | src/config/navbar-config.ts, footer-config.ts |
| Docs content | content/docs/*.md (Chinese uses .zh) |
| Colors, fonts, radii | src/styles/globals.css, rules in Design System |
| Add a page | Create a file under src/routes/; the router picks it up |
| Add an API route | src/routes/api/<name>/$ or src/routes/api/<name>.ts |
| Change a table | src/db/schema.ts, then pnpm db:generate |
| Add a service module | Mirror src/mail/: types.ts + provider + index.ts |
| Add a UI screenshot to docs | Put the image in public/docs/screenshots/, reference it as  |
Two boundaries that are easy to confuse
`components/sections/` vs `components/marketing/`
sections/holds reusable content blocks: a Hero, a set of Features. Given props,
they render; they do not care which page they are on.
marketing/holds page-level assembly:LandingPagecomposes sections in order,
MarketingLayout owns the navbar and footer.
Route components vs `config/website.ts`
website.tsis global switches and identity: what the site is called, which modules
are on.
- Page copy lives in the route component, right next to the block that renders it.
Always call m.*() during render, never at module top level: Paraglide reads the current locale at call time, so a top-level constant freezes the language of the first request for every later one (SSR renders the whole page in the wrong language, the client then derives another language from the URL, and hydration mismatches).
Generated files — do not edit
| Path | Generated by |
|---|---|
src/routeTree.gen.ts | TanStack Router plugin |
src/locale/paraglide/** | pnpm locale:compile |
.content-collections/generated/** | pnpm content:build |
All three are rewritten at build time; manual edits are overwritten.