SEO meta (articleHead)
articleHead(article, opts?) turns an Article into a useHead() input — the <title>, meta description, Open Graph + Twitter cards, the canonical <link>, and schema.org Article JSON-LD — all driven by the feed’s guarantees. It’s a pure function: it returns plain head data and you call useHead yourself, so you stay in control of when and how the head is set. See SEO & JSON-LD for the cross-SDK model.
It lives at the core entry:
import { articleHead } from "@ghostwritr/vue";In Nuxt, useHead is built in (auto-imported). In plain Vue (Vite SSR/SPA), install @unhead/vue and import useHead from it. The articleHead call is identical either way.
<script setup lang="ts">import { fetchArticle, articleHead } from "@ghostwritr/vue";
const route = useRoute();const { data: article } = await useAsyncData(`gw-${route.params.slug}`, () => fetchArticle({ siteId: useRuntimeConfig().public.ghostwritrSiteId, slug: String(route.params.slug), }),);
if (!article.value) throw createError({ statusCode: 404, statusMessage: "Not found" });
// <title>, description, Open Graph + Twitter, canonical, and schema.org Article JSON-LD.useHead(articleHead(article.value, { siteName: "Acme" }));</script>
<template> <article v-if="article"> <h1>{{ article.title }}</h1> </article></template>import { fetchArticle, articleHead } from "@ghostwritr/vue";import { useHead } from "@unhead/vue";
const article = await fetchArticle({ siteId, slug });if (article) { useHead(articleHead(article, { siteName: "Acme", url: pageUrl }));}useHead comes from @unhead/vue here, not from the framework. Wire the head plugin once at app creation (createHead() → app.use(head)) per the @unhead/vue setup.
Options
Section titled “Options”ArticleHeadOptions is optional — every field within it is too.
| Option | Type | Effect |
|---|---|---|
url | string | Absolute URL of this page. Sets <link rel="canonical"> and og:url. Falls back to article.canonicalUrl. Pass your own when self-hosting under a different domain. |
siteName | string | Sets og:site_name and becomes the JSON-LD publisher (an Organization). |
twitterSite | string | Sets twitter:site (your default @handle). |
What it emits
Section titled “What it emits”From a typical article, articleHead returns an ArticleHead with title, a meta array, an optional canonical link, and a JSON-LD script:
title=article.seoTitle— the<title>.seoTitlealways falls back totitleupstream, so it’s never empty.{ name: "description", content: article.seoDescription }— only whenseoDescriptionis set.og:type="article",og:title=seoTitle, andog:description(when present).og:url(when a canonical resolves),og:site_name(whensiteNameis given),og:image(when the article has animage).article:published_time=publishedAt,article:modified_time=updatedAt, and onearticle:tagper entry intags.twitter:card="summary_large_image"when the article has an image, otherwise"summary"; plustwitter:sitewhentwitterSiteis given.link=[{ rel: "canonical", href }]— only when a canonical resolves.script=[{ type: "application/ld+json", innerHTML }]— the schema.orgArticleJSON-LD (below).
The JSON-LD
Section titled “The JSON-LD”The injected Article JSON-LD carries headline (= article.title), description (when set), url + mainEntityOfPage (when a canonical resolves), image (when present), datePublished, dateModified, inLanguage, the author, and publisher (when siteName is given). The innerHTML is pre-serialized with < escaped, so a stray </script> inside a field can’t break out of the element.
The author shape follows author.kind:
"real"→ aPersonwithname, plusjobTitleandsameAswhen present."persona"→ anOrganizationbyline (justname). Ghost Writr never fabricates a human for a brand voice — see SEO & JSON-LD.