docs(05): add detailed pattern map for @nuxt/content setup and renderer

- Introduced a new document outlining the configuration and component patterns for integrating @nuxt/content.
- Included mappings for `nuxt.config.ts`, `content.config.ts`, and new components `ProseImg.vue` and `Alert.vue`.
- Added example markdown content for testing syntax highlighting and layout.
- Documented critical patterns and anti-patterns to follow during implementation.
This commit is contained in:
2026-04-21 12:52:59 +02:00
parent 29c1dff759
commit 6762990e61
@@ -0,0 +1,372 @@
# Phase 5: @nuxt/content Setup & Renderer — Pattern Map
**Mapped:** 2026-04-21
**Files analyzed:** 7 (2 modifications + 5 créations)
**Analogs found:** 5 / 7
---
## File Classification
| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
|---|---|---|---|---|
| `nuxt.config.ts` | config | — | `nuxt.config.ts` (lui-même, existant) | exact — extension |
| `content.config.ts` | config | CRUD | `nuxt.config.ts` (structure `defineNuxtConfig`) | role-match |
| `app/assets/css/main.css` | config | — | `app/assets/css/main.css` (lui-même, existant) | exact — extension |
| `app/components/content/ProseImg.vue` | component | request-response | `app/components/ProjectCard.vue` (NuxtImg + Props interface) | role-match |
| `app/components/content/Alert.vue` | component | request-response | `app/components/TechBadge.vue` (withDefaults + UBadge + computed map) | role-match |
| `content/fr/blog/test-kotlin-syntax.md` | content | — | aucun | no-analog |
| `content/en/blog/test-kotlin-syntax.md` | content | — | aucun | no-analog |
---
## Pattern Assignments
### `nuxt.config.ts` (config — extension)
**Analog:** `nuxt.config.ts` lui-même (ligne 165)
**État actuel** (lignes 714) :
```typescript
modules: [
'@nuxt/ui',
'@nuxtjs/i18n',
'@nuxt/eslint',
'@nuxtjs/sitemap',
'nuxt-gtag',
'@nuxt/image'
],
```
**Pattern à ajouter — ajout dans `modules`** :
```typescript
modules: [
'@nuxt/ui',
'@nuxtjs/i18n',
'@nuxt/eslint',
'@nuxtjs/sitemap',
'nuxt-gtag',
'@nuxt/image',
'@nuxt/content' // ← ajouter ici
],
```
**Pattern à ajouter — bloc `content` après les modules existants** :
```typescript
content: {
build: {
markdown: {
highlight: {
theme: {
default: 'github-light',
dark: 'github-dark'
},
langs: ['kotlin', 'java', 'typescript', 'shell', 'bash', 'json', 'vue', 'html', 'css']
}
}
},
experimental: {
sqliteConnector: 'native' // Node 22+ — pas de better-sqlite3
}
},
```
**Contexte critique :** `colorMode.classSuffix: ''` est déjà configuré ligne 29 — Shiki dual-theme fonctionne via `html.dark`, donc compatible sans modification.
---
### `content.config.ts` (config — création, racine du projet)
**Analog:** Structure `nuxt.config.ts` (pattern `defineNuxtConfig``defineContentConfig`)
**Pattern complet** (source: RESEARCH.md Pattern 2) :
```typescript
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: '/blog' },
schema: blogSchema,
}),
blog_en: defineCollection({
type: 'page',
source: { include: 'en/blog/**/*.md', prefix: '/en/blog' },
schema: blogSchema,
}),
},
})
```
**Note sur le prefix :** `i18n.strategy: 'prefix_except_default'` avec `defaultLocale: 'fr'` → les articles FR sont sous `/blog/slug`, les EN sous `/en/blog/slug`. (Assumption A3 de RESEARCH.md — valider avec l'article de test.)
---
### `app/assets/css/main.css` (config — extension)
**Analog:** `app/assets/css/main.css` lui-même (lignes 13, existant)
**État actuel** (lignes 13) :
```css
@import "tailwindcss";
@import "@nuxt/ui";
```
**Pattern à ajouter — une ligne après `@import "@nuxt/ui"`** :
```css
@import "tailwindcss";
@import "@nuxt/ui";
@plugin "@tailwindcss/typography"; /* ← ajouter ici — syntaxe Tailwind v4 obligatoire */
```
**Anti-pattern à éviter :** Ne pas utiliser `plugins: [require('@tailwindcss/typography')]` dans `tailwind.config.js` — ignoré en Tailwind v4.
---
### `app/components/content/ProseImg.vue` (component, request-response)
**Analog:** `app/components/ProjectCard.vue` — utilisation de `NuxtImg` avec Props interface (lignes 116, 2535)
**Imports pattern** (depuis ProjectCard.vue, lignes 13) :
```vue
<script setup lang="ts">
// Pas d'import externe — NuxtImg est auto-importé par @nuxt/image
```
**Props pattern** (depuis ProjectCard.vue, lignes 48) :
```typescript
interface Props {
project: Project
}
const props = defineProps<Props>()
```
**NuxtImg pattern** (depuis ProjectCard.vue, lignes 2635) :
```vue
<NuxtImg
:src="project.image"
:alt="`...`"
loading="lazy"
format="webp"
width="400"
height="300"
class="w-full h-52 object-cover"
/>
```
**Pattern cible pour ProseImg.vue** (adapté avec `withDefaults` — depuis TechBadge.vue ligne 11) :
```vue
<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>
```
---
### `app/components/content/Alert.vue` (component, request-response)
**Analog:** `app/components/TechBadge.vue``withDefaults` + computed map de valeurs + composant Nuxt UI (`UBadge`) (lignes 157)
**withDefaults pattern** (depuis TechBadge.vue, lignes 1113) :
```typescript
const props = withDefaults(defineProps<Props>(), {
showLevel: true,
showImage: true,
})
```
**Computed map pattern** (depuis TechBadge.vue, lignes 4456) :
```typescript
const levelColor = computed(() => {
switch (techData.value.level) {
case 'Advanced': return 'success' as const
case 'Intermediate': return 'primary' as const
default: return 'neutral' as const
}
})
```
**Pattern cible pour Alert.vue** (adapté avec `UAlert` Nuxt UI + `ContentSlot` MDC) :
```vue
<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>
```
**Point critique :** `<ContentSlot :use="$slots.default" unwrap="p" />` est obligatoire — sans lui le contenu entre `::alert` et `::` n'est pas rendu (Pitfall 4 RESEARCH.md).
---
### `content/fr/blog/test-kotlin-syntax.md` (content — création)
**Analog:** aucun (pas de fichiers markdown dans le projet actuellement)
**Pattern depuis RESEARCH.md (Code Examples)** :
```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.
::
```
**Critère de validation :** Ce fichier doit couvrir les 4 success criteria : bloc Kotlin coloré (BLOG-04), image via ProseImg (BLOG-05), tableau, callout (BLOG-01).
---
### `content/en/blog/test-kotlin-syntax.md` (content — création)
**Analog:** même structure que la version FR, même slug, contenu traduit en anglais.
---
## Shared Patterns
### Props avec valeurs par défaut (withDefaults)
**Source:** `app/components/TechBadge.vue` lignes 1114
**Apply to:** `ProseImg.vue`, `Alert.vue`
```typescript
const props = withDefaults(defineProps<Props>(), {
// valeurs par défaut pour props optionnelles
})
```
### NuxtImg usage
**Source:** `app/components/ProjectCard.vue` lignes 2635, `app/components/TechBadge.vue` lignes 6574
**Apply to:** `ProseImg.vue`
- Toujours utiliser `:src`, `:alt`, `loading="lazy"` au minimum
- `format="webp"` si format fixe, sinon laisser @nuxt/image décider
- `sizes` pour responsive
### Composants Nuxt UI (UAlert, UBadge)
**Source:** `app/components/TechBadge.vue` ligne 76, `app/components/FAQSection.vue` ligne 33
**Apply to:** `Alert.vue`
- Nuxt UI est auto-importé — pas d'import explicite nécessaire
- Utiliser `color` + `variant` pour le style
- `as any` acceptable pour les types union non-exhaustifs de Nuxt UI
### Convention import types
**Source:** `app/components/ProjectCard.vue` ligne 2
**Apply to:** `content.config.ts`
```typescript
import type { ... } from '~~/shared/types' // types partagés
import { ... } from '@nuxt/content' // imports de librairie
```
### Auto-import composants
**Source:** `nuxt.config.ts` lignes 1519
**Apply to:** `ProseImg.vue`, `Alert.vue`
```typescript
components: [
{
path: '~/components',
pathPrefix: false, // → components/content/Alert.vue est auto-importé
},
],
```
Les composants dans `components/content/` sont auto-importés par Nuxt ET reconnus par `@nuxt/content` comme Prose overrides / composants MDC.
---
## No Analog Found
| File | Role | Data Flow | Reason |
|---|---|---|---|
| `content/fr/blog/test-kotlin-syntax.md` | content | — | Pas de fichiers markdown dans le projet — nouveau format |
| `content/en/blog/test-kotlin-syntax.md` | content | — | Pas de fichiers markdown dans le projet — nouveau format |
Le planner doit utiliser le pattern RESEARCH.md "Code Examples" pour ces deux fichiers.
---
## Metadata
**Analog search scope:** `app/components/`, `app/assets/css/`, `nuxt.config.ts`
**Files scanned:** 6 (nuxt.config.ts, main.css, ProjectCard.vue, TechBadge.vue, FAQSection.vue, app.vue partiel)
**Pattern extraction date:** 2026-04-21