diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md new file mode 100644 index 0000000..0f46b95 --- /dev/null +++ b/.planning/codebase/ARCHITECTURE.md @@ -0,0 +1,97 @@ +# Architecture + +**Analysis Date:** 2026-04-10 + +## SSR Strategy + +Nuxt 4 with `ssr: true` and `compatibilityVersion: 4`. Every page renders server-side HTML with SEO metadata before hydrating client-side. Cookie-only persistence for locale and theme (no localStorage, SSR-safe). + +## Layer Breakdown + +``` +Pages (app/pages/) + └─> Layout (app/layouts/default.vue) + ├─> AppHeader (nav, locale toggle, theme toggle) + ├─> Page content (slot) + └─> AppFooter (social links, copyright) + └─> Components (app/components/) + └─> Composables (app/composables/) + └─> Static Data (app/data/) + └─> Shared Types (shared/types/) + +Server (server/api/) + └─> Contact POST handler (nodemailer SMTP) +``` + +## Data Flow + +### Static Data + i18n +1. `app/data/projects.ts` exports projects WITHOUT translatable fields (title, description, longDescription omitted) +2. `app/composables/useProjects.ts` merges static data with i18n translations at runtime via `computed()` +3. Components consume `useProjects()` which returns reactive translated data +4. Language changes trigger recomputation automatically + +### SSR Render Flow +1. Request hits Nitro server +2. Nuxt resolves locale from cookie (`i18n_redirected`) or URL prefix (`/en/`) +3. `useLocaleHead()` in `app.vue` sets `` and alternate links +4. Page's `useSeoMeta()` resolves i18n keys server-side +5. `useHead()` injects JSON-LD structured data +6. Full HTML sent to client with correct locale, theme class, SEO metadata + +### Theme Resolution +1. `@nuxtjs/color-mode` reads `nuxt-color-mode` cookie +2. Default: `dark` for new visitors +3. Cookie persistence — no flash on cold load (class applied server-side) + +### Contact Form Flow +1. Client: Zod validation in `ContactForm.vue` +2. POST to `/api/contact` (Nitro route) +3. Server: manual validation, nodemailer SMTP via `useRuntimeConfig()` env vars +4. Response: success/error JSON + +## Module System + +| Module | Purpose | +|--------|---------| +| `@nuxt/ui` | Component library (Nuxt UI v3) | +| `@nuxtjs/i18n` | Internationalization (prefix_except_default, FR default) | +| `@nuxtjs/sitemap` | Auto-generated sitemap with i18n alternates | +| `nuxt-gtag` | Google Analytics (runtime config) | +| `@nuxt/image` | Image optimization | +| `@nuxt/eslint` | ESLint integration | + +## Entry Points + +| File | Role | +|------|------| +| `app/app.vue` | Root — wraps in ``, applies `useLocaleHead()` | +| `app/layouts/default.vue` | Default layout — AppHeader + slot + AppFooter | +| `app/error.vue` | Global error handler (404 page) | +| `nuxt.config.ts` | App configuration | +| `app.config.ts` | Nuxt UI theme tokens (primary color) | + +## State Management + +No Pinia store. All state is: +- **Composable-scoped:** `useProjects()` returns reactive computed data +- **Module-managed:** locale via `@nuxtjs/i18n`, theme via `@nuxtjs/color-mode` +- **Component-local:** `ref()` / `reactive()` in `