Compare commits
5 Commits
a0788f1edd
...
afd81e7e84
| Author | SHA1 | Date | |
|---|---|---|---|
| afd81e7e84 | |||
| 2d8f0ca7c3 | |||
| 2b06dfe463 | |||
| 2658b0c607 | |||
| a4ee7fe007 |
@@ -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
@@ -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.
|
||||
|
||||
## 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
|
||||
|
||||
### Validated
|
||||
@@ -39,7 +50,7 @@ Le portfolio doit positionner Killian comme LE developpeur de plugins Hytale pro
|
||||
### Out of Scope
|
||||
|
||||
- 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
|
||||
- PWA/Service Workers — pas de besoin offline
|
||||
- Pub payante — budget zero
|
||||
|
||||
+70
-45
@@ -1,82 +1,107 @@
|
||||
# Requirements: Portfolio Killian' Dalcin
|
||||
|
||||
**Defined:** 2026-04-10
|
||||
**Updated:** 2026-04-21 (v1.1 added)
|
||||
**Core Value:** Positionner Killian comme dev Hytale #1, crawlable sans JS, SEO optimise
|
||||
|
||||
## v1 Requirements
|
||||
## v1 Requirements (M1 — Complété 2026-04-21)
|
||||
|
||||
### Content
|
||||
|
||||
- [ ] **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
|
||||
- [ ] **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-01**: Refonte Hero accueil — "Hytale Plugin Developer" en H1, CTA Discord/contact, bilingue
|
||||
- [x] **CONT-02**: Page Hytale dediee `/hytale` — services plugin dev, tiers pricing, demos placeholders, maintenance recurrente, bilingue
|
||||
- [x] **CONT-03**: Grille tarifaire — plugin simple/complexe/sur-mesure/maintenance/web avec prix visibles
|
||||
- [x] **CONT-04**: Temoignages — section featured + stats sur homepage et page Hytale (5 avis Fiverr existants)
|
||||
|
||||
### SEO
|
||||
|
||||
- [ ] **SEO-01**: Canonical links — `<link rel="canonical">` sur chaque page pour eviter duplication i18n
|
||||
- [ ] **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
|
||||
- [ ] **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-01**: Canonical links — `<link rel="canonical">` sur chaque page pour eviter duplication i18n
|
||||
- [x] **SEO-02**: ogUrl par page — chaque `useSeoMeta()` inclut `ogUrl` specifique
|
||||
- [x] **SEO-03**: og:image par page — images distinctes au lieu du meme og-image.png partout
|
||||
- [x] **SEO-04**: JSON-LD complet — Person (homepage), Service (hytale), SoftwareApplication (projets), composable `useJsonLd.ts`
|
||||
- [x] **SEO-05**: jobTitle corrige — "Hytale Plugin Developer" dans site.ts et JSON-LD, pas "Full Stack Freelance"
|
||||
|
||||
### i18n
|
||||
|
||||
- [ ] **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
|
||||
- [ ] **I18N-03**: Hardcoded strings — eliminer toutes les chaines en dur dans les composants (HeroSection, error.vue)
|
||||
- [ ] **I18N-04**: SEO keys Hytale — title/description/og specifiques pour la page Hytale en FR et EN
|
||||
- [x] **I18N-01**: Audit complet FR/EN — chaque cle FR doit exister en EN avec traduction reelle
|
||||
- [x] **I18N-02**: Qualite traductions FR — reformuler les traductions approximatives/anglicismes
|
||||
- [x] **I18N-03**: Hardcoded strings — eliminer toutes les chaines en dur dans les composants
|
||||
- [x] **I18N-04**: SEO keys Hytale — title/description/og specifiques pour la page Hytale en FR et EN
|
||||
|
||||
### Fixes
|
||||
|
||||
- [ ] **FIX-01**: Supprimer `public/sitemap.xml` statique — conflit avec `@nuxtjs/sitemap` dynamique
|
||||
- [ ] **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`
|
||||
- [ ] **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-01**: Supprimer `public/sitemap.xml` statique — conflit avec `@nuxtjs/sitemap` dynamique
|
||||
- [x] **FIX-02**: Dockerfile pnpm — remplacer `npm ci` par `pnpm install --frozen-lockfile`
|
||||
- [x] **FIX-03**: Rate limiting contact API — protection anti-spam in-memory sur `/api/contact`
|
||||
- [x] **FIX-04**: Donnees incoherentes — `reviewCount: '50'` vs `totalReviews: 10`, Fiverr URLs `#`
|
||||
- [x] **FIX-05**: Pinning deps — `vue: "latest"` et `vue-router: "latest"` a pincer sur `^3.5.0` / `^4.5.0`
|
||||
|
||||
### 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
|
||||
- **SEO-06**: og:image dynamique generee par page
|
||||
## v1.1 Requirements (M1.1 — SEO Hytale — Autorité & Contenu)
|
||||
|
||||
### 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-02**: Section portfolio Minecraft Java
|
||||
- **CONT-08**: Newsletter / liste email pour communauté Hytale
|
||||
|
||||
## Out of Scope
|
||||
|
||||
| Feature | Reason |
|
||||
|---------|--------|
|
||||
| Tests automatises | Ship d'abord, tests ensuite |
|
||||
| Blog/CMS | Pas de contenu dynamique pour l'instant |
|
||||
| Dashboard admin | Portfolio statique |
|
||||
| Dashboard admin | Blog statique markdown, pas de CMS |
|
||||
| PWA/Service Workers | Pas de besoin offline |
|
||||
| Pub payante | Budget zero |
|
||||
| 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 |
|
||||
|-------------|-------|--------|
|
||||
| CONT-01 | TBD | Pending |
|
||||
| CONT-02 | TBD | Pending |
|
||||
| CONT-03 | TBD | Pending |
|
||||
| CONT-04 | TBD | Pending |
|
||||
| SEO-01 | TBD | Pending |
|
||||
| SEO-02 | TBD | Pending |
|
||||
| SEO-03 | TBD | Pending |
|
||||
| SEO-04 | TBD | Pending |
|
||||
| SEO-05 | TBD | Pending |
|
||||
| I18N-01 | TBD | Pending |
|
||||
| I18N-02 | TBD | Pending |
|
||||
| I18N-03 | TBD | Pending |
|
||||
| I18N-04 | TBD | 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 |
|
||||
| BLOG-01 | Phase 5 | Pending |
|
||||
| BLOG-04 | Phase 5 | Pending |
|
||||
| BLOG-05 | Phase 5 | Pending |
|
||||
| BLOG-02 | Phase 6 | Pending |
|
||||
| BLOG-03 | Phase 6 | Pending |
|
||||
| BLOG-06 | Phase 6 | Pending |
|
||||
| SEO-10 | Phase 7 | Pending |
|
||||
| SEO-11 | Phase 7 | Pending |
|
||||
| SEO-12 | Phase 7 | Pending |
|
||||
| SEO-13 | Phase 7 | Pending |
|
||||
| SEO-15 | Phase 7 | Pending |
|
||||
| BLOG-07 | Phase 8 | Pending |
|
||||
| SEO-14 | Phase 8 | Pending |
|
||||
|
||||
@@ -82,3 +82,86 @@ Plans:
|
||||
| 2. Content | 3/3 | Complete | 2026-04-21 |
|
||||
| 3. SEO & i18n | 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
@@ -1,15 +1,15 @@
|
||||
---
|
||||
gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: Complete
|
||||
milestone: v1.1
|
||||
milestone_name: SEO Hytale — Autorité & Contenu
|
||||
status: In Progress
|
||||
last_updated: "2026-04-21T00:00:00.000Z"
|
||||
progress:
|
||||
total_phases: 4
|
||||
completed_phases: 4
|
||||
total_plans: 7
|
||||
completed_plans: 7
|
||||
percent: 100
|
||||
completed_phases: 0
|
||||
total_plans: 0
|
||||
completed_plans: 0
|
||||
percent: 0
|
||||
---
|
||||
|
||||
# Project State
|
||||
@@ -22,10 +22,18 @@ progress:
|
||||
|
||||
## 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 5–8)
|
||||
|
||||
## Session Notes
|
||||
## Accumulated Context
|
||||
|
||||
- Project initialized 2026-04-10 with codebase mapping + 4-agent research
|
||||
- Brownfield: Nuxt 4 SSR portfolio already functional, needs content pivot to Hytale + SEO fixes
|
||||
- Phase 1 vérifiée complète le 2026-04-21 (commits appliqués hors GSD tracking)
|
||||
- M1 complet — déployé en production sur killiandalcin.fr (phases 1–4)
|
||||
- Stack : Nuxt 4 SSR + Nuxt UI v3 + Tailwind v4 + pnpm
|
||||
- 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
|
||||
|
||||

|
||||
|
||||
## 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) | 20–24px | 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
|
||||
Reference in New Issue
Block a user