# Phase 6: Blog Pages - Pattern Map
**Mapped:** 2026-04-22
**Files analyzed:** 10 (3 new components, 1 new page, 1 new composable, 1 new Nitro plugin, 4 modifications)
**Analogs found:** 10 / 10
## File Classification
| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
|-------------------|------|-----------|----------------|---------------|
| `app/pages/blog/index.vue` (NEW) | page (listing) | SSR request-response | `app/pages/projects.vue` | exact (hero + grid + empty state) |
| `app/pages/blog/[slug].vue` (MODIFY) | page (detail) | SSR request-response | `app/pages/blog/[slug].vue` (existing) + `app/pages/test.vue` | self + role-match |
| `app/components/BlogCard.vue` (NEW) | component (presentational) | prop-driven | `app/components/ProjectCard.vue` | exact (card pattern) |
| `app/components/BlogToc.vue` (NEW) | component (stateful client) | event-driven (IntersectionObserver) | `app/components/layout/AppHeader.vue` (USlideover) + `app/components/content/ProseImg.vue` (defineProps) | partial |
| `app/components/BlogPrevNext.vue` (NEW) | component (presentational) | prop-driven | `app/components/ProjectCard.vue` | role-match (card wrapper) |
| `app/composables/useReadingTime.ts` (NEW) | composable (utility) | pure transform | n/a (aucun composable existant hors `useProjects`) | no analog |
| `app/utils/countWords.ts` (NEW) | utility | pure transform | n/a | no analog |
| `server/plugins/reading-time.ts` (NEW) | Nitro plugin | build-time hook | `server/plugins/rate-limit.ts` | role-match (defineNitroPlugin + hooks.hook) |
| `content.config.ts` (MODIFY) | config (schema) | Zod schema | `content.config.ts` (existing) | self |
| `app/components/layout/AppHeader.vue` (MODIFY) | component (navigation) | prop-driven | `app/components/layout/AppHeader.vue` (existing) | self |
| `i18n/locales/fr.json` + `en.json` (MODIFY) | config (locale) | key-value | existing `fr.json` / `en.json` | self |
## Pattern Assignments
### `app/pages/blog/index.vue` (page listing, SSR)
**Analog:** `app/pages/projects.vue` (lines 1-132)
**Script setup pattern** (projects.vue lines 1-51):
```typescript
const { t } = useI18n()
const { projects } = useProjects()
useSeoMeta({
title: () => t('seo.projects.title'),
description: () => t('seo.projects.description'),
// ...
})
const totalProjects = computed(() => projects.value.length)
const featuredCount = computed(() => projects.value.filter((p) => p.featured).length)
```
**Adaptation Phase 6:** Remplacer `useProjects()` par `useAsyncData` + `queryCollection` littéraux isFr (voir Pitfall 1 RESEARCH §Pattern 1). Ajouter `watch: [locale]`.
**Hero section pattern** (projects.vue lines 56-83) — **à copier tel quel** avec substitution des clés i18n :
```vue
{{ t('blog.subtitle') }} {{ totalArticles }} {{ t('blog.stats.articles') }}{{ t('blog.title') }}
{{ t('blog.emptyState.description') }}