SEO meta (articleMeta)
articleMeta(article, opts?) turns an Article into the MetaDescriptor[] that a React Router route’s meta export returns. One call emits the <title>, meta description, Open Graph + Twitter cards, the canonical <link>, and schema.org Article JSON-LD — all driven by the feed’s guarantees. See SEO & JSON-LD for the why.
Return it from the article route’s meta export. When the loader threw a 404, data is undefined, so guard and return []:
import { fetchArticle, articleMeta } from "@ghostwritr/react-router";import type { Route } from "./+types/blog.$slug";
export async function loader({ params }: Route.LoaderArgs) { const article = await fetchArticle({ siteId: process.env.GHOSTWRITR_SITE_ID!, slug: params.slug!, }); if (!article) throw new Response("Not found", { status: 404 }); return { article };}
export const meta: Route.MetaFunction = ({ data }) => data ? articleMeta(data.article, { siteName: "Your Brand" }) : [];Options
Section titled “Options”ArticleMetaOptions 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, articleMeta produces these descriptors:
{ 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.{ tagName: "link", rel: "canonical", href }— when a canonical resolves.{ "script:ld+json": ... }— the schema.orgArticleJSON-LD (below).
The JSON-LD
Section titled “The JSON-LD”The injected Article JSON-LD carries headline, description (when set), url + mainEntityOfPage (when a canonical resolves), image (when present), datePublished, dateModified, inLanguage, the author, and publisher (when siteName is given).
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.