SEO & JSON-LD
Every field you need for on-page SEO and structured data ships inside the Article. There is nothing to compute and nothing to fetch separately — map the fields to meta tags and JSON-LD.
The meta fields
Section titled “The meta fields”| Field | Use it for | Notes |
|---|---|---|
seoTitle | <title> | Always present — falls back to title server-side. Use it directly. |
seoDescription | <meta name="description"> | string | null. When null, omit the tag and let the search engine derive one. |
canonicalUrl | <link rel="canonical"> | string | null. null only when the site has no URL set. |
inLanguage | <html lang> / JSON-LD inLanguage | BCP-47 tag, e.g. "en". |
formatType | JSON-LD @type hint | schema.org type, e.g. "Article". |
tags | keywords / topical metadata | May be empty. |
Because seoTitle is guaranteed and the two nullable fields are explicitly typed, you can emit clean head tags without guesswork:
const head = { title: article.seoTitle, description: article.seoDescription ?? undefined, // omit when null canonical: article.canonicalUrl ?? undefined, lang: article.inLanguage,};The hero image
Section titled “The hero image”When present, image is everything you need for an Open Graph card and a layout-shift-free render in one object.
interface ArticleImage { url: string; // og:image and your <img src> alt: string; // <img alt> width: number | null; // set <img width/height> from these height: number | null; srcset: string | null; // <img srcset> for responsive sources}- Use
image.urlforog:image(andtwitter:image). - Set
<img width>/<img height>fromimage.width/image.heightso the browser reserves space and you avoid cumulative layout shift. - Drop
image.srcsetstraight into<img srcset>when it’s set.
image is null when the article has no generated cover — fall back to a site-level default OG image, or omit the card.
author.kind selects the JSON-LD shape
Section titled “author.kind selects the JSON-LD shape”The author’s kind is the switch between a Person and an Organization in your structured data.
author.kind | JSON-LD author @type | When |
|---|---|---|
"real" | Person | A real human byline. sameAs carries their profile URLs. |
"persona" | Organization | A brand byline rather than an individual. |
function authorLd(author: Article["author"]) { if (author.kind === "real") { return { "@type": "Person", name: author.name, jobTitle: author.jobTitle ?? undefined, image: author.avatarUrl ?? undefined, description: author.bio ?? undefined, sameAs: author.sameAs.length ? author.sameAs : undefined, }; } return { "@type": "Organization", name: author.name, sameAs: author.sameAs.length ? author.sameAs : undefined, };}A complete Article JSON-LD node then assembles from the same fields:
const articleLd = { "@context": "https://schema.org", "@type": article.formatType, // e.g. "Article" headline: article.title, inLanguage: article.inLanguage, datePublished: article.publishedAt, dateModified: article.updatedAt, description: article.seoDescription ?? undefined, image: article.image?.url, mainEntityOfPage: article.canonicalUrl ?? undefined, author: authorLd(article.author),};What to reach for next
Section titled “What to reach for next” The article shape The full field-by-field reference behind these tags.
The content feed Where the SEO fields come from.
Instant updates Keep canonical and metadata fresh as articles change.