SEO and Sharing

Every web page is complete HTML when it leaves the server, so search engines and link previews see exactly what visitors see. web_render writes the title, description, canonical link, Open Graph tags, Twitter card and structured data from its pins, and leaves alone any tag your own markup already has.

Each pin becomes head tags

PinWritesGood to know
title<title> and og:titleThe template can print it as {{title}}.
description<meta name="description"> and og:descriptionThe template can print it as {{description}}.
imageog:imageA /path becomes an absolute URL on the page's own host. With an image, twitter:card becomes summary_large_image.
canonical<link rel="canonical"> and og:urlDefaults to the page's URL without its query. A /path becomes absolute.
metaAny tag, from an object of name to contentSee below.
json_ld<script type="application/ld+json">See below.

Some tags need no pin at all: og:type is website, twitter:card is summary, and og:locale and the hreflang links come from the page's language and its l10n URLs. See Languages.

The head tags read these pins, not the template's variables. A title field in the data object prints in your template, but it writes no <title>.

What a product page gets
<title>Green tea</title>
<meta name="description" content="Light and grassy.">
<link rel="canonical" href="https://shop.example.com/shop/green-tea">
<meta property="og:description" content="Light and grassy.">
<meta property="og:image" content="https://shop.example.com/images/green-tea.jpg">
<meta property="og:locale" content="en_US">
<meta property="og:title" content="Green tea">
<meta property="og:type" content="website">
<meta property="og:url" content="https://shop.example.com/shop/green-tea">
<meta name="twitter:card" content="summary_large_image">

The meta pin adds or overrides any tag

It takes an object of name to content, and writes one tag per key:

Value for the meta pin
{
  "robots": "noindex",
  "og:type": "product",
  "twitter:image": "https://shop.example.com/images/green-tea.jpg"
}
  • A key starting with og:, article: or product: is written as <meta property="...">. Any other as <meta name="...">.
  • A key that matches a generated tag replaces its value, such as og:type above.
  • The pins write twitter:card and no other Twitter tag. Add twitter:image here if you want one.
  • A tag with an empty value is left out.

Structured data goes on the json_ld pin

Wire an object into json_ld, and it lands in the head as JSON-LD. The data cannot close its own script element, because <, > and & in it are written as escapes. An empty object writes nothing.

Value for the json_ld pin
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Green tea",
  "description": "Light and grassy.",
  "offers": {"@type": "Offer", "price": "4.50", "priceCurrency": "EUR"}
}

You can also write it into your template, where the same safety applies: a value in a <script> is written as JSON, so it takes no quotes.

JSON-LD in a template
<script type="application/ld+json">
  {"@context": "https://schema.org", "@type": "Product", "name": {{product.name}}, "offers": {"@type": "Offer", "price": {{product.price}}}}
</script>

Status and headers are pins too

PinIs
statusThe HTTP status, 200 by default. Answer a missing product with a real 404 and its own template, so it does not get indexed as a page.
headersExtra response headers as [{"key": "...", "value": "..."}], or an object of name to value. One value per name.
cacheHow many seconds shared caches may keep the page. See Speed and Caching.

A header you set wins. That goes for Content-Type, Cache-Control, Referrer-Policy and X-Content-Type-Options, and for Content-Security-Policy: see Escaping and Security before you replace that one.

A flow can also skip web_render and answer with an httpconnection block. That is how a web_page with the event sitemap.xml can answer XML through httpconnection_set_xml.

A tag your layout writes is not written twice

The block looks at the part of the document before </head> and skips what it finds there. The layout can write its own title and still get everything else:

Own title, generated rest
<!doctype html>
<html lang="{{language}}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="{{viewport}}">
  <title>{{title}} | My Shop</title>
  {{{head}}}
</head>
<body>{{{body}}}</body>
</html>
  • It looks for <title, name="description", rel="canonical", hreflang, and each generated name in double quotes, such as "og:image". Write your tags with double quotes, or the check misses them and you get both.
  • An inline SVG's <title> in the body is not the page's title, and does not count.
  • {{{head}}} places the tags. Without it they go before </head>, and the document's three shapes all get them.

Next steps

Languages hreflang, og:locale and a URL per language. Speed and Caching ETags, cache seconds and prefetching. Layouts and Partials Where the head of a stored layout comes from.

Frequently asked

Does a RUAL web page get Open Graph tags and a canonical link?

Yes, from the pins of web_render: the title, the meta description, a canonical link that defaults to the page URL without its query, Open Graph and Twitter tags, hreflang links and JSON-LD from the json_ld pin. Anything else goes through the meta pin, and a tag your layout already writes is not written twice.