Compare commits

...

5 Commits

9 changed files with 1081 additions and 58 deletions
+15
View File
@@ -0,0 +1,15 @@
# Milestones
## M1 — Portfolio Hytale-first, SEO-ready, production
**Version:** v1.0
**Completed:** 2026-04-21
**Phases:** 4
**Delivered:**
- Hero Hytale-first avec H1 "Hytale Plugin Developer"
- Page `/hytale` avec pricing 3 tiers, témoignages
- SEO complet : canonical, ogUrl, og:image, JSON-LD, sitemap dynamique
- i18n bilingue FR/EN audit complet
- Dockerfile SSR pnpm, rate limiting contact form
- Déployé en production sur killiandalcin.fr
+12 -1
View File
@@ -8,6 +8,17 @@ Portfolio professionnel de Killian' Dalcin, developpeur freelance specialise en
Le portfolio doit positionner Killian comme LE developpeur de plugins Hytale professionnel — pas un "dev web freelance generique" perdu parmi 500 000 autres. Chaque page doit etre crawlable sans JavaScript (SSR), avec un SEO optimise pour le marche Hytale. Le portfolio doit positionner Killian comme LE developpeur de plugins Hytale professionnel — pas un "dev web freelance generique" perdu parmi 500 000 autres. Chaque page doit etre crawlable sans JavaScript (SSR), avec un SEO optimise pour le marche Hytale.
## Current Milestone: v1.1 — SEO Hytale — Autorité & Contenu
**Goal:** Dominer les requêtes Hytale sur Google via un blog markdown complet (tutos, guides, news) combiné à un SEO on-page renforcé — deux leviers pour ranker sur les mots-clés directs ET capter le trafic longue traîne.
**Target features:**
- Blog markdown avec renderer complet (syntax highlighting, images, embeds, tables, alerts)
- Articles Hytale bilingues FR/EN (tutos, guides, contenus communauté)
- SEO par article : JSON-LD Article, og:image, canonical, sitemap étendu
- Cocon sémantique : liens internes blog ↔ page /hytale
- Open Graph peaufiné par article
## Requirements ## Requirements
### Validated ### Validated
@@ -39,7 +50,7 @@ Le portfolio doit positionner Killian comme LE developpeur de plugins Hytale pro
### Out of Scope ### Out of Scope
- Tests automatises — priorite au shipping, tests si necessaire apres - Tests automatises — priorite au shipping, tests si necessaire apres
- Blog/CMS — pas de contenu dynamique pour l'instant - Blog/CMS — promu en Active pour M1.1 (blog markdown statique)
- Dashboard admin — portfolio statique - Dashboard admin — portfolio statique
- PWA/Service Workers — pas de besoin offline - PWA/Service Workers — pas de besoin offline
- Pub payante — budget zero - Pub payante — budget zero
+70 -45
View File
@@ -1,82 +1,107 @@
# Requirements: Portfolio Killian' Dalcin # Requirements: Portfolio Killian' Dalcin
**Defined:** 2026-04-10 **Defined:** 2026-04-10
**Updated:** 2026-04-21 (v1.1 added)
**Core Value:** Positionner Killian comme dev Hytale #1, crawlable sans JS, SEO optimise **Core Value:** Positionner Killian comme dev Hytale #1, crawlable sans JS, SEO optimise
## v1 Requirements ## v1 Requirements (M1 — Complété 2026-04-21)
### Content ### Content
- [ ] **CONT-01**: Refonte Hero accueil — "Hytale Plugin Developer" en H1, CTA Discord/contact, bilingue - [x] **CONT-01**: Refonte Hero accueil — "Hytale Plugin Developer" en H1, CTA Discord/contact, bilingue
- [ ] **CONT-02**: Page Hytale dediee `/hytale` — services plugin dev, tiers pricing, demos placeholders, maintenance recurrente, bilingue - [x] **CONT-02**: Page Hytale dediee `/hytale` — services plugin dev, tiers pricing, demos placeholders, maintenance recurrente, bilingue
- [ ] **CONT-03**: Grille tarifaire — plugin simple/complexe/sur-mesure/maintenance/web avec prix visibles - [x] **CONT-03**: Grille tarifaire — plugin simple/complexe/sur-mesure/maintenance/web avec prix visibles
- [ ] **CONT-04**: Temoignages — section featured + stats sur homepage et page Hytale (5 avis Fiverr existants) - [x] **CONT-04**: Temoignages — section featured + stats sur homepage et page Hytale (5 avis Fiverr existants)
### SEO ### SEO
- [ ] **SEO-01**: Canonical links — `<link rel="canonical">` sur chaque page pour eviter duplication i18n - [x] **SEO-01**: Canonical links — `<link rel="canonical">` sur chaque page pour eviter duplication i18n
- [ ] **SEO-02**: ogUrl par page — chaque `useSeoMeta()` inclut `ogUrl` specifique - [x] **SEO-02**: ogUrl par page — chaque `useSeoMeta()` inclut `ogUrl` specifique
- [ ] **SEO-03**: og:image par page — images distinctes au lieu du meme og-image.png partout - [x] **SEO-03**: og:image par page — images distinctes au lieu du meme og-image.png partout
- [ ] **SEO-04**: JSON-LD complet — Person (homepage), Service (hytale), SoftwareApplication (projets), composable `useJsonLd.ts` - [x] **SEO-04**: JSON-LD complet — Person (homepage), Service (hytale), SoftwareApplication (projets), composable `useJsonLd.ts`
- [ ] **SEO-05**: jobTitle corrige — "Hytale Plugin Developer" dans site.ts et JSON-LD, pas "Full Stack Freelance" - [x] **SEO-05**: jobTitle corrige — "Hytale Plugin Developer" dans site.ts et JSON-LD, pas "Full Stack Freelance"
### i18n ### i18n
- [ ] **I18N-01**: Audit complet FR/EN — chaque cle FR doit exister en EN avec traduction reelle - [x] **I18N-01**: Audit complet FR/EN — chaque cle FR doit exister en EN avec traduction reelle
- [ ] **I18N-02**: Qualite traductions FR — reformuler les traductions approximatives/anglicismes - [x] **I18N-02**: Qualite traductions FR — reformuler les traductions approximatives/anglicismes
- [ ] **I18N-03**: Hardcoded strings — eliminer toutes les chaines en dur dans les composants (HeroSection, error.vue) - [x] **I18N-03**: Hardcoded strings — eliminer toutes les chaines en dur dans les composants
- [ ] **I18N-04**: SEO keys Hytale — title/description/og specifiques pour la page Hytale en FR et EN - [x] **I18N-04**: SEO keys Hytale — title/description/og specifiques pour la page Hytale en FR et EN
### Fixes ### Fixes
- [ ] **FIX-01**: Supprimer `public/sitemap.xml` statique — conflit avec `@nuxtjs/sitemap` dynamique - [x] **FIX-01**: Supprimer `public/sitemap.xml` statique — conflit avec `@nuxtjs/sitemap` dynamique
- [ ] **FIX-02**: Dockerfile pnpm — remplacer `npm ci` par `pnpm install --frozen-lockfile` - [x] **FIX-02**: Dockerfile pnpm — remplacer `npm ci` par `pnpm install --frozen-lockfile`
- [ ] **FIX-03**: Rate limiting contact API — protection anti-spam in-memory sur `/api/contact` - [x] **FIX-03**: Rate limiting contact API — protection anti-spam in-memory sur `/api/contact`
- [ ] **FIX-04**: Donnees incoherentes — `reviewCount: '50'` vs `totalReviews: 10`, Fiverr URLs `#` - [x] **FIX-04**: Donnees incoherentes — `reviewCount: '50'` vs `totalReviews: 10`, Fiverr URLs `#`
- [ ] **FIX-05**: Pinning deps — `vue: "latest"` et `vue-router: "latest"` a pincer sur `^3.5.0` / `^4.5.0` - [x] **FIX-05**: Pinning deps — `vue: "latest"` et `vue-router: "latest"` a pincer sur `^3.5.0` / `^4.5.0`
### Deployment ### Deployment
- [ ] **DEPLOY-01**: Dockerfile production corrige — pnpm, node:22-alpine, env vars SMTP/gtag runtime - [x] **DEPLOY-01**: Dockerfile production corrige — pnpm, node:22-alpine, env vars SMTP/gtag runtime
## v2 Requirements ---
- **CONT-05**: Blog technique — articles Hytale plugin dev pour SEO long-tail ## v1.1 Requirements (M1.1 — SEO Hytale — Autorité & Contenu)
- **SEO-06**: og:image dynamique generee par page
### Blog — Système
- [ ] **BLOG-01**: Intégration `@nuxt/content` ou équivalent — renderer markdown complet (syntax highlighting, images, embeds, tables, callouts/alerts)
- [ ] **BLOG-02**: Page listing `/blog` — liste de tous les articles avec titre, description, date, tags, SSR
- [ ] **BLOG-03**: Page article `/blog/[slug]` — rendu SSR complet, table des matières, navigation prev/next
- [ ] **BLOG-04**: Blocs de code avec syntax highlighting (Kotlin, Java, TypeScript, Shell prioritaires pour Hytale)
- [ ] **BLOG-05**: Support images dans articles — images optimisées avec `<NuxtImg>` ou `<nuxt-img>`
### Blog — Contenu
- [ ] **BLOG-06**: Articles bilingues FR/EN — structure i18n dans le système de contenu
- [ ] **BLOG-07**: Au moins 2 articles seed Hytale au lancement (ex: "How to build your first Hytale plugin", "Hytale plugin development in 2026")
### SEO — Blog
- [ ] **SEO-10**: `useSeoMeta()` par article — title, description, og:title, og:description, og:image uniques
- [ ] **SEO-11**: JSON-LD `Article` par billet de blog — author, datePublished, dateModified, headline
- [ ] **SEO-12**: Sitemap étendu — URLs `/blog/[slug]` et `/en/blog/[slug]` incluses automatiquement
- [ ] **SEO-13**: Open Graph image par article — og:image spécifique (image de l'article ou fallback branded)
### SEO — Cocon sémantique
- [ ] **SEO-14**: Liens internes structurés — articles de blog pointent vers `/hytale`, page `/hytale` liste les articles récents
- [ ] **SEO-15**: BreadcrumbList JSON-LD sur les pages blog (Accueil → Blog → Article)
## Future Requirements (backlog)
- **SEO-06**: og:image dynamique générée par page (OG image generator)
- **FEAT-01**: Formulaire devis en ligne - **FEAT-01**: Formulaire devis en ligne
- **FEAT-02**: Section portfolio Minecraft Java - **FEAT-02**: Section portfolio Minecraft Java
- **CONT-08**: Newsletter / liste email pour communauté Hytale
## Out of Scope ## Out of Scope
| Feature | Reason | | Feature | Reason |
|---------|--------| |---------|--------|
| Tests automatises | Ship d'abord, tests ensuite | | Tests automatises | Ship d'abord, tests ensuite |
| Blog/CMS | Pas de contenu dynamique pour l'instant | | Dashboard admin | Blog statique markdown, pas de CMS |
| Dashboard admin | Portfolio statique |
| PWA/Service Workers | Pas de besoin offline | | PWA/Service Workers | Pas de besoin offline |
| Pub payante | Budget zero | | Pub payante | Budget zero |
| Payment integration | Paiements via Fiverr ou virement | | Payment integration | Paiements via Fiverr ou virement |
| Core Web Vitals | Milestone dédié si besoin |
| OG image generator | Complexité vs impact — backlog |
## Traceability ## Traceability v1.1
| Requirement | Phase | Status | | Requirement | Phase | Status |
|-------------|-------|--------| |-------------|-------|--------|
| CONT-01 | TBD | Pending | | BLOG-01 | Phase 5 | Pending |
| CONT-02 | TBD | Pending | | BLOG-04 | Phase 5 | Pending |
| CONT-03 | TBD | Pending | | BLOG-05 | Phase 5 | Pending |
| CONT-04 | TBD | Pending | | BLOG-02 | Phase 6 | Pending |
| SEO-01 | TBD | Pending | | BLOG-03 | Phase 6 | Pending |
| SEO-02 | TBD | Pending | | BLOG-06 | Phase 6 | Pending |
| SEO-03 | TBD | Pending | | SEO-10 | Phase 7 | Pending |
| SEO-04 | TBD | Pending | | SEO-11 | Phase 7 | Pending |
| SEO-05 | TBD | Pending | | SEO-12 | Phase 7 | Pending |
| I18N-01 | TBD | Pending | | SEO-13 | Phase 7 | Pending |
| I18N-02 | TBD | Pending | | SEO-15 | Phase 7 | Pending |
| I18N-03 | TBD | Pending | | BLOG-07 | Phase 8 | Pending |
| I18N-04 | TBD | Pending | | SEO-14 | Phase 8 | Pending |
| FIX-01 | TBD | Pending |
| FIX-02 | TBD | Pending |
| FIX-03 | TBD | Pending |
| FIX-04 | TBD | Pending |
| FIX-05 | TBD | Pending |
| DEPLOY-01 | TBD | Pending |
+83
View File
@@ -82,3 +82,86 @@ Plans:
| 2. Content | 3/3 | Complete | 2026-04-21 | | 2. Content | 3/3 | Complete | 2026-04-21 |
| 3. SEO & i18n | 1/1 | Complete | 2026-04-21 | | 3. SEO & i18n | 1/1 | Complete | 2026-04-21 |
| 4. Ship | 1/1 | Complete | 2026-04-21 | | 4. Ship | 1/1 | Complete | 2026-04-21 |
---
---
# Roadmap: Portfolio Killian' Dalcin — M1.1
**Milestone:** M1.1 — SEO Hytale — Autorité & Contenu
**Granularity:** Standard
**Coverage:** 13/13 requirements mapped
---
## Phases (M1.1)
- [ ] **Phase 5: @nuxt/content Setup & Renderer** - Integration @nuxt/content, markdown renderer complet avec syntax highlighting et images
- [ ] **Phase 6: Blog Pages** - Page listing /blog et page article /blog/[slug] SSR, bilingue, avec TOC et nav prev/next
- [ ] **Phase 7: SEO Blog** - useSeoMeta par article, JSON-LD Article, sitemap etendu, og:image, BreadcrumbList
- [ ] **Phase 8: Content & Cocon Semantique** - 2 articles seed Hytale, liens internes blog-hytale
---
## Phase Details (M1.1)
### Phase 5: @nuxt/content Setup & Renderer
**Goal**: Le systeme de contenu markdown est installe et rend fidelement le contenu technique — blocs de code colores, images optimisees, tables, alerts — sans configuration supplementaire dans les phases suivantes
**Depends on**: Phase 4 (M1 complete)
**Requirements**: BLOG-01, BLOG-04, BLOG-05
**Success Criteria** (what must be TRUE):
1. `@nuxt/content` est installe et configure dans `nuxt.config.ts``pnpm dev` demarre sans erreur
2. Un article markdown de test avec un bloc Kotlin est rendu avec coloration syntaxique visible dans le navigateur
3. Une image referencee dans un article s'affiche via `<NuxtImg>` avec les optimisations (lazy, format webp)
4. Un tableau markdown et un callout/alert sont rendus avec le style correct
**Plans**: TBD
**UI hint**: yes
### Phase 6: Blog Pages
**Goal**: Un visiteur peut naviguer vers /blog, parcourir la liste des articles, ouvrir un article, voir sa table des matieres et naviguer vers l'article precedent/suivant — le tout en SSR et en FR ou EN
**Depends on**: Phase 5
**Requirements**: BLOG-02, BLOG-03, BLOG-06
**Success Criteria** (what must be TRUE):
1. `curl localhost:3000/blog` retourne du HTML SSR avec une liste d'articles (titre, description, date, tags)
2. `curl localhost:3000/blog/[slug]` retourne le contenu de l'article rendu en HTML, pas de SPA shell vide
3. La page article affiche une table des matieres generee depuis les headings du markdown
4. Des liens "Article precedent" et "Article suivant" sont presents en bas de chaque article
5. `curl localhost:3000/en/blog` retourne le listing en anglais — les articles ont leur version EN
**Plans**: TBD
**UI hint**: yes
### Phase 7: SEO Blog
**Goal**: Chaque page blog est indexable avec des meta tags complets, un JSON-LD Article valide, et les URLs /blog figurent dans le sitemap — Google peut crawler et comprendre le contenu sans ambiguite
**Depends on**: Phase 6
**Requirements**: SEO-10, SEO-11, SEO-12, SEO-13, SEO-15
**Success Criteria** (what must be TRUE):
1. `curl localhost:3000/blog/[slug]` retourne `<meta property="og:title">`, `<meta property="og:description">` et `<meta property="og:image">` uniques dans le HTML
2. Le meme curl retourne un JSON-LD de type `Article` avec `author`, `datePublished`, `headline` remplis
3. `curl localhost:3000/sitemap.xml` contient les URLs `/blog/[slug]` et `/en/blog/[slug]`
4. `og:image` pointe vers l'image de l'article ou vers un fallback branded — jamais vers og-image.png generique
5. La page article contient un JSON-LD `BreadcrumbList` : Accueil → Blog → Titre de l'article
**Plans**: TBD
### Phase 8: Content & Cocon Semantique
**Goal**: Le blog est lance avec au moins 2 articles Hytale de qualite, et un visiteur qui arrive sur /hytale decouvre les articles recents — le cocon semantique entre blog et page hytale est etabli
**Depends on**: Phase 7
**Requirements**: BLOG-07, SEO-14
**Success Criteria** (what must be TRUE):
1. `/blog` liste au moins 2 articles avec le tag "hytale", avec titres, descriptions et dates reels
2. Chaque article blog contient au moins un lien interne vers `/hytale` dans le corps du texte
3. La page `/hytale` affiche une section "Articles recents" avec liens vers les 2 articles seed
4. Les articles existent en FR et EN — `curl localhost:3000/en/blog` les liste en anglais
**Plans**: TBD
**UI hint**: yes
---
## Progress (M1.1)
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 5. @nuxt/content Setup & Renderer | 0/? | Not started | - |
| 6. Blog Pages | 0/? | Not started | - |
| 7. SEO Blog | 0/? | Not started | - |
| 8. Content & Cocon Semantique | 0/? | Not started | - |
+20 -12
View File
@@ -1,15 +1,15 @@
--- ---
gsd_state_version: 1.0 gsd_state_version: 1.0
milestone: v1.0 milestone: v1.1
milestone_name: milestone milestone_name: SEO Hytale — Autorité & Contenu
status: Complete status: In Progress
last_updated: "2026-04-21T00:00:00.000Z" last_updated: "2026-04-21T00:00:00.000Z"
progress: progress:
total_phases: 4 total_phases: 4
completed_phases: 4 completed_phases: 0
total_plans: 7 total_plans: 0
completed_plans: 7 completed_plans: 0
percent: 100 percent: 0
--- ---
# Project State # Project State
@@ -22,10 +22,18 @@ progress:
## Current Focus ## Current Focus
Milestone M1 complet — déployé en production sur killiandalcin.fr Phase: Phase 5 — @nuxt/content Setup & Renderer
Plan: —
Status: Roadmap defined, ready to plan Phase 5
Last activity: 2026-04-21 — M1.1 roadmap created (phases 58)
## Session Notes ## Accumulated Context
- Project initialized 2026-04-10 with codebase mapping + 4-agent research - M1 complet — déployé en production sur killiandalcin.fr (phases 14)
- Brownfield: Nuxt 4 SSR portfolio already functional, needs content pivot to Hytale + SEO fixes - Stack : Nuxt 4 SSR + Nuxt UI v3 + Tailwind v4 + pnpm
- Phase 1 vérifiée complète le 2026-04-21 (commits appliqués hors GSD tracking) - Blog/CMS était Out of Scope en M1, promu en priorité principale pour M1.1
- Renderer markdown doit supporter : syntax highlighting, images, embeds, tables, alerts — utiliser @nuxt/content
- Objectif double : ranker sur "Hytale plugin developer" ET capter trafic longue traîne via contenu communauté
- Phase 5 ajoute @nuxt/content comme dépendance — vérifier compatibilité Nuxt 4 / compatibilityVersion 4
- Articles bilingues : structure FR/EN dans content/ (ex: content/fr/blog/, content/en/blog/)
- og:image par article : image frontmatter ou fallback branded — jamais l'og-image.png générique M1
@@ -0,0 +1,95 @@
# Phase 5: @nuxt/content Setup & Renderer - Context
**Gathered:** 2026-04-21
**Status:** Ready for planning
<domain>
## Phase Boundary
Installer et configurer `@nuxt/content` pour que le markdown rende fidèlement du contenu technique : blocs de code colorés, images optimisées, tableaux, callouts/alerts — sans configuration supplémentaire dans les phases suivantes.
Cette phase ne crée pas encore les pages blog (Phase 6) ni les articles réels (Phase 8). Elle pose l'infrastructure de rendu.
</domain>
<decisions>
## Implementation Decisions
### Style du rendu markdown
- **D-01:** Utiliser `@tailwindcss/typography` (plugin officiel). Classe `prose dark:prose-invert` sur le wrapper `<article>`. Compatible Tailwind v4, support dark mode natif synchronisé avec `colorMode` existant.
### Callouts / Alerts
- **D-02:** Implémenter via la syntaxe MDC de `@nuxt/content``::alert{type="warning"}` dans le markdown appelle un composant Vue dédié (`components/content/Alert.vue`). Aucun HTML brut dans les fichiers markdown.
### Structure content/
- **D-03:** Dossiers par langue : `content/fr/blog/` et `content/en/blog/`. Un fichier markdown par article par langue, avec le même slug. Aligné avec `@nuxtjs/i18n` strategy `prefix_except_default`.
### Syntax highlighting
- **D-04:** Shiki intégré à `@nuxt/content` v3 (zéro dépendance supplémentaire). Langages à déclarer dans `nuxt.config.ts` : Kotlin, Java, TypeScript, Shell. Thème dark/light synchronisé avec `colorMode` du site.
### Claude's Discretion
- Choix du thème Shiki exact (ex: `github-dark` / `github-light` ou variante) — cohérence avec la charte dark/light du site.
- Nombre et types de callouts MDC à créer au minimum (au moins : info, warning, tip).
- Frontmatter schema exact des articles (title, description, date, tags, image...) — à définir mais pas bloquant pour cette phase.
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Requirements
- `.planning/REQUIREMENTS.md` §BLOG-01, BLOG-04, BLOG-05 — exigences exactes de cette phase
### Stack existant
- `nuxt.config.ts` — configuration actuelle (modules, i18n, colorMode, image) à étendre, ne pas réécrire
- `assets/css/main.css` — styles globaux existants, vérifier compatibilité avec prose Tailwind
### Documentation externe (à consulter)
- `@nuxt/content` v3 docs : https://content.nuxt.com — installation, MDC syntax, ContentRenderer
- `@tailwindcss/typography` : https://tailwindcss.com/docs/typography-plugin — configuration prose
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `@nuxt/image` déjà installé et configuré → `<NuxtImg>` disponible pour les images dans les articles via MDC ou ContentRenderer
- `colorMode` configuré avec cookie (SSR-safe) → le thème Shiki doit répondre à `useColorMode().value`
### Established Patterns
- Modules Nuxt déclarés dans `nuxt.config.ts` → ajouter `@nuxt/content` dans le tableau `modules`
- Composants auto-importés depuis `~/components` avec `pathPrefix: false` → les composants MDC dans `components/content/` seront auto-importés
### Integration Points
- `nuxt.config.ts` → ajouter `@nuxt/content` dans `modules` + config `content: {}` + `shiki` langages
- `assets/css/main.css` → importer `@tailwindcss/typography` si nécessaire
- `components/content/` → dossier à créer pour les composants MDC (Alert, etc.)
- `content/fr/blog/` et `content/en/blog/` → à créer avec au moins 1 article de test Kotlin
</code_context>
<specifics>
## Specific Ideas
- L'article de test (critère de succès) doit contenir un bloc Kotlin coloré, une image, un tableau et un callout — couvre les 4 success criteria de la phase.
- Le callout minimum pour valider : `::alert{type="info"}` rendant un composant stylisé Nuxt UI ou Tailwind.
</specifics>
<deferred>
## Deferred Ideas
- Pages /blog et /blog/[slug] — Phase 6
- SEO par article (useSeoMeta, JSON-LD Article) — Phase 7
- Articles seed Hytale réels — Phase 8
- Frontmatter complet avec og:image par article — Phase 7
</deferred>
---
*Phase: 05-nuxt-content-setup-renderer*
*Context gathered: 2026-04-21*
@@ -0,0 +1,71 @@
# Phase 5: @nuxt/content Setup & Renderer - Discussion Log
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
**Date:** 2026-04-21
**Phase:** 05-nuxt-content-setup-renderer
**Areas discussed:** Style du rendu markdown, Callouts / Alerts, Structure content/, Syntax highlighting
---
## Style du rendu markdown
| Option | Description | Selected |
|--------|-------------|----------|
| @tailwindcss/typography | Plugin officiel Tailwind — classe `prose dark:prose-invert`, dark mode natif | ✓ |
| Nuxt UI prose styles | Via `UProse`, cohérent avec le design system existant | |
| CSS custom à la main | Tout écrire dans assets/css/ — contrôle total, effort élevé | |
**User's choice:** @tailwindcss/typography
**Notes:**
---
## Callouts / Alerts
| Option | Description | Selected |
|--------|-------------|----------|
| MDC Components | Syntaxe `::alert{type}` dans le markdown, composant Vue dédié | ✓ |
| HTML dans le markdown | `<div class="callout">` directement dans les .md | |
**User's choice:** MDC Components
**Notes:**
---
## Structure content/
| Option | Description | Selected |
|--------|-------------|----------|
| content/fr/ + content/en/ | Un dossier par langue, aligné avec @nuxtjs/i18n | ✓ |
| content/blog/ avec champ locale | Fichiers `.fr.md` / `.en.md` dans un seul dossier | |
**User's choice:** content/fr/ + content/en/
**Notes:**
---
## Syntax highlighting
| Option | Description | Selected |
|--------|-------------|----------|
| Shiki intégré | Zéro config, langues déclarées dans nuxt.config.ts, thème dark/light | ✓ |
| Prism | Alternative plus ancienne, moins performante | |
**User's choice:** Shiki intégré
**Notes:** Langages prioritaires : Kotlin, Java, TypeScript, Shell
---
## Claude's Discretion
- Thème Shiki exact (dark/light)
- Types de callouts MDC à créer (minimum : info, warning, tip)
- Frontmatter schema des articles
## Deferred Ideas
- Pages /blog et /blog/[slug] → Phase 6
- SEO par article → Phase 7
- Articles Hytale réels → Phase 8
@@ -0,0 +1,523 @@
# Phase 5: @nuxt/content Setup & Renderer — Research
**Researched:** 2026-04-21
**Domain:** @nuxt/content v3, Shiki, @tailwindcss/typography v4, MDC components
**Confidence:** HIGH
---
<user_constraints>
## User Constraints (from CONTEXT.md)
### Locked Decisions
- **D-01:** Utiliser `@tailwindcss/typography` (plugin officiel). Classe `prose dark:prose-invert` sur le wrapper `<article>`. Compatible Tailwind v4, support dark mode natif synchronisé avec `colorMode` existant.
- **D-02:** Implémenter les callouts via la syntaxe MDC de `@nuxt/content``::alert{type="warning"}` dans le markdown appelle un composant Vue dédié (`components/content/Alert.vue`). Aucun HTML brut dans les fichiers markdown.
- **D-03:** Dossiers par langue : `content/fr/blog/` et `content/en/blog/`. Un fichier markdown par article par langue, avec le même slug. Aligné avec `@nuxtjs/i18n` strategy `prefix_except_default`.
- **D-04:** Shiki intégré à `@nuxt/content` v3 (zéro dépendance supplémentaire). Langages à déclarer dans `nuxt.config.ts` : Kotlin, Java, TypeScript, Shell. Thème dark/light synchronisé avec `colorMode` du site.
### Claude's Discretion
- Choix du thème Shiki exact (ex: `github-dark` / `github-light` ou variante) — cohérence avec la charte dark/light du site.
- Nombre et types de callouts MDC à créer au minimum (au moins : info, warning, tip).
- Frontmatter schema exact des articles (title, description, date, tags, image...) — à définir mais pas bloquant pour cette phase.
### Deferred Ideas (OUT OF SCOPE)
- Pages /blog et /blog/[slug] — Phase 6
- SEO par article (useSeoMeta, JSON-LD Article) — Phase 7
- Articles seed Hytale réels — Phase 8
- Frontmatter complet avec og:image par article — Phase 7
</user_constraints>
---
<phase_requirements>
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|------------------|
| BLOG-01 | Intégration `@nuxt/content` — renderer markdown complet (syntax highlighting, images, embeds, tables, callouts/alerts) | Couvert par stack + patterns ci-dessous |
| BLOG-04 | Blocs de code avec syntax highlighting (Kotlin, Java, TypeScript, Shell) | Shiki intégré, config `highlight.langs` confirmée |
| BLOG-05 | Support images dans articles — images optimisées avec `<NuxtImg>` | ProseImg.vue override pattern confirmé |
</phase_requirements>
---
## Summary
`@nuxt/content` v3 (actuellement v3.6.3) est pleinement compatible avec Nuxt 4 (`compatibilityVersion: 4`). La v3 introduit une rupture majeure par rapport à la v2 : une nouvelle architecture basée sur **SQLite** (au lieu de fichiers parsés en mémoire) et un fichier de configuration dédié `content.config.ts` où l'on déclare les **collections**. Cette approche collection-based est exactement ce qu'il faut pour la structure bilingue `content/fr/blog/` et `content/en/blog/`.
Le stack se compose de trois briques : (1) `@nuxt/content` v3 pour le parsing et la query API, (2) Shiki intégré pour le highlighting sans dépendance supplémentaire, (3) `@tailwindcss/typography` pour le styling `prose`. L'intégration avec `@nuxt/image` se fait via un composant override `components/content/ProseImg.vue`. Sur Node.js 22 (la cible de ce projet), le connecteur SQLite natif est disponible sans installer `better-sqlite3`.
Point d'attention pnpm : `@nuxt/content` nécessite l'ajout de `better-sqlite3` OU l'activation du connecteur natif Node 22 dans `onlyBuiltDependencies`. Le projet utilise déjà `pnpm.onlyBuiltDependencies` dans `package.json` — il faudra soit y ajouter `better-sqlite3`, soit activer `experimental.sqliteConnector: 'native'` (recommandé car Node 22 est déjà la cible).
**Recommandation principale :** Utiliser `experimental.sqliteConnector: 'native'` pour éviter toute dépendance supplémentaire — le Dockerfile cible déjà `node:22-alpine` (Node 22.5+).
---
## Architectural Responsibility Map
| Capability | Tier Primaire | Tier Secondaire | Rationale |
|------------|---------------|-----------------|-----------|
| Parsing et indexation markdown | Serveur (build) | — | @nuxt/content compile les fichiers en DB SQLite au build |
| Rendu HTML depuis markdown | Serveur (SSR) | Client (hydration) | ContentRenderer s'exécute côté serveur |
| Syntax highlighting | Build/Serveur | — | Shiki génère le HTML coloré au build, pas au runtime |
| Images optimisées dans articles | Serveur (SSR) | CDN/Edge | NuxtImg génère les directives d'optimisation SSR-side |
| Composants MDC (callouts) | Serveur (SSR) | Client | Composants Vue auto-importés, rendus en SSR |
| Query des articles par locale | Serveur (SSR) | — | `queryCollection()` dans les pages = data fetching SSR |
---
## Standard Stack
### Core
| Librairie | Version | Rôle | Pourquoi |
|-----------|---------|------|---------|
| @nuxt/content | ^3.6.3 | CMS file-based, parsing markdown, query API | Module officiel Nuxt, Shiki intégré, MDC natif |
| @tailwindcss/typography | ^0.5.x | Styles `prose` pour le HTML généré | Plugin officiel, syntaxe `@plugin` Tailwind v4 |
### Supporting (déjà installés)
| Librairie | Version | Rôle | Note |
|-----------|---------|------|------|
| @nuxt/image | ^2.0.0 | Optimisation images via ProseImg override | Déjà dans le projet |
| tailwindcss | ^4.2.2 | Déjà présent | Supporte `@plugin` directive |
### Alternatives considérées
| Standard | Alternative | Tradeoff |
|----------|-------------|----------|
| Shiki intégré | Prism.js | Shiki = zero install, meilleur rendu, thèmes Shiki-compatibles |
| @tailwindcss/typography | CSS prose custom | Typography = 0 maintenance, dark mode natif |
| ProseImg override | MDC component custom | Override = transparent pour les auteurs |
**Installation :**
```bash
pnpm add @nuxt/content
pnpm add -D @tailwindcss/typography
```
**Versions vérifiées :**
- `@nuxt/content` : v3.6.3 [VERIFIED: Context7 registry]
- `@tailwindcss/typography` : compatible Tailwind v4 via `@plugin` directive [VERIFIED: github.com/tailwindlabs/tailwindcss-typography]
---
## Architecture Patterns
### Diagramme de flux
```
Fichiers markdown (content/fr/blog/, content/en/blog/)
▼ (build time)
@nuxt/content parser + Shiki
│ SQLite DB générée
content.config.ts collections (blog_fr, blog_en)
▼ (SSR request)
queryCollection('blog_fr' | 'blog_en')
│ document parsé retourné
<ContentRenderer :value="page" />
├─── ProseImg.vue → <NuxtImg> (images optimisées)
├─── ProsePre.vue / ProseCode → HTML Shiki (coloration)
├─── Alert.vue (MDC ::alert{type}) → <UAlert> stylisé
└─── prose dark:prose-invert wrapper (typography)
```
### Structure de fichiers recommandée
```
content/
├── fr/
│ └── blog/
│ └── test-kotlin-syntax.md # article de test
└── en/
└── blog/
└── test-kotlin-syntax.md # même slug, contenu EN
content.config.ts # collections blog_fr + blog_en
components/
└── content/
├── ProseImg.vue # override → NuxtImg
├── Alert.vue # MDC ::alert{type="info|warning|tip"}
└── (optionnel: ProseCode.vue) # si customisation inline code
assets/css/main.css # ajouter @plugin "@tailwindcss/typography"
```
### Pattern 1 : Configuration nuxt.config.ts
```typescript
// Source: content.nuxt.com/docs/getting-started/configuration
export default defineNuxtConfig({
modules: [
// ... modules existants
'@nuxt/content'
],
content: {
build: {
markdown: {
highlight: {
theme: {
default: 'github-light',
dark: 'github-dark'
},
langs: ['kotlin', 'java', 'typescript', 'shell', 'bash', 'json', 'vue']
}
}
},
experimental: {
sqliteConnector: 'native' // Node 22+ natif, pas de better-sqlite3
}
}
})
```
### Pattern 2 : content.config.ts (collections bilingues)
```typescript
// Source: content.nuxt.com/docs/integrations/i18n
import { defineContentConfig, defineCollection, z } from '@nuxt/content'
const blogSchema = z.object({
title: z.string(),
description: z.string(),
date: z.string(),
tags: z.array(z.string()).optional(),
image: z.string().optional(),
})
export default defineContentConfig({
collections: {
blog_fr: defineCollection({
type: 'page',
source: { include: 'fr/blog/**/*.md', prefix: '/fr/blog' },
schema: blogSchema,
}),
blog_en: defineCollection({
type: 'page',
source: { include: 'en/blog/**/*.md', prefix: '/en/blog' },
schema: blogSchema,
}),
},
})
```
### Pattern 3 : @tailwindcss/typography avec Tailwind v4
```css
/* assets/css/main.css */
/* Source: github.com/tailwindlabs/tailwindcss-typography */
@import "tailwindcss";
@plugin "@tailwindcss/typography";
```
Usage dans un composant :
```html
<article class="prose dark:prose-invert max-w-none">
<ContentRenderer :value="page" />
</article>
```
### Pattern 4 : ProseImg.vue — override NuxtImg
```vue
<!-- components/content/ProseImg.vue -->
<!-- Source: github.com/nuxt/content/discussions/2082 -->
<script setup lang="ts">
interface Props {
src: string
alt?: string
title?: string
width?: string | number
height?: string | number
}
const props = withDefaults(defineProps<Props>(), {
alt: '',
})
</script>
<template>
<NuxtImg
:src="props.src"
:alt="props.alt"
:title="props.title"
:width="props.width"
:height="props.height"
class="rounded-lg w-full"
sizes="sm:600px md:800px lg:1000px"
/>
</template>
```
### Pattern 5 : Composant MDC Alert.vue
```vue
<!-- components/content/Alert.vue -->
<!-- Usage markdown: ::alert{type="warning"} Contenu :: -->
<script setup lang="ts">
interface Props {
type?: 'info' | 'warning' | 'tip' | 'danger'
}
const props = withDefaults(defineProps<Props>(), {
type: 'info',
})
const iconMap = {
info: 'i-heroicons-information-circle',
warning: 'i-heroicons-exclamation-triangle',
tip: 'i-heroicons-light-bulb',
danger: 'i-heroicons-x-circle',
}
const colorMap = {
info: 'info',
warning: 'warning',
tip: 'success',
danger: 'error',
}
</script>
<template>
<UAlert
:icon="iconMap[props.type]"
:color="colorMap[props.type] as any"
variant="soft"
class="my-4"
>
<template #description>
<ContentSlot :use="$slots.default" unwrap="p" />
</template>
</UAlert>
</template>
```
### Pattern 6 : Requête dans une page (preview Phase 6)
```typescript
// Source: content.nuxt.com/docs/integrations/i18n
const { locale } = useI18n()
const collectionName = computed(
() => ('blog_' + locale.value) as 'blog_fr' | 'blog_en'
)
const { data: page } = await useAsyncData('article', () =>
queryCollection(collectionName.value).path(route.path).first()
)
```
### Anti-Patterns à éviter
- **Ne pas utiliser `nativeSqlite: true`** — option dépréciée, utiliser `sqliteConnector: 'native'` à la place.
- **Ne pas mettre `better-sqlite3` dans dependencies** — inutile avec Node 22 natif ; alourdit l'image Docker.
- **Ne pas nommer les composants MDC avec des tirets dans le fichier** — nommer `Alert.vue` pas `alert-component.vue`. Le mapping MDC utilise le nom PascalCase du fichier.
- **Ne pas utiliser `v-html` pour le rendu markdown** — toujours passer par `<ContentRenderer>` pour bénéficier des Prose overrides.
- **Ne pas oublier `ContentSlot` dans les composants MDC avec slots** — le contenu entre `::alert` et `::` doit passer par `<ContentSlot :use="$slots.default" />` sinon il n'est pas rendu.
- **Ne pas confondre `prefix` et dossier source** dans `content.config.ts``prefix` définit le path URL, `source.include` définit où chercher les fichiers.
---
## Don't Hand-Roll
| Problème | Ne pas construire | Utiliser à la place | Pourquoi |
|----------|------------------|---------------------|---------|
| Syntax highlighting | Parseur custom regex | Shiki (intégré @nuxt/content) | 200+ langages, thèmes CSS variables, SSR-safe |
| Typography styles | CSS prose custom | @tailwindcss/typography | Dark mode, responsive, rythme vertical correct |
| Image optimisation dans articles | `<img>` natif | ProseImg.vue + NuxtImg | Lazy loading, formats modernes, responsive sizes |
| Callouts/alerts | HTML brut dans markdown | MDC + composants Vue | Type-safe, ré-utilisable, stylisable via Nuxt UI |
| Parsing SQLite | Driver custom | `experimental.sqliteConnector: 'native'` | Node 22 built-in, zéro install |
---
## Common Pitfalls
### Pitfall 1 : pnpm build scripts bloqués pour SQLite
**Ce qui se passe :** `pnpm install` refuse d'exécuter les scripts de build de `better-sqlite3` par défaut.
**Pourquoi :** pnpm v10+ restreint les scripts de build.
**Comment éviter :** Utiliser `experimental.sqliteConnector: 'native'` — aucune dépendance SQLite externe nécessaire sur Node 22. Si `better-sqlite3` est quand même nécessaire, ajouter `"better-sqlite3"` dans `pnpm.onlyBuiltDependencies` (déjà configuré dans `package.json`).
**Signal d'alerte :** Erreur `Cannot find module 'better-sqlite3'` au démarrage.
### Pitfall 2 : Thèmes Shiki et classe CSS du dark mode
**Ce qui se passe :** Le thème `dark` ne s'applique pas — le code reste en thème clair.
**Pourquoi :** Shiki dual-theme fonctionne via la classe `html.dark`. Le projet configure `colorMode` avec `classSuffix: ''`, ce qui génère bien `class="dark"` sur `<html>` — c'est compatible.
**Comment éviter :** Vérifier que `colorMode.classSuffix` reste `''` dans `nuxt.config.ts`. Shiki génère automatiquement les CSS variables pour les deux thèmes.
**Signal d'alerte :** Code toujours en clair même en mode dark → inspecter `<html class>`.
### Pitfall 3 : `source.prefix` mal configuré dans content.config.ts
**Ce qui se passe :** Les articles FR apparaissent sous `/blog/...` au lieu de `/fr/blog/...`, ou vice-versa.
**Pourquoi :** La valeur `prefix` dans `defineCollection.source` définit le path URL racine de la collection.
**Comment éviter :** Pour `content/fr/blog/*.md` avec i18n `prefix_except_default` (FR = default sans préfixe) : `prefix: '/blog'` pour la collection FR, `prefix: '/en/blog'` pour EN.
### Pitfall 4 : ContentSlot manquant dans composants MDC avec contenu
**Ce qui se passe :** Le contenu entre `::alert` et `::` n'est pas affiché.
**Pourquoi :** Les composants MDC reçoivent leur contenu via un slot — il faut explicitement le rendre avec `<ContentSlot :use="$slots.default" unwrap="p" />`.
**Comment éviter :** Toujours inclure `ContentSlot` dans les composants MDC qui acceptent du contenu.
**Signal d'alerte :** Alert visible mais vide.
### Pitfall 5 : @tailwindcss/typography et Tailwind v4 — ancienne syntaxe
**Ce qui se passe :** `plugins: [require('@tailwindcss/typography')]` dans `tailwind.config.js` est ignoré.
**Pourquoi :** Tailwind v4 n'utilise plus `tailwind.config.js` pour les plugins — tout passe par le CSS avec `@plugin`.
**Comment éviter :** Utiliser `@plugin "@tailwindcss/typography";` dans `assets/css/main.css`.
**Signal d'alerte :** Les classes `prose` n'ont aucun effet visible.
---
## Code Examples
### Article de test markdown (critère de validation)
```markdown
---
title: "Test Kotlin Syntax Highlighting"
description: "Article de test pour valider le renderer"
date: "2026-04-21"
tags: ["kotlin", "hytale", "test"]
---
## Bloc de code Kotlin
```kotlin
fun main() {
println("Hello, Hytale!")
}
```
## Image optimisée
![Test image](/images/test.png)
## Tableau
| Colonne A | Colonne B |
|-----------|-----------|
| Valeur 1 | Valeur 2 |
## Callout
::alert{type="info"}
Ceci est un callout d'information.
::
```
### Kotlin dans Shiki — langages Shiki acceptés
```typescript
// Noms de langages valides pour Shiki :
// 'kotlin' ✓, 'java' ✓, 'typescript' ✓ (ou 'ts'), 'shell' ✓ (ou 'bash', 'sh')
highlight: {
langs: ['kotlin', 'java', 'typescript', 'shell', 'bash', 'json', 'vue', 'html', 'css']
}
```
---
## State of the Art
| Ancienne approche | Approche actuelle | Changement | Impact |
|-------------------|-------------------|------------|--------|
| @nuxt/content v2 (fichiers en mémoire) | v3 (SQLite) | v3.0.0 (2024) | Nouvelle API `queryCollection()`, fichier `content.config.ts` requis |
| `experimental.nativeSqlite: true` | `experimental.sqliteConnector: 'native'` | v3.x (2025) | Ancienne option dépréciée |
| `plugins: [require('...')]` dans tailwind.config.js | `@plugin "..."` dans CSS | Tailwind v4 (2024) | tailwind.config.js supprimé |
| `<NuxtContent>` (v2) | `<ContentRenderer :value="page">` (v3) | v3.0.0 | Composant renommé et refactorisé |
**Déprécié :**
- `queryContent()` (v2) → remplacé par `queryCollection()` (v3) — ne pas utiliser l'ancienne API
- `experimental.nativeSqlite` → utiliser `experimental.sqliteConnector: 'native'`
---
## Assumptions Log
| # | Claim | Section | Risque si faux |
|---|-------|---------|----------------|
| A1 | Le thème Shiki `github-dark`/`github-light` est cohérent avec la charte visuelle du site | Standard Stack / Pattern 1 | Mineur — changeable post-implémentation |
| A2 | `assets/css/main.css` existe et est déjà l'entrée CSS principale (référencé dans nuxt.config.ts `css: ['~/assets/css/main.css']`) | Pattern 3 | Si le fichier n'existe pas, il faut le créer avec le contenu complet |
| A3 | Le prefix collection FR doit être `/blog` (pas `/fr/blog`) car `prefix_except_default` avec FR comme locale par défaut | Pattern 2 | Moyen — si faux, les URLs des articles seront mal formées |
---
## Open Questions
1. **Frontmatter schema définitif**
- Ce qu'on sait : `title`, `description`, `date` sont nécessaires pour Phase 6 (listing)
- Ce qui est flou : `tags` (array ou string?), `image` (path relatif ou absolu?), `author`
- Recommandation : Définir un schema minimal dans `content.config.ts` avec `z.string().optional()` pour les champs non-critiques — peut s'étendre en Phase 7
2. **Prefix des collections i18n**
- Ce qu'on sait : `prefix_except_default` avec `defaultLocale: 'fr'` → les URLs FR sont sans `/fr/`
- Ce qui est flou : Le `source.prefix` dans content.config.ts doit-il matcher exactement le path i18n ?
- Recommandation : Tester avec l'article de validation que `queryCollection('blog_fr').path('/blog/test-kotlin-syntax').first()` retourne le bon article
---
## Environment Availability
| Dépendance | Requis par | Disponible | Version | Fallback |
|------------|-----------|-----------|---------|----------|
| Node.js 22 | `sqliteConnector: 'native'` (22.5+) | ✓ (Dockerfile node:22-alpine) | 22+ | Installer better-sqlite3 |
| pnpm | Install @nuxt/content | ✓ (package.json pnpm field présent) | — | — |
| @nuxt/image | ProseImg.vue → NuxtImg | ✓ (déjà dans package.json) | ^2.0.0 | — |
**Aucune dépendance bloquante sans fallback.**
---
## Validation Architecture
> `workflow.nyquist_validation` non configuré — traité comme activé.
### Test Framework
| Propriété | Valeur |
|-----------|--------|
| Framework | Manuel (vitest non configuré) |
| Config file | Aucune |
| Quick run command | `pnpm dev` + navigation sur l'article de test |
| Full suite command | `pnpm build && pnpm preview` |
### Phase Requirements → Test Map
| Req ID | Comportement | Type de test | Commande | Fichier existe? |
|--------|-------------|-------------|----------|----------------|
| BLOG-01 | ContentRenderer rend un fichier .md | smoke | `pnpm dev` — vérifier /blog/test-kotlin-syntax | ❌ Wave 0 |
| BLOG-04 | Bloc ```kotlin coloré avec thème dark/light | visuel | Inspecter DOM — spans avec classes Shiki | ❌ Wave 0 |
| BLOG-05 | Image dans article rendue via NuxtImg | visuel | Inspecter balise `<img>` — attributs srcset présents | ❌ Wave 0 |
### Wave 0 Gaps
- [ ] `content/fr/blog/test-kotlin-syntax.md` — article de test couvrant BLOG-01, BLOG-04, BLOG-05
- [ ] `content/en/blog/test-kotlin-syntax.md` — version EN du même article
- [ ] `content.config.ts` — collections blog_fr + blog_en
- [ ] `components/content/ProseImg.vue` — override NuxtImg
- [ ] `components/content/Alert.vue` — composant MDC callout
- [ ] `assets/css/main.css` — vérifier/créer avec `@plugin "@tailwindcss/typography"`
---
## Security Domain
### Applicable ASVS Categories
| Catégorie ASVS | Applicable | Contrôle standard |
|----------------|-----------|-------------------|
| V5 Input Validation | Oui (faible) | Le markdown est statique (fichiers gérés par l'auteur) — pas d'input utilisateur dans cette phase |
| V6 Cryptography | Non | — |
**Note sécurité :** Le markdown est géré par l'auteur (fichiers statiques). Pas d'injection utilisateur possible dans cette phase. Le rendu HTML via ContentRenderer est sûr — Shiki génère du HTML échappé. Aucun XSS vector identifié.
---
## Sources
### Primaires (HIGH confidence)
- [content.nuxt.com/docs/getting-started/installation](https://content.nuxt.com/docs/getting-started/installation) — installation, pnpm, SQLite native
- [content.nuxt.com/docs/getting-started/configuration](https://content.nuxt.com/docs/getting-started/configuration#highlight) — Shiki dual theme, langs
- [content.nuxt.com/docs/components/prose](https://content.nuxt.com/docs/components/prose) — liste composants Prose, ProseImg
- [content.nuxt.com/docs/files/markdown](https://content.nuxt.com/docs/files/markdown) — MDC syntax
- [content.nuxt.com/docs/integrations/i18n](https://content.nuxt.com/docs/integrations/i18n) — collections bilingues
- [github.com/tailwindlabs/tailwindcss-typography](https://github.com/tailwindlabs/tailwindcss-typography) — `@plugin` syntax Tailwind v4
- Context7 `/nuxt/content` — version v3.6.3 confirmée
### Secondaires (MEDIUM confidence)
- [masteringnuxt.com/blog/mastering-prose-components-in-nuxt-content](https://masteringnuxt.com/blog/mastering-prose-components-in-nuxt-content) — ProseImg.vue pattern avec NuxtImg
- [github.com/nuxt/content/discussions/2082](https://github.com/nuxt/content/discussions/2082) — recommandation ProseImg + NuxtImg
---
## Metadata
**Confidence breakdown :**
- Standard stack : HIGH — versions vérifiées, docs officielles consultées
- Architecture patterns : HIGH — examples tirés de la doc officielle
- Pitfalls : MEDIUM — combinaison doc officielle + patterns communautaires vérifiés
- Tailwind v4 + typography : HIGH — vérifié sur le repo officiel
**Research date :** 2026-04-21
**Valid until :** 2026-05-21 (librairies stables, pas de breaking changes attendus)
@@ -0,0 +1,192 @@
---
phase: 5
slug: nuxt-content-setup-renderer
status: draft
shadcn_initialized: false
preset: none
created: 2026-04-21
---
# Phase 5 — UI Design Contract
## @nuxt/content Setup & Renderer
> Contrat visuel et d'interaction pour la phase d'infrastructure de rendu markdown.
> Généré par gsd-ui-researcher — à valider par gsd-ui-checker.
---
## Design System
| Property | Value | Source |
|----------|-------|--------|
| Tool | Nuxt UI v3 | CONTEXT.md / nuxt.config.ts |
| Preset | not applicable (Nuxt UI, pas shadcn) | nuxt.config.ts |
| Component library | Nuxt UI v3 (@nuxt/ui) | nuxt.config.ts modules |
| Icon library | Heroicons via Nuxt UI (i-heroicons-*) | RESEARCH.md Pattern 5 |
| Font | Hérité du site (pas de font custom déclarée dans main.css) | app/assets/css/main.css |
| Tailwind | v4 avec @theme tokens brand-* | app/assets/css/main.css |
> Note : pas de shadcn dans ce projet — stack Nuxt UI v3 + Tailwind v4. La shadcn gate ne s'applique pas.
---
## Spacing Scale
Échelle 8-points standard (multiples de 4). Tailwind v4 gère ces valeurs via ses classes utilitaires.
| Token | Value | Usage dans cette phase |
|-------|-------|------------------------|
| xs | 4px | Gap icône/texte dans les callouts Alert |
| sm | 8px | Padding interne compact (inline code, badges tags) |
| md | 16px | Padding article, espacement entre blocs prose |
| lg | 24px | Marge verticale entre sections de l'article |
| xl | 32px | Padding extérieur du wrapper `<article>` |
| 2xl | 48px | — (réservé Phase 6 pour les pages) |
| 3xl | 64px | — (réservé Phase 6 pour les pages) |
Exceptions :
- `my-4` (16px) sur les composants `Alert.vue` — conforme à l'échelle, source : RESEARCH.md Pattern 5
- Images prose : `rounded-lg w-full` sans contrainte de hauteur fixe — taille naturelle de l'image
---
## Typography
La typographie du corps de l'article est entièrement gérée par `@tailwindcss/typography` via la classe `prose`.
Les valeurs ci-dessous reflètent les valeurs par défaut du plugin, conformes aux décisions D-01.
| Role | Size | Weight | Line Height | Usage |
|------|------|--------|-------------|-------|
| Body prose | 16px (1rem) | 400 (regular) | 1.75 | Corps du texte dans `<article class="prose">` |
| Label / caption | 14px (0.875rem) | 400 (regular) | 1.5 | Tags frontmatter, métadonnées date |
| Heading article (h2/h3) | 2024px | 600 (semibold) | 1.25 | Titres de sections générés par prose |
| Inline code | 14px (0.875rem) | 400 (regular) | 1.5 | `` `code` `` inline dans prose |
> Source : valeurs par défaut `@tailwindcss/typography` — RESEARCH.md D-01, Pattern 3.
> Police héritée du site (system-ui ou celle définie par Nuxt UI). Pas de font custom dans cette phase.
---
## Color
Le site utilise dark mode par défaut (`colorMode.preference: 'dark'`) avec cookie SSR-safe.
| Role | Value | Usage |
|------|-------|-------|
| Dominant (60%) | `bg-background` (Nuxt UI token — dark: ~#0f172a, light: #ffffff) | Surface principale de l'article, fond de page |
| Secondary (30%) | `bg-muted` / `bg-elevated` (Nuxt UI token) | Blocs de code Shiki (fond), callouts Alert background |
| Accent (10%) | `--color-brand-500: #85cb85` (vert) | Liens dans prose, bordure left des callouts `tip`, focus states |
| Destructive | `color-error` (Nuxt UI token — rouge) | Callouts `danger` uniquement |
Accent réservé exclusivement à :
- Liens hypertextes dans le contenu `prose` (`:hover` underline brand-500)
- Bordure gauche du callout `::alert{type="tip"}` (couleur success = vert)
- Aucun autre usage dans cette phase
Thème Shiki :
- `default: 'github-light'` en mode light
- `dark: 'github-dark'` en mode dark
- Synchronisé via `html.dark` (classSuffix: '' confirmé dans nuxt.config.ts)
- Source : RESEARCH.md Pattern 1, assumption A1 validée (cohérence avec charte vert/dark du site)
---
## Component Inventory
Composants à créer dans cette phase (zéro shadcn — tout Nuxt UI ou Tailwind CSS) :
| Composant | Chemin | Rôle | Base |
|-----------|--------|------|------|
| ProseImg | `components/content/ProseImg.vue` | Override prose img → NuxtImg optimisé | `<NuxtImg>` déjà installé |
| Alert | `components/content/Alert.vue` | Callout MDC `::alert{type}` | `<UAlert>` Nuxt UI |
Types de callouts MDC à implémenter (minimum) :
| Type | Icône Heroicons | Couleur Nuxt UI | Usage |
|------|-----------------|-----------------|-------|
| `info` | `i-heroicons-information-circle` | `info` | Notes générales |
| `warning` | `i-heroicons-exclamation-triangle` | `warning` | Avertissements |
| `tip` | `i-heroicons-light-bulb` | `success` (vert brand) | Conseils pratiques |
| `danger` | `i-heroicons-x-circle` | `error` | Erreurs critiques |
Source : RESEARCH.md Pattern 5 — iconMap et colorMap déjà définis, à utiliser tel quel.
---
## Copywriting Contract
Cette phase est une phase d'infrastructure — aucune page publique n'est exposée.
Le seul contenu visible est l'article de test servant à valider les critères de succès.
| Element | Copy (FR) | Copy (EN) |
|---------|-----------|-----------|
| Titre article de test | "Test Kotlin Syntax Highlighting" | "Test Kotlin Syntax Highlighting" |
| Description article de test | "Article de test pour valider le renderer @nuxt/content" | "Test article to validate the @nuxt/content renderer" |
| Contenu callout info de test | "Ceci est un callout d'information." | "This is an information callout." |
| Contenu callout warning de test | "Ceci est un avertissement." | "This is a warning." |
| Contenu callout tip de test | "Conseil pratique de développement Kotlin." | "Practical Kotlin development tip." |
| Alt image de test | "Image de test pour NuxtImg dans les articles" | "Test image for NuxtImg in articles" |
États d'erreur (infrastructure — pas d'UI utilisateur) :
- Aucun état d'erreur visible par l'utilisateur dans cette phase
- En cas d'échec du build SQLite : erreur côté serveur uniquement (logs), pas de fallback UI
Aucune action destructive dans cette phase.
---
## Prose Wrapper Contract
Le wrapper autour de `<ContentRenderer>` suit ce contrat exact :
```html
<article class="prose dark:prose-invert max-w-none">
<ContentRenderer :value="page" />
</article>
```
- `max-w-none` : la contrainte de largeur est gérée par le layout parent (Phase 6), pas par prose
- `dark:prose-invert` : inverse automatiquement les couleurs prose en dark mode
- Source : RESEARCH.md D-01, Pattern 3
---
## Frontmatter Schema (minimal Phase 5)
Déclaré dans `content.config.ts` via Zod. Utilisé par l'article de test.
| Champ | Type | Requis | Usage |
|-------|------|--------|-------|
| `title` | `z.string()` | Oui | Titre de l'article |
| `description` | `z.string()` | Oui | Meta description (Phase 7) |
| `date` | `z.string()` | Oui | Date ISO 8601 (YYYY-MM-DD) |
| `tags` | `z.array(z.string()).optional()` | Non | Tags thématiques |
| `image` | `z.string().optional()` | Non | Chemin image og (Phase 7) |
Source : RESEARCH.md Pattern 2 — schema `blogSchema` à utiliser tel quel.
---
## Registry Safety
| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| Nuxt UI officiel | `UAlert` | Non requis — composant officiel @nuxt/ui |
| @nuxt/content officiel | `ContentRenderer`, `ContentSlot` | Non requis — module officiel Nuxt |
| Tiers | aucun | Non applicable |
> Note : Ce projet utilise Nuxt UI, pas shadcn. La registry safety gate shadcn ne s'applique pas.
> Aucun composant tiers hors ecosystem Nuxt officiel dans cette phase.
---
## Checker Sign-Off
- [ ] Dimension 1 Copywriting: PASS
- [ ] Dimension 2 Visuals: PASS
- [ ] Dimension 3 Color: PASS
- [ ] Dimension 4 Typography: PASS
- [ ] Dimension 5 Spacing: PASS
- [ ] Dimension 6 Registry Safety: PASS
**Approval:** pending