Docs
Dashboard

Concepts

SEO

One resolved object on every page-shaped thing, with the fallback chain already applied.

Anything in ZevContent that corresponds to a page carries a complete seo object: blog articles, blog categories, knowledge base articles, knowledge base collections, and authors.

"seo": {
  "title": "How we deploy",
  "description": "A short tour of the pipeline.",
  "og_image": {
    "url": "https://cdn.example.com/media/deploy.jpg",
    "alt": "A deployment pipeline",
    "width": 1200,
    "height": 630,
    "content_type": "image/jpeg"
  },
  "canonical_url": null,
  "noindex": false
}

It always arrives complete and resolved. Render what you are given.

The fallback chains

A writer fills in as much as they care to. The server applies the rest, in the same code path for every module:

title falls back todescription falls back toog_image falls back to
Blog articlethe article titlethe excerpt, then the opening of the bodythe featured image
Blog categorythe namethe descriptionthe featured image
KB articlethe titlethe summary, then the opening of the bodythe featured image
KB collectionthe titlethe descriptionthe collection image
Authorthe namethe opening of the biothe avatar

Descriptions derived from a body are cut on a word boundary, not mid-word.

canonical_url and noindex always pass through exactly as set. There is nothing sensible to derive them from, and guessing a canonical URL is how duplicate-content problems get created rather than solved.

Rendering it

<title>{seo.title}</title>
{seo.description && <meta name="description" content={seo.description} />}
{seo.canonical_url && <link rel="canonical" href={seo.canonical_url} />}
{seo.noindex && <meta name="robots" content="noindex" />}

<meta property="og:title" content={seo.title} />
{seo.description && <meta property="og:description" content={seo.description} />}
{seo.og_image && <meta property="og:image" content={seo.og_image.url} />}
{seo.og_image?.width && <meta property="og:image:width" content={seo.og_image.width} />}
{seo.og_image?.height && <meta property="og:image:height" content={seo.og_image.height} />}

og_image is the same media object as everywhere else, so on a private project it is a presigned URL that expires. Social crawlers fetch when someone shares a link, which may be months after your build. See Media for why a static build should ask for media_urls=permanent.

Where seo is not

Nested references do not carry it. An author inside an article’s authors array is a byline carrying id, name, slug, bio and social, and a category inside categories is a breadcrumb. Neither has seo or article_count, because a card printing twenty bylines will never render twenty og images.

Fetch the author’s or category’s own endpoint for the page shape.

Custom content types

seo is a feature of the built-in modules, not of custom types. Your own type gets the fields you define. If a case-study needs a meta description, add a string field for it and render it yourself.

We do not synthesise an SEO object for a shape we did not design, because we would have to guess which of your fields is the title.

Updated at, Wednesday, September 2, 2026