diff --git a/openspec/changes/archive/2026-03-07-seo-meta-og/design.md b/openspec/changes/archive/2026-03-07-seo-meta-og/design.md new file mode 100644 index 000000000..c36b465c6 --- /dev/null +++ b/openspec/changes/archive/2026-03-07-seo-meta-og/design.md @@ -0,0 +1,166 @@ +# Design: seo-meta-og + +## Scope + +Changes only in `packages/docs/`. No app code touched. + +## URL structure + +``` +https://openpencil.dev/ → EN (root, x-default) +https://openpencil.dev/de/ → DE +https://openpencil.dev/fr/ → FR +https://openpencil.dev/es/ → ES +https://openpencil.dev/it/ → IT +https://openpencil.dev/pl/ → PL +``` + +Page paths follow the same pattern: `/guide/features` (EN), `/de/guide/features` (DE), etc. + +## Locale map + +```ts +const LOCALES = { + en: { lang: 'en', hreflang: 'en', ogLocale: 'en_US', prefix: '' }, + de: { lang: 'de', hreflang: 'de', ogLocale: 'de_DE', prefix: '/de' }, + fr: { lang: 'fr', hreflang: 'fr', ogLocale: 'fr_FR', prefix: '/fr' }, + es: { lang: 'es', hreflang: 'es', ogLocale: 'es_ES', prefix: '/es' }, + it: { lang: 'it', hreflang: 'it', ogLocale: 'it_IT', prefix: '/it' }, + pl: { lang: 'pl', hreflang: 'pl', ogLocale: 'pl_PL', prefix: '/pl' }, +} +const BASE = 'https://openpencil.dev' +const LOCALE_PREFIXES = ['de', 'fr', 'es', 'it', 'pl'] +``` + +## 1. Global head tags (`config.ts`) + +Add to the root `head: []`: + +```ts +['meta', { property: 'og:site_name', content: 'OpenPencil' }], +['meta', { property: 'og:image', content: 'https://openpencil.dev/screenshot.png' }], +['meta', { property: 'og:image:width', content: '2784' }], +['meta', { property: 'og:image:height', content: '1824' }], +['meta', { property: 'og:image:alt', content: 'OpenPencil — AI-Native Design Editor' }], +['meta', { name: 'twitter:card', content: 'summary_large_image' }], +['meta', { name: 'twitter:site', content: '@openpencildev' }], +['meta', { name: 'twitter:image', content: 'https://openpencil.dev/screenshot.png' }], +``` + +Keep existing: favicon, `og:type`, `og:title`, `og:description`. + +## 2. `transformPageData` hook + +Fires at build time for every page. Logic: + +```ts +transformPageData(pageData) { + const rel = pageData.relativePath // e.g. 'de/guide/features.md' + + // Determine current locale + const localeKey = LOCALE_PREFIXES.find(p => rel.startsWith(p + '/')) ?? 'en' + const locale = LOCALES[localeKey] + + // Slug = path without locale prefix and .md, index → '' + const slug = rel + .replace(/^(de|fr|es|it|pl)\//, '') + .replace(/\.md$/, '') + .replace(/\/index$/, '') + .replace(/^index$/, '') + + const pageUrl = `${BASE}${locale.prefix}/${slug}`.replace(/\/$/, '') || BASE + const enUrl = slug ? `${BASE}/${slug}` : BASE + + pageData.frontmatter.head ??= [] + const h = pageData.frontmatter.head + + // Canonical (locale-specific) + h.push(['link', { rel: 'canonical', href: pageUrl }]) + + // og:url (locale-specific) + h.push(['meta', { property: 'og:url', content: pageUrl }]) + + // og:locale (current locale) + h.push(['meta', { property: 'og:locale', content: locale.ogLocale }]) + + // og:locale:alternate for other locales + for (const [key, loc] of Object.entries(LOCALES)) { + if (key !== localeKey) { + h.push(['meta', { property: 'og:locale:alternate', content: loc.ogLocale }]) + } + } + + // hreflang for all locales + x-default + for (const [key, loc] of Object.entries(LOCALES)) { + const altUrl = slug ? `${BASE}${loc.prefix}/${slug}` : `${BASE}${loc.prefix || ''}` + h.push(['link', { rel: 'alternate', hreflang: loc.hreflang, href: altUrl.replace(/\/$/, '') || BASE }]) + } + // x-default points to EN + h.push(['link', { rel: 'alternate', hreflang: 'x-default', href: enUrl || BASE }]) + + // og:title (per page) + if (pageData.title) { + h.push(['meta', { property: 'og:title', content: `${pageData.title} — OpenPencil` }]) + h.push(['meta', { name: 'twitter:title', content: `${pageData.title} — OpenPencil` }]) + } + + // og:description + meta description (per page) + if (pageData.description) { + h.push(['meta', { property: 'og:description', content: pageData.description }]) + h.push(['meta', { name: 'twitter:description', content: pageData.description }]) + h.push(['meta', { name: 'description', content: pageData.description }]) + } +} +``` + +## 3. Sitemap with hreflang alternates + +```ts +sitemap: { + hostname: BASE, + transformItems(items) { + return items.map(item => { + // Determine slug and locale from url + const localeKey = LOCALE_PREFIXES.find(p => item.url.startsWith('/' + p + '/')) ?? 'en' + const slug = item.url.replace(/^\/(de|fr|es|it|pl)\//, '/').replace(/\/$/, '') || '/' + + return { + ...item, + links: Object.entries(LOCALES).map(([, loc]) => ({ + lang: loc.hreflang, + url: `${BASE}${loc.prefix}${slug === '/' ? '' : slug}` || BASE, + })), + } + }) + }, +} +``` + +## 4. JSON-LD component (`SchemaOrg.vue`) + +Renders a ` + + diff --git a/packages/docs/de/index.md b/packages/docs/de/index.md index f025bf40d..1a44d2f9d 100644 --- a/packages/docs/de/index.md +++ b/packages/docs/de/index.md @@ -1,5 +1,6 @@ --- layout: home +title: OpenPencil — KI-nativer Design-Editor description: Open-Source Figma-Alternative. Vollständig lokal, KI-nativ, programmierbar. hero: diff --git a/packages/docs/es/index.md b/packages/docs/es/index.md index 7e5114040..a26041ec3 100644 --- a/packages/docs/es/index.md +++ b/packages/docs/es/index.md @@ -1,5 +1,6 @@ --- layout: home +title: OpenPencil — Editor de Diseño IA-Nativo description: Alternativa open-source a Figma. Completamente local, IA-nativa, programable. hero: diff --git a/packages/docs/fr/index.md b/packages/docs/fr/index.md index bc8bebc8a..3f5827ec5 100644 --- a/packages/docs/fr/index.md +++ b/packages/docs/fr/index.md @@ -1,5 +1,6 @@ --- layout: home +title: OpenPencil — Éditeur de Design IA-Natif description: Alternative open-source à Figma. Entièrement local, IA-native, programmable. hero: diff --git a/packages/docs/index.md b/packages/docs/index.md index 5f0d54417..cba2b5b9e 100644 --- a/packages/docs/index.md +++ b/packages/docs/index.md @@ -1,5 +1,6 @@ --- layout: home +title: OpenPencil — AI-Native Design Editor description: Open-source Figma alternative. Fully local, AI-native, programmable. hero: diff --git a/packages/docs/it/index.md b/packages/docs/it/index.md index 1c2db4177..b4ddec76a 100644 --- a/packages/docs/it/index.md +++ b/packages/docs/it/index.md @@ -1,5 +1,6 @@ --- layout: home +title: OpenPencil — Editor di Design IA-Nativo description: Alternativa open-source a Figma. Completamente locale, IA-nativa, programmabile. hero: diff --git a/packages/docs/pl/index.md b/packages/docs/pl/index.md index cce917216..f37d2049b 100644 --- a/packages/docs/pl/index.md +++ b/packages/docs/pl/index.md @@ -1,5 +1,6 @@ --- layout: home +title: OpenPencil — Edytor Graficzny z Natywnym AI description: Open-source'owa alternatywa dla Figma. W pełni lokalna, natywnie AI, programowalna. hero: