From 513e0b6f4a070b77747b4d385c8040957a8d8b32 Mon Sep 17 00:00:00 2001 From: Anton A S Date: Sat, 7 Mar 2026 19:49:24 +0300 Subject: [PATCH] Add SEO: OG tags, Twitter Card, hreflang, JSON-LD, sitemap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - config.ts: og:site_name, og:image (+w/h/alt), twitter:card/site/image - config.ts: transformPageData — per-page canonical, og:url, og:locale, og:locale:alternate, hreflang for all 6 locales + x-default, og:title/description and twitter:title/description from frontmatter - config.ts: sitemap with xhtml:link alternates for all 174 pages × 6 locales - SchemaOrg.vue: SoftwareApplication JSON-LD on EN homepage only - HomeLayout.vue: render SchemaOrg in home-features-after slot - index.md + de/fr/es/it/pl index.md: add translated title to frontmatter --- .../archive/2026-03-07-seo-meta-og/design.md | 166 ++++++++++++++++++ .../2026-03-07-seo-meta-og/proposal.md | 25 +++ .../specs/vitepress-docs/spec.md | 93 ++++++++++ .../archive/2026-03-07-seo-meta-og/tasks.md | 39 ++++ packages/docs/.vitepress/config.ts | 98 ++++++++++- packages/docs/.vitepress/theme/HomeLayout.vue | 4 + packages/docs/.vitepress/theme/SchemaOrg.vue | 39 ++++ packages/docs/de/index.md | 1 + packages/docs/es/index.md | 1 + packages/docs/fr/index.md | 1 + packages/docs/index.md | 1 + packages/docs/it/index.md | 1 + packages/docs/pl/index.md | 1 + 13 files changed, 463 insertions(+), 7 deletions(-) create mode 100644 openspec/changes/archive/2026-03-07-seo-meta-og/design.md create mode 100644 openspec/changes/archive/2026-03-07-seo-meta-og/proposal.md create mode 100644 openspec/changes/archive/2026-03-07-seo-meta-og/specs/vitepress-docs/spec.md create mode 100644 openspec/changes/archive/2026-03-07-seo-meta-og/tasks.md create mode 100644 packages/docs/.vitepress/theme/SchemaOrg.vue 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: