Project Architecture
Directory layout, conventions, and where to find things in the Zutra codebase.
Directory layout
zutra-saas/
├── astro.config.mjs ← Astro integrations + i18n config
├── tailwind.config.* ← Tailwind v4 (configured via @theme in global.css)
├── tsconfig.json ← Strict TS, extends astro/tsconfigs/strict
├── package.json ← pnpm + scripts (dev / build / test / lint)
├── public/ ← Static assets served as-is (favicons, OG images, logos)
├── scripts/ ← Build-time scripts (icon generation, OG generation)
├── src/
│ ├── pages/ ← File-based routes
│ ├── components/ ← Astro components
│ ├── layouts/ ← Layout.astro (marketing) + AppLayout.astro (dashboard)
│ ├── content/ ← Content collections (blog, docs, changelog, …)
│ ├── content.config.ts ← Zod schemas for every collection
│ ├── config/ ← The files you edit (site, pricing, features, …)
│ ├── i18n/ ← en/ + es/ (5 files each) + helpers
│ ├── lib/ ← API helpers, rate limiting
│ ├── stories/ ← Storybook stories for UI primitives
│ └── styles/
│ └── global.css ← Design tokens (@theme block) + base reset
└── docs/ ← Deployment guides (Vercel, Cloudflare)
Pages
src/pages/ uses Astro’s file-based routing.
- Files at the root of
src/pages/are English routes:pages/pricing.astro→/pricing - Files under
src/pages/es/are Spanish routes:pages/es/pricing.astro→/es/pricing - Dynamic routes use brackets:
pages/blog/[slug].astro,pages/docs/[...slug].astro - API routes live under
src/pages/api/:api/waitlist.ts,api/contact.ts,api/auth/login.ts
The i18n config (astro.config.mjs) uses prefixDefaultLocale: false, which is why English routes don’t have a /en/ prefix.
Components
Components live under src/components/, grouped by feature:
src/components/
├── ui/ ← Reusable primitives (Badge, Button, Card, Input, Logo, Select, Alert)
├── auth/ ← Auth forms (Login, Signup, ForgotPassword, SuccessReceipt)
├── blog/ ← Blog-specific (BlogGrid, BlogHeader)
├── changelog/ ← Changelog-specific (ChangelogHeader, ChangelogEntry)
├── contact/ ← Contact form + channels
├── customers/ ← Customer page (CustomersHero)
├── dashboard/ ← Dashboard sub-components (Sidebar, Overview, etc.)
├── home/ ← Homepage sections (Hero, FeaturesSection, PricingSection, …)
├── open/ ← /open page (TechStack, OpenHero)
├── pricing/ ← Pricing page (PricingHero, FeatureMatrix)
├── status/ ← Status page (ServicesList, IncidentsList)
├── roadmap/ ← Roadmap page (RoadmapList, RoadmapHero)
└── about/ ← About page (MissionSection, TeamSection, ValuesGrid, SocialLinks)
When adding a new feature, create a subdirectory rather than dumping into src/components/.
Layouts
Two layouts wrap every page:
Layout.astro— marketing pages. IncludesSiteHead(SEO, fonts, OG),SiteNav,SiteFooter,CookieBanner,PurchaseBanner. Used by every page except the dashboard.AppLayout.astro— dashboard pages. Minimal chrome — no nav, no footer. JustDashboardSidebar+ main content area.
When you create a new page, decide: marketing page or app page? That tells you which layout to import.
Config (the files you edit)
Everything that’s a string, URL, color, or toggle lives in src/config/:
| File | What it controls |
|---|---|
site.ts |
Name, tagline, URL, social handles, OG image, theme color |
pricing.ts |
Plan definitions, features, CTAs |
features.ts |
Feature list rendered on the homepage |
nav.ts |
Top nav links + dropdown |
hero.ts |
Hero copy, simulator window metrics/logs/deploy steps |
integrations.ts |
Logos and labels for the integration section |
index.ts |
Re-exports everything as a single CONFIG object |
Rule: if you find a string hardcoded outside
config/, that’s a bug — open an issue (or fix it).
Content collections
src/content/ is where Markdown/JSON content lives, organized by collection:
blog/*.md— blog postsdocs/*.md— this documentationchangelog/*.json— release notesfaq/*.json— FAQ itemsfeatures/*.json— feature list (alternative tosrc/config/features.ts)testimonials/*.json— customer quotesroadmap/*.json— public roadmap itemsteam/*.json— team membersvalues/*.json— company valuesservices/*.json— system status servicesmetrics/*.json— open-page metricscase-studies/*.json— customer case studiesstack/*.json— tech stack entriescontact-channels/*.json— contact page channels
Every collection has a Zod schema in src/content.config.ts — frontmatter is validated at build time.
i18n
Two locales out of the box: en and es. Translations are split into 5 focused files per locale inside src/i18n/en/ and src/i18n/es/ (nav.json, hero.json, sections.json, auth.json, app.json). All files are merged at build time by index.ts — components use the same t() function unchanged. See the i18n Guide for adding a third language.
Styles
src/styles/global.css is the single source of design truth. It defines:
- The
@themeblock (Tailwind v4 tokens): colors, fonts, radii - The base reset
- Reusable utility classes:
.bg-grid,.card-border,.card-border-highlighted,.btn-primary,.pulse-dot,.text-gradient-white,.text-display,.text-display-sm,.section-divider
When you add a new color or radius, put it in @theme so Tailwind picks it up as a utility class automatically.
Scripts
scripts/ runs at build time:
generate-icons.mjs— regenerates the curated Phosphor icon setgenerate-og.mjs— generates per-page OG images
Run with pnpm tsx scripts/<filename>.mjs.
Scripts (npm)
Top-level npm scripts:
pnpm dev # Astro dev server on :4321
pnpm build # Production build → dist/
pnpm preview # Preview the production build
pnpm test # Vitest unit tests
pnpm lint # ESLint
pnpm typecheck # astro check + tsc
pnpm storybook # Component explorer on :6006
Conventions
- Edit
config/before components — copy, branding, pricing all live there. - Components are dumb — they read from config and render. They don’t fetch data themselves.
- Content collections for text-heavy content — blog posts, changelog, docs, FAQ. Not config.
- i18n via
t()— never hardcode user-facing strings in components. - Tailwind utilities over custom CSS — the
<style>block in a.astrofile is for animation/keyframes only. - No inline styles — except for truly dynamic values (CSS custom properties, computed colors).
Next steps
- Configuration Reference — what every
src/config/*.tsfile does - Content Authoring — schemas for every collection
- Components — the UI primitives and how to extend them