Files
portfolio/.planning/phases/07-seo-blog/07-01-PLAN.md
T

9.2 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
07-seo-blog 01 execute 1
package.json
pnpm-lock.yaml
nuxt.config.ts
content.config.ts
app/app.vue
app/utils/seo-person.ts
true
SEO-11
SEO-12
truths artifacts key_links
nuxt-schema-org est installé et chargé comme module Nuxt
Schema Zod blog_fr/blog_en accepte `updated` (ISO string) en plus de `image`
Une identité Person Killian globale (definePerson) + defineWebSite est émise dans chaque page SSR
nuxt.config.ts référence /api/__sitemap__/urls dans sitemap.sources
path provides contains
app/utils/seo-person.ts KILLIAN_PERSON_ID const + killianPerson object (dérivé de siteConfig) export const KILLIAN_PERSON_ID
path provides contains
content.config.ts blogSchema étendu avec updated.optional() updated: z.string().optional()
path provides contains
nuxt.config.ts module nuxt-schema-org + sitemap.sources nuxt-schema-org
path provides contains
app/app.vue useSchemaOrg global (definePerson + defineWebSite) useSchemaOrg
from to via pattern
app/app.vue app/utils/seo-person.ts import killianPerson killianPerson
from to via pattern
nuxt.config.ts /api/__sitemap__/urls sitemap.sources sitemap.*sources.*__sitemap__/urls
Fondation SEO Blog : installer `nuxt-schema-org`, étendre le schema Zod `blog_fr`/`blog_en` avec `updated`, déclarer l'identité Killian globale (Person + WebSite) dans `app.vue`, et brancher le sitemap dynamique sur un endpoint Nitro (déclaration uniquement — l'endpoint est créé plan 07-04).

Purpose: Aucun des plans Wave 2 ne peut fonctionner sans (a) le module nuxt-schema-org présent dans modules[], (b) le champ updated queryable, (c) l'identité Person disponible par @id global, (d) sitemap.sources wiré. Output: package installé, 1 fichier utilitaire créé, 3 fichiers config/racine modifiés.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/07-seo-blog/07-CONTEXT.md @.planning/phases/07-seo-blog/07-RESEARCH.md @.planning/phases/07-seo-blog/07-PATTERNS.md @nuxt.config.ts @content.config.ts @app/app.vue @app/data/site.ts Depuis app/data/site.ts : - `siteConfig.url` = 'https://killiandalcin.fr' - `siteConfig.social` = tableau avec entrées Gitea, LinkedIn, Discord, Email (reprendre `url` pour `sameAs`)

Depuis content.config.ts (existant) :

const blogSchema = z.object({
  title: z.string(),
  description: z.string(),
  date: z.string(),
  tags: z.array(z.string()).optional(),
  image: z.string().optional(),   // DÉJÀ présent (D-14 #2 = no-op)
  draft: z.boolean().optional().default(false),
  wordCount: z.number().optional(),
  minutes: z.number().optional(),
})

Depuis app/app.vue (existant) : useHead + useLocaleHead({ seo: true }) — NE PAS remplacer, APPEND.

Auto-imports nuxt-schema-org (une fois module ajouté) : useSchemaOrg, definePerson, defineWebSite, defineArticle, defineBreadcrumb, defineWebPage.

