Skip to content

vitepress-plugin-meta

Автоматически выставляет per-page head-теги для VitePress: description, Open Graph, Twitter Card, JSON-LD Article, canonical, robots, keywords, author, article:published_time / article:modified_time. По желанию рендерит per-page OG-картинку из SVG-шаблона через sharp.

Плагин закрывает пласт SEO/GEO, который в базовой теме VitePress приходится расписывать вручную в head каждой страницы. Метаданные резолвятся из frontmatter, если есть, иначе из первого абзаца исходника; уже проставленные теги не перетираются.

Установка

bash
pnpm add @ampernic/vitepress-plugin-meta

Опционально sharp -- нужен только если пользуетесь генерацией OG-картинок:

bash
pnpm add -D sharp

Пакет sharp объявлен peer'ом с флагом optional. Если он не установлен, плагин пишет OG-картинку как SVG (браузеры и краулеры её поддерживают).

Скоуп @ampernic из Forgejo цепляется через .npmrc:

@ampernic:registry=https://altlinux.space/api/packages/ampernic/npm/

Использование

Плагин возвращает transformPageData и buildEnd, которые кладутся в корень defineConfig:

ts
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
import { MetaPlugin, productTemplate } from '@ampernic/vitepress-plugin-meta'

const meta = MetaPlugin({
  hostname: 'https://docs.altlinux.space',
  siteName: 'ALT Workstation',
  locale: 'ru_RU',
  twitter: { site: '@altlinux' },
  ogImage: {
    generate: {
      template: productTemplate,
      output: 'og/{slug}.png',
      width: 1200,
      height: 630,
    },
  },
})

export default defineConfig({
  transformPageData: meta.transformPageData,
  buildEnd: meta.buildEnd,
})

При использовании @ampernic/vitepress-theme-alt-docs те же опции пробрасываются полем meta внутри createSharedConfig -- плагин уже примонтирован в тему.

Уже прописанные в frontmatter head-теги не перетираются: страница, объявившая свой og:image, оставляет его.

Опции

ОпцияТипПо умолчаниюОписание
hostnamestring--Абсолютный origin для canonical и og:url. Без него оба тега не пишутся
siteNamestring'Documentation'og:site_name и фолбэк описания
localestring'ru_RU'og:locale
fallbackDescriptionstringДокументация ${siteName}Заглушка, если у страницы нет ни frontmatter-описания, ни первого абзаца
emitEmitTogglesвсе trueФлаги семейств: description, keywords, author, canonical, openGraph, twitter, jsonLd, robots, articleTime
resolveTitle(ctx) => string--Хук над резолвом заголовка
resolveDescription(ctx) => string--Хук над резолвом описания
resolveKeywords(ctx) => string[] | undefined--Ключевые слова
resolveAuthor(ctx) => string | undefined--Автор
resolveImage(ctx) => string | undefined--Явный путь og:image
resolveRobots(ctx) => string | undefined--Значение robots -- перекрывает robots.default
resolveOgImageData(ctx) => Partial<...>--Данные для генератора картинки: tagline, theme, logo, logoSecondary
extraHead(ctx) => HeadConfig[]--Кастомные теги, добавляемые в конец
ogImage.defaultstring--Сайт-уровневая fallback-картинка
ogImage.defaultWidth / defaultHeightnumber--Размеры fallback-картинки
ogImage.generate.template(data)(data: OgImageData) => string--Возвращает SVG-строку, которая рендерится в PNG через sharp. Без sharp пишется тот же SVG
ogImage.generate.outputstring'og/{slug}.png'Шаблон пути OG-картинки в outDir
ogImage.generate.width / heightnumber1200 / 630Размеры сгенерированной картинки
ogImage.generate.filter(ctx) => booleanвсегда trueРазрешает пропустить страницы (например, 404)
ogImage.generate.fontDirsstring[][]Каталоги со шрифтами для рендера. См. ниже
robots.defaultstring'index,follow'Дефолтная строка robots
twitter.site / creatorstring--twitter:site / twitter:creator
pwaPwaOptions--Иконки/manifest для PWA, автогенерация из одного исходного SVG

Встроенные шаблоны

productTemplate и minimalTemplate импортируются из корня пакета. Оба читают data.theme (bg, bgAccent, fg, muted, accent, fontFamily), так что тема-потребитель ребрендит без форка шаблона:

ts
import { productTemplate } from '@ampernic/vitepress-plugin-meta'

MetaPlugin({
  ogImage: {
    generate: {
      template: productTemplate,
      output: 'og/{slug}.png',
      filter: (ctx) => !ctx.pageData.relativePath.startsWith('404'),
    },
  },
  resolveOgImageData: () => ({
    theme: {
      bg: '#0d1117',
      accent: '#fa814c',
      fontFamily: 'Inter, sans-serif',
    },
  }),
})

Порядок работы

  1. transformPageData читает исходник страницы (учитывая vitepress rewrites), резолвит поля через resolveAll, собирает head через headBuilder, дописывает его во frontmatter страницы.
  2. Существующие head-записи с тем же name / property / rel сохраняются -- side-by-side frontmatter выигрывает у автогенерации.
  3. Для каждой страницы, у которой сработал filter OG-генератора, накапливается запись; при buildEnd она превращается в PNG (через sharp) или SVG (без него).
  4. Если задан pwa.generate, тот же buildEnd генерирует иконки и site.webmanifest в outDir.

Нюансы

  • hostname пропускать нельзя, если сайт хочет валидный og:url и canonical.
  • articleTime берётся из git-метаданных исходника; отключите флагом emit.articleTime, если репозиторий без истории коммитов.
  • OG-картинка не перерисовывается, если resolveImage вернул готовый URL: он ставится напрямую как og:image.
  • Для канонических URL с локалью учитывайте vitepress.rewrites: ru/index.md рендерится как /, и плагин уже применяет прямой rewrite.

Шрифты в картинке

sharp резолвит гарнитуры через системный fontconfig и игнорирует@font-face внутри SVG. Поэтому шрифт, которого нет в системе сборки, молча подменяется на первый подходящий - и картинка уезжает от фирменного стиля.

fontDirs указывает каталоги, которые нужно добавить к поиску:

ts
ogImage: {
  generate: {
    template: myTemplate,
    fontDirs: ['/path/to/fonts'],
  },
}

Плагин собирает временный конфиг, включающий системный, так что обычные запасные гарнитуры продолжают работать. Каталоги подключаются один раз за процесс, до первой отрисовки текста.

Кладите статические начертания в TrueType. Вариативный шрифт fontconfig индексирует под именем инстанса по умолчанию (Montserrat Thin), и запрос базового семейства его не находит.