--- phase: 07-seo-blog plan: 01 subsystem: seo-infrastructure tags: [seo, schema-org, sitemap, nuxt-content, foundation] status: shipped completed: 2026-04-22 requirements: [SEO-11, SEO-12] dependency_graph: requires: - "@nuxtjs/sitemap (déjà présent)" - "@nuxt/content blog_fr/blog_en (Phase 5)" - "app/data/site.ts siteConfig" provides: - "Module nuxt-schema-org chargé globalement (useSchemaOrg / definePerson / defineWebSite / defineArticle / defineBreadcrumb auto-imports)" - "Identité Person Killian globale (@id #killian) injectée via JSON-LD SSR sur chaque page" - "WebSite schema.org global (FR+EN inLanguage)" - "Schema Zod blog `updated: z.string().optional()` queryable (dateModified upstream)" - "nuxt.config.ts > sitemap.sources branché sur /api/__sitemap__/urls (endpoint créé Plan 07-04)" - "app/utils/seo-person.ts : KILLIAN_PERSON_ID + killianPerson (single source of truth)" affects: - "Wave 2 Plans 07-02/07-03/07-04 (consomment l'identité Person + module schema-org)" tech_stack: added: - "nuxt-schema-org ^6.0.4 (devDependency)" patterns: - "Auto-imports nuxt-schema-org : useSchemaOrg, definePerson, defineWebSite (pas d'import explicite requis dans .vue)" - "Person helper module-level (pattern app/utils/countWords.ts) : JSDoc top + named const typé `as const`" key_files: created: - "app/utils/seo-person.ts (20 lignes, KILLIAN_PERSON_ID + killianPerson)" modified: - "package.json + pnpm-lock.yaml (devDep nuxt-schema-org ^6.0.4)" - "content.config.ts (blogSchema + updated: z.string().optional())" - "nuxt.config.ts (modules[] + 'nuxt-schema-org', new sitemap.sources)" - "app/app.vue (useSchemaOrg global append, pas de remplacement du useLocaleHead/useHead existant)" decisions: - "D-01, D-04: cherry-pick nuxt-schema-org (pas le bundle @nuxtjs/seo umbrella qui doublonne avec sitemap déjà présent)" - "D-12: Person Killian déclarée en global (app.vue) — les defineArticle des plans suivants référenceront @id=#killian au lieu de réinliner author/publisher" - "D-13, D-14: `updated` optional dans schema Zod (si absent → dateModified = date dans les plans downstream)" - "Sitemap endpoint déclaré mais pas créé ici (Plan 07-04 owner)" metrics: duration_minutes: 8 tasks_completed: 2 commits: 2 files_created: 1 files_modified: 4 --- # Phase 7 Plan 1 : Foundation SEO Blog — Summary **One-liner** : Module `nuxt-schema-org` installé + identité Person/WebSite Killian globale + schema Zod blog étendu avec `updated` + `sitemap.sources` branché sur endpoint Nitro futur. ## Ce qui a été fait **Task 1 — `chore(07-01)`** (commit `17420af`) - `pnpm add -D nuxt-schema-org@^6.0.4` - `content.config.ts` : ajout `updated: z.string().optional()` entre `date` et `tags` dans `blogSchema` (partagé `blog_fr` + `blog_en`) - Caches `node_modules/.cache/content` + `.nuxt` vidés (Pitfall 8 research — forcer la re-ingestion) - `pnpm typecheck` exit 0 **Task 2 — `feat(07-01)`** (commit `654842b`) - `nuxt.config.ts` : `'nuxt-schema-org'` ajouté dans `modules[]` juste après `'@nuxtjs/sitemap'`; nouveau bloc `sitemap: { sources: ['/api/__sitemap__/urls'] }` au même niveau d'indentation que `site`/`i18n` - `app/utils/seo-person.ts` créé : exporte `KILLIAN_PERSON_ID = '#killian'` et `killianPerson` (dérivé de `siteConfig` — `sameAs` filtre l'entrée `Email`) - `app/app.vue` : append (pas de remplacement) d'un bloc `useSchemaOrg([definePerson(killianPerson), defineWebSite({ name, inLanguage: ['fr-FR','en-US'] })])` après le `useHead` existant - `pnpm typecheck` exit 0 - Validation SSR curl : `curl http://localhost:3001/fr` renvoie bien un `