When someone shares a docs link, Slack and other services fetch the page's HTML and look for a title, description, and image. Shoalrun writes these tags at build time from page metadata and can generate a share image for every page. Start with a clear page title and a one-sentence summary; you do not need to hand-write Open Graph or Twitter tags.
Set the public address
Put the address readers actually use in site.origin. This is where crawlers will fetch the page and its image:
site:
title: Acme API docs
origin: https://docs.acme.com
Check the domain in the built og:image URL. A page may display in a browser while its social preview cannot load an image from that domain. Preview deployments behind sign-in cannot be unfurled by a public crawler. Publish on Vercel explains the production and preview origins for this site's build.
Write the page title and summary
title: Getting started
summary: Create your first project and make a request.
The title becomes the page heading and browser title. The summary is the default preview description and appears on the generated share image. For short titles, Shoalrun adds site.title to the Open Graph and Twitter titles when the result fits within 70 characters. The browser title and page heading remain unchanged.
If a page needs a different browser and social title, or a different description, use seo.title and seo.description in its frontmatter:
seo:
title: Getting started with Acme API
description: Create a project, choose a credential, and make your first request.
seo.title sets the exact browser and social title; it skips the automatic site-name suffix. seo.description sets the browser and social description unless the page selects a separate displayText.socialCard. The generated image uses the same selected description, so its copy agrees with the tags. You can configure the text choices site-wide under outputs.text; see text surfaces for precedence.
What the build adds
The build derives these metadata and discovery outputs from your site and page settings.
| Output | Default source |
|---|---|
HTML <title> |
seo.title, then page title |
HTML name="description" |
seo.description, then configured HTML description |
og:title and twitter:title |
Browser title, with site.title added for short titles when it fits |
og:description and twitter:description |
Selected social description, falling back to the page summary |
og:url |
site.origin plus the published page route |
| Canonical URL | site.origin plus the published page route |
XML sitemap lastmod |
Page updated, then optional site.updated |
og:image and twitter:image |
Absolute URL of that page's share image |
twitter:card |
summary_large_image when a share image exists |
The default image is a 1200×630 PNG with the page type, title, summary, site name, and URL. Share images and favicon shows how to replace a page image or the site-wide design. Turning off share images removes image tags on pages without their own image.
Check the deployed page
Build and deploy, then run the deployment checker with the public origin:
npm --prefix framework run deploy:check -- https://docs.acme.com/
It checks page responses, compares og:url and og:image with that origin, and fetches a share image. To inspect one page directly, view its HTML source and open the absolute og:image URL in a private browser window. On Vercel, the deployment's Open Graph tab can show a preview for Slack and other services.
When site.origin is set, the build writes /sitemap.xml from public, indexable page routes and advertises it in /robots.txt. Every page gets a self-referencing canonical URL. A page with seo.noindex: true stays public, gets a noindex tag, and is omitted from the sitemap. Set site.indexing: false for a preview: every page gets noindex and the build omits the sitemap. Crawlers can still reach those pages to read noindex.
Set updated in a page's frontmatter to the date of a meaningful content update, such as 2026-09-27. A timezone-qualified ISO timestamp is also accepted. Shoalrun copies it to sitemap lastmod; it never substitutes build time. site.updated is an optional date fallback for generated pages and other pages without updated. Change it only when the material it covers changes. Dates may repeat across pages when they really changed together. Without site.freshness, the build uses declared dates. It never reads file modification times to determine content updates.
Derive revision dates from content changes
Opt into a content-digest history:
site:
freshness:
history: ./config/content-updates.json
The history file must be inside the project and its parent directory must already exist. The first successful build seeds each page with its declared updated, then site.updated, then today's UTC date. Later builds compare the resolved HTML and Markdown content plus displayed page metadata. Unchanged content keeps its recorded Updated date; changed content receives today's UTC date. File touches, AST source positions, and changes to verification dates do not change the content digest. The history is written after a successful build. Commit it with the source so clean CI builds use the same history. Without that file, the next build seeds a new history from declared dates.
The manifest records contentDigest for this comparison and digest for the complete generated Markdown. A Markdown date or metadata edit changes the latter; it does not necessarily change the technical content fingerprint.
Record verification separately
Set native frontmatter verified only when someone checks the page's technical claims:
verified: 2026-10-01
The HTML page, Markdown twin, and manifest expose this date independently of Updated. A build never assigns it. This project also declares private editorial.lastVerified and editorial.verificationNote fields for internal review notes; use native verified when the date should be public. A rebuild or unchanged-content file touch establishes neither a content change nor verification.
Search indexing still depends on the public host and crawler access; metadata alone does not guarantee it. If Slack does not unfurl after a corrected deployment, confirm the page and image are public, then try sharing the link in a new conversation. Slack may omit a preview when the same link was recently posted in that conversation.
What agents receive
The social tags are in HTML, and each generated share card is a public PNG. Agents reading Markdown get the authored title, body, and fields published by your Markdown metadata settings. The Markdown body does not contain HTML meta tags. documents.json and search-index.json list the published page and its routes. Changing seo.title or the share image does not replace a clear page title or summary for agents that use the Markdown and search index. The agent-friendly documentation guide explains how these public files differ from the documentation bundle shipped inside Shoalrun's CLI.
Related reading
No matching documents.