Task 1: Installer nuxt-schema-org + étendre content.config.ts (schema updated) package.json, pnpm-lock.yaml, content.config.ts - package.json (vérifier absence de nuxt-schema-org) - content.config.ts (schéma actuel, ligne 3-12) - .planning/phases/07-seo-blog/07-RESEARCH.md §Standard Stack (version cible ^6.0.4) - .planning/phases/07-seo-blog/07-RESEARCH.md Pitfall 8 (cache invalidation) - .planning/phases/07-seo-blog/07-PATTERNS.md §content.config.ts (modify) 1. Installer : `pnpm add -D nuxt-schema-org@^6.0.4` (D-01, D-04 — NE PAS installer `@nuxtjs/seo` umbrella). 2. Dans `content.config.ts`, modifier `blogSchema` : ajouter exactement la ligne `updated: z.string().optional(),` entre `date: z.string(),` et `tags: z.array(z.string()).optional(),` (D-13, D-14). Ne PAS toucher aux autres champs (`image` déjà présent). 3. Vider les caches pour forcer la re-ingestion : `rm -rf node_modules/.cache/content .nuxt` (Pitfall 8 RESEARCH). grep -q '"nuxt-schema-org"' package.json && grep -q 'updated: z.string().optional()' content.config.ts && pnpm typecheck nuxt-schema-org^6.0.4 dans devDependencies, `updated: z.string().optional()` présent dans blogSchema, caches vidés, typecheck exit 0. Task 2: Enregistrer module + sitemap.sources dans nuxt.config.ts, créer app/utils/seo-person.ts, brancher useSchemaOrg global dans app/app.vue nuxt.config.ts, app/utils/seo-person.ts, app/app.vue - nuxt.config.ts (lignes 1-82 entier, surtout modules[] 5-13) - app/app.vue (10 lignes entier) - app/data/site.ts (lignes 5-43 — source url + social) - .planning/phases/07-seo-blog/07-PATTERNS.md §seo-person.ts, §nuxt.config.ts, §app.vue - .planning/phases/07-seo-blog/07-RESEARCH.md §Pattern 1 (Global Schema Identity) 1. **nuxt.config.ts** : - Ajouter `'nuxt-schema-org'` dans `modules[]` après `'@nuxtjs/sitemap'` (ligne ~12). - Ajouter, au même niveau d'indentation que `site:` et `i18n:`, le bloc : ```ts sitemap: { sources: ['/api/__sitemap__/urls'], }, ``` - Ne PAS modifier `site`, `i18n`, `content`, `runtimeConfig`, `gtag`, `vite`. 2. **Créer `app/utils/seo-person.ts`** avec le contenu exact (pattern `app/utils/countWords.ts` : JSDoc top + export nommé + const typé) : ```ts /** * Global Person identity for schema.org (Killian Dal-Cin). * Consumed by: app/app.vue (definePerson global) and app/pages/blog/[slug].vue (author/publisher @id ref). * Derives URLs from siteConfig — single source of truth. */ import { siteConfig } from '~/data/site'
   export const KILLIAN_PERSON_ID = '#killian'

   export const killianPerson = {
     '@id': KILLIAN_PERSON_ID,
     name: "Killian' Dal-Cin",
     url: siteConfig.url,
     jobTitle: siteConfig.jobTitle,
     sameAs: siteConfig.social
       .filter((s) => s.name !== 'Email')
       .map((s) => s.url),
   } as const
   ```
3. **app/app.vue** : APPEND (ne pas remplacer) après le bloc `useHead({...})` existant, AVANT la fermeture `</script>` :
   ```ts
   import { killianPerson } from '~/utils/seo-person'

   useSchemaOrg([
     definePerson(killianPerson),
     defineWebSite({
       name: "Killian' Dal-Cin — Hytale Plugin Developer",
       inLanguage: ['fr-FR', 'en-US'],
     }),
   ])
   ```
   Ne pas toucher au `<template>` ni au `useLocaleHead`/`useHead` existants.
grep -q "'nuxt-schema-org'" nuxt.config.ts && grep -q "/api/__sitemap__/urls" nuxt.config.ts && grep -q "KILLIAN_PERSON_ID" app/utils/seo-person.ts && grep -q "definePerson(killianPerson)" app/app.vue && pnpm typecheck && pnpm dev --port 3000 & sleep 10 && curl -s http://localhost:3000/ | grep -q '"@type":"Person"' && kill %1 Module chargé sans erreur ; `curl /` contient un `<script type="application/ld+json">` avec `"@type":"Person"` et `"@id":"#killian"` émis en SSR ; typecheck vert.

<threat_model>

Trust Boundaries

Boundary Description
build → runtime Dépendance npm (nuxt-schema-org) introduite dans le supply chain — version figée ^6.0.4

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-07-01 Tampering package.json (nouveau module) mitigate Version explicite ^6.0.4 + pnpm-lock.yaml committé, intégrité pnpm
T-07-02 Information Disclosure schema.org Person (exposition URLs publiques) accept URLs déjà publiques (portfolio freelance), email exclu de sameAs
</threat_model>
- Module présent : `grep "'nuxt-schema-org'" nuxt.config.ts` - Sitemap source : `grep "sources.*__sitemap__/urls" nuxt.config.ts` - Schema étendu : `grep "updated: z.string().optional()" content.config.ts` - Person global en HTML SSR : `curl http://localhost:3000/ | grep '"@id":"#killian"'` - TypeScript : `pnpm typecheck` exit 0

<success_criteria>

  1. nuxt-schema-org installé (^6.0.4), lockfile à jour
  2. updated queryable (Zod) — un article avec updated: frontmatter sera exposé par queryCollection(...).select('updated')
  3. curl / émet JSON-LD global avec Person (@id=#killian) + WebSite, en SSR pur
  4. nuxt.config.ts > sitemap.sources déclaré (l'endpoint sera créé 07-04) </success_criteria>
Après complétion, créer `.planning/phases/07-seo-blog/07-01-SUMMARY.md`.