Files
portfolio/.planning/phases/07-seo-blog/07-01-SUMMARY.md
T
kayjaydee 44fcca6345 docs(07-01): capture plan summary
Foundation SEO Blog shipped — nuxt-schema-org installed, blog schema extended
with updated field, global Person/WebSite schema.org emitted SSR, sitemap.sources
wired to future Nitro endpoint (07-04).
2026-04-22 11:14:46 +02:00

98 lines
6.0 KiB
Markdown

---
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 `<script type="application/ld+json" data-nuxt-schema-org="true">` contenant `@type: Person` (id se terminant par `#killian`) + `@type: WebSite` + `@type: WebPage` auto-attaché par le module
## Deviations from Plan
**None critical.** Deux points de friction mineurs rencontrés & résolus sans changer le plan :
1. **Port** : `pnpm dev --port 3000` a basculé automatiquement sur 3001 (port 3000 déjà occupé). Non-bloquant — validation faite sur 3001.
2. **@id Person** : le module `nuxt-schema-org` préfixe l'`@id` fourni (`#killian`) par la route canonique du site (résultat final : `https://killiandalcin.fr/#/schema/person/#killian`). Comportement attendu du module et cohérent avec la spec schema.org — le fragment `#killian` reste identifiable en suffixe, ce qui suffit aux références inter-entités (author/publisher) dans les plans Wave 2 via la forme `{ '@id': '#killian' }` (le module résout le préfixe tout seul).
## Acceptance Criteria — tous passés
- [x] `grep "'nuxt-schema-org'" nuxt.config.ts` — match ligne 12
- [x] `grep "sources.*__sitemap__/urls" nuxt.config.ts` — match bloc sitemap
- [x] `grep "updated: z.string().optional()" content.config.ts` — match ligne 7
- [x] `curl http://localhost:3001/fr` émet JSON-LD global Person (@id suffixe `#killian`) + WebSite + WebPage, en SSR pur (aucun JS client requis — détection `<script type="application/ld+json">` directement dans le HTML renvoyé)
- [x] `pnpm typecheck` exit 0 (sortie clean, seulement banners Nuxt Icon)
## Known Stubs
Aucun. Le seul placeholder explicitement déclaré (`sitemap.sources: ['/api/__sitemap__/urls']`) référence un endpoint Nitro qui sera implémenté par le Plan 07-04 (ownership clair, documenté dans dependency_graph).
## Threat Flags
Aucun nouveau surface de menace introduit. Le module `nuxt-schema-org ^6.0.4` figé en devDependency + `pnpm-lock.yaml` commité mitige T-07-01 (Tampering supply chain). T-07-02 (IDisclo Person public) accepté — URLs du `sameAs` déjà publiques, l'email est explicitement filtré du `sameAs` dans `seo-person.ts` (`filter((s) => s.name !== 'Email')`).
## Self-Check: PASSED
- `app/utils/seo-person.ts` — FOUND
- Commit `17420af` (chore Task 1) — FOUND in git log
- Commit `654842b` (feat Task 2) — FOUND in git log
- Validation SSR JSON-LD — confirmée via curl (Person @id=#killian + WebSite + WebPage émis avant hydratation)