docs(07): plan SEO blog — 4 plans (schema-org, useSeoMeta enrich, sitemap Nitro) .planning/phases/07-seo-blog/07-01-PLAN.md .planning/phases/07-seo-blog/07-02-PLAN.md .planning/phases/07-seo-blog/07-03-PLAN.md .planning/phases/07-seo-blog/07-04-PLAN.md .planning/ROADMAP.md
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
---
|
||||
phase: 07-seo-blog
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- package.json
|
||||
- pnpm-lock.yaml
|
||||
- nuxt.config.ts
|
||||
- content.config.ts
|
||||
- app/app.vue
|
||||
- app/utils/seo-person.ts
|
||||
autonomous: true
|
||||
requirements: [SEO-11, SEO-12]
|
||||
must_haves:
|
||||
truths:
|
||||
- "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"
|
||||
artifacts:
|
||||
- path: "app/utils/seo-person.ts"
|
||||
provides: "KILLIAN_PERSON_ID const + killianPerson object (dérivé de siteConfig)"
|
||||
contains: "export const KILLIAN_PERSON_ID"
|
||||
- path: "content.config.ts"
|
||||
provides: "blogSchema étendu avec updated.optional()"
|
||||
contains: "updated: z.string().optional()"
|
||||
- path: "nuxt.config.ts"
|
||||
provides: "module nuxt-schema-org + sitemap.sources"
|
||||
contains: "nuxt-schema-org"
|
||||
- path: "app/app.vue"
|
||||
provides: "useSchemaOrg global (definePerson + defineWebSite)"
|
||||
contains: "useSchemaOrg"
|
||||
key_links:
|
||||
- from: "app/app.vue"
|
||||
to: "app/utils/seo-person.ts"
|
||||
via: "import killianPerson"
|
||||
pattern: "killianPerson"
|
||||
- from: "nuxt.config.ts"
|
||||
to: "/api/__sitemap__/urls"
|
||||
via: "sitemap.sources"
|
||||
pattern: "sitemap.*sources.*__sitemap__/urls"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<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
|
||||
|
||||
<interfaces>
|
||||
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) :
|
||||
```ts
|
||||
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`.
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Installer nuxt-schema-org + étendre content.config.ts (schema updated)</name>
|
||||
<files>package.json, pnpm-lock.yaml, content.config.ts</files>
|
||||
<read_first>
|
||||
- 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)
|
||||
</read_first>
|
||||
<action>
|
||||
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).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>grep -q '"nuxt-schema-org"' package.json && grep -q 'updated: z.string().optional()' content.config.ts && pnpm typecheck</automated>
|
||||
</verify>
|
||||
<done>nuxt-schema-org^6.0.4 dans devDependencies, `updated: z.string().optional()` présent dans blogSchema, caches vidés, typecheck exit 0.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Enregistrer module + sitemap.sources dans nuxt.config.ts, créer app/utils/seo-person.ts, brancher useSchemaOrg global dans app/app.vue</name>
|
||||
<files>nuxt.config.ts, app/utils/seo-person.ts, app/app.vue</files>
|
||||
<read_first>
|
||||
- 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)
|
||||
</read_first>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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</automated>
|
||||
</verify>
|
||||
<done>Module chargé sans erreur ; `curl /` contient un `<script type="application/ld+json">` avec `"@type":"Person"` et `"@id":"#killian"` émis en SSR ; typecheck vert.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<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>
|
||||
|
||||
<verification>
|
||||
- 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
|
||||
</verification>
|
||||
|
||||
<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>
|
||||
|
||||
<output>
|
||||
Après complétion, créer `.planning/phases/07-seo-blog/07-01-SUMMARY.md`.
|
||||
</output>
|
||||
Reference in New Issue
Block a user