11 KiB
Phase 7: SEO Blog - Context
Gathered: 2026-04-22 Status: Ready for planning
## Phase BoundaryRendre chaque page blog (article + listing) parfaitement indexable par les moteurs de recherche : meta tags complets et uniques par article, JSON-LD Article + BreadcrumbList valides côté article, JSON-LD Blog simple côté listing, sitemap incluant /blog/[slug] FR+EN avec alternates hreflang. Aucun JavaScript client requis pour que le crawl fonctionne (SSR pur).
Hors scope : JSON-LD WebSite/Person global sur la home, refonte SEO des autres pages (projets, hytale, contact), liens internes /hytale ↔ articles (= SEO-14, Phase 8 cocon sémantique).
Génération JSON-LD
- D-01: Installer le module
nuxt-schema-org(famille Nuxt SEO). APIdefineArticle()/defineBreadcrumb()typée, auto-merge avecsite.url, locale-aware FR/EN. Évite le hand-rolleduseHead({ script: [...] })répétitif et le drift schema.org. - D-02: Sur
/blog/[slug]→useSchemaOrg([defineArticle(...), defineBreadcrumb(...)]). Champs Article :headline,description,image,datePublished,dateModified,author(Person Killian),publisher(Person Killian),inLanguage(fr-FR / en-US),mainEntityOfPage. - D-03: Sur
/blog(listing) →useSchemaOrg([defineCollectionPage(...)])ou équivalentBlogminimal (pas deBlogPosting[]exhaustif — coût/bruit). Breadcrumb Accueil → Blog. - D-04: Ne PAS installer le bundle
@nuxtjs/seoumbrella — doublonne avec@nuxtjs/sitemapdéjà présent et embarque modules non désirés (link-checker, robots déjà géré). Cherry-picknuxt-schema-org(+ éventuellementnuxt-og-imagereporté en Phase 8 si besoin).
og:image
- D-05: Stratégie hybride frontmatter → fallback statique. Si l'article a
image:en frontmatter (chemin relatif depuispublic/) → utilisé tel quel. Sinon → fallback branded statique/og-blog-default.jpg(1200×630, à créer une fois souspublic/, design : logo Killian' + accent typographique "Blog · killiandalcin.fr"). - D-06: Composable ou helper
resolveOgImage(article)qui retourne le chemin absolu (préfixésite.url) — utilisé à la fois paruseSeoMeta({ ogImage })ET pardefineArticle({ image })pour cohérence. - D-07: Génération dynamique via
nuxt-og-image(Satori) explicitement reportée — coût (asset à designer + runtime edge) > bénéfice tant qu'on n'a pas validé le ratio articles publiés × engagement social.
Sitemap
- D-08: Endpoint Nitro
server/api/__sitemap__/urls.tsqui queryblog_fretblog_en(wheredraft = false), retourne pour chaque article{ loc, lastmod, alternatives: [{ hreflang, href }] }. Référencé dansnuxt.config.ts > sitemap.sources. Pattern officiel@nuxtjs/sitemap+ i18n. - D-09:
lastmod=dateModifiedde l'article (=updatedfrontmatter si présent, sinondate). - D-10: Drafts (
draft: true) EXCLUS du sitemap — cohérent avec le filtrage des listings (Phase 6 D-14). Restent accessibles par URL directe pour preview. - D-11: Alternates hreflang générés par paire de slugs : si
mon-slug.mdexiste en FR ET EN → entrées sitemap déclarentxhtml:link rel="alternate" hreflang="fr"ethreflang="en"croisés (+x-defaultpointant vers FR, locale par défaut). Si l'article n'existe que dans une langue → pas d'alternate.
Article metadata
- D-12:
authoretpublisher: constante globale Killian (single Person identity), définie dans un helper partagé (ex:app/utils/seo-person.ts) ou directement dans la config schema-org globale (useSchemaOrgau niveau app.vue avecdefineWebSite+definePersonKillian, hérité par les Article enfants). Pas de support frontmatterauthor:override (pas de guest authors planifiés). - D-13:
dateModifiedsource : champupdatedoptionnel dans le frontmatter (Zodupdated.optional()à ajouter au schemablog_fr/blog_en). Si absent →dateModified = date. Pas de git mtime (casse en build Docker sans .git layer).
Schema content extension
- D-14: Étendre les collections
blog_fr/blog_en(config @nuxt/content) avec :updated: z.string().optional()(ISO date, alimente dateModified)image: z.string().optional()(déjà présent en pratique frontmatter, formaliser dans le schema)
useSeoMeta enrichissement
- D-15:
[slug].vueuseSeoMetacomplété avec :ogImage(résolu via D-06),ogUrl(URL canonique localisée),ogLocale(fr_FR/en_US),ogLocaleAlternate(l'autre locale si l'article existe dans les deux),twitterCard: 'summary_large_image',twitterImage(= ogImage),articlePublishedTime,articleModifiedTime,articleAuthor. - D-16:
/blogindex :useSeoMetaenrichi avecogImage(= fallback statique/og-blog-default.jpg),ogType: 'website',ogLocale,ogLocaleAlternate.
Claude's Discretion
- Naming exact du composable/helper de résolution og:image (D-06)
- Format précis de la
descriptiondu JSON-LDBlog/CollectionPagedu listing (D-03) - Choix entre déclarer Killian en
definePersonglobal au niveauapp.vuevs enauthorinline dans chaquedefineArticle— selon ce quenuxt-schema-orgrecommande (à confirmer en research/plan) - Design exact de
/og-blog-default.jpg(juste un fallback branded, pas critique tant que ≠og-image.pngM1 générique)
<canonical_refs>
Canonical References
Downstream agents MUST read these before planning or implementing.
Specs Phase 7 — sources internes
.planning/REQUIREMENTS.md§SEO-10 → SEO-13, SEO-15 — exigences acceptance pour cette phase.planning/ROADMAP.md§"Phase 7: SEO Blog" — Success Criteria (5 critères curl)
Décisions héritées des phases précédentes
.planning/phases/03-seo-i18n/03-CONTEXT.md— décisions SEO M1 (siteConfig, baseUrl, useLocaleHead pattern).planning/phases/05-nuxt-content-setup-renderer/05-CONTEXT.md— schémas Zod blog_fr/blog_en, conventions @nuxt/content v3.planning/phases/06-blog-pages/06-CONTEXT.md— D-14 (drafts accessibles direct URL mais filtrés des listings), conventions BlogCard / breadcrumb.planning/phases/06-blog-pages/06-04-SUMMARY.md— état actuel useSeoMeta sur[slug].vue
Code existant à étendre
app/pages/blog/[slug].vue— useSeoMeta minimal à enrichir + ajout useSchemaOrg (D-02, D-15)app/pages/blog/index.vue— useSeoMeta minimal à enrichir + JSON-LD listing (D-03, D-16)app/app.vue— useLocaleHead({ seo: true }) déjà présent ; potentiellement y ajouter le definePerson/defineWebSite global (D-12)nuxt.config.ts—site,i18n,@nuxtjs/sitemapconfig existante ; ajouternuxt-schema-orgau modules array +sitemap.sourcesserver/plugins/reading-time.ts— pattern Nitro hookcontent:file:afterParse(référence pour ajouter d'autres injections schema si nécessaire)app/data/site.ts(ou équivalent siteConfig) — source identité Killian pour Person/publisher
Docs externes (officielles)
nuxt-schema-orgdocs : https://nuxtseo.com/schema-org — defineArticle, defineBreadcrumb, defineWebSite, definePerson@nuxtjs/sitemapdocs : https://nuxtseo.com/sitemap — sources config, multi-sitemap i18n, alternates hreflang@nuxt/content v3queryCollection API — déjà maîtrisé Phase 5/6- schema.org/Article — champs requis Google : headline, image, datePublished, author, publisher (Organization OR Person)
- Google Search Central — Article structured data : https://developers.google.com/search/docs/appearance/structured-data/article
</canonical_refs>
<code_context>
Existing Code Insights
Reusable Assets
useSeoMeta()(Nuxt auto-import) : déjà utilisé sur[slug].vueetindex.vue— étendre, ne pas réécrireuseLocaleHead({ seo: true })(@nuxtjs/i18n) : déjà géré au niveauapp.vuepour les hreflang globaux et og:locale — ne pas dupliquer côté pagesqueryCollection('blog_fr' | 'blog_en'): pattern figé Phase 5/6, à réutiliser pour le sitemap source endpointuseReadingTime()composable + champsminutes/wordCountPhase 6 : disponibles si on veut les exposer en JSON-LDwordCountsiteConfig/app/data/site.ts(à confirmer chemin) : source de vérité identité Killian (nom, URL, social) pour Person
Established Patterns
- Locale via
useI18n()+localePath()partout — toute URL canonique doit passer parlocalePathpour respecterprefixstrategy useAsyncDatakeys incluent${locale.value}pour invalidation correcte au switch FR/EN- Schema Zod content : extension via
.optional()pattern (cf. Phase 6 D-01 pourwordCount/minutes) — appliquer même approche pourupdated/image - Convention og:image M1 explicite : jamais réutiliser
og-image.pnggénérique sur les pages blog
Integration Points
nuxt.config.ts > modules[]: ajouter'nuxt-schema-org'(ordre indifférent, mais cohérent à côté de@nuxtjs/sitemap)nuxt.config.ts > sitemap: ajoutersources: ['/api/__sitemap__/urls']et confirmer config i18n auto-detectionserver/api/__sitemap__/urls.ts: nouveau fichier — pattern Nitro server route, retourneSitemapUrlInput[]content.config.ts(ou bloc équivalent) : étendre les schémasblog_fr/blog_enavecupdated,imagepublic/og-blog-default.jpg: nouvel asset 1200×630 à créer
</code_context>
## Specific Ideas- Killian = Person unique (pas d'Organization) — portfolio personnel freelance, pas une marque collective
- Articles bilingues = même slug FR et EN doivent rester appairables (cohérent avec convention Phase 5/6 : nom de fichier identique entre
content/fr/blog/etcontent/en/blog/) - Validation finale doit pouvoir se faire en pur
curlsans navigateur (cf. Success Criteria ROADMAP) — donc tout le SEO doit être SSR, jamais hydraté côté client
- og:image dynamique via nuxt-og-image (Satori) — reportée. À reconsidérer si traction social mesurée justifie l'investissement design + runtime edge.
- JSON-LD WebSite + Person globaux sur la home — relève d'une phase SEO globale du portfolio, pas SEO blog. À ajouter si Phase 8 ou audit SEO ultérieur le demande.
- Liens internes structurés /hytale ↔ articles (SEO-14) — explicitement Phase 8 (Cocon Sémantique).
- git mtime pour dateModified — non retenu (casse Docker sans .git). À reconsidérer si on ajoute un layer git ou un build-time stamping en CI.
- JSON-LD
BlogPosting[]exhaustif sur /blog — bruit pour Google, pas standard pour les listings. Si besoin de richesse listing, préférerItemListminimal en Phase 8.
Phase: 07-seo-blog Context gathered: 2026-04-22