Skip to content

Rendering HTML

A payload can render itself to HTML. This is the fastest way to get SEO tags into a <head> block, and it is safe by construction: user data is escaped, and the JSON-LD script is escaped so a value cannot break out of the tag.

Full head block

payload = build_seo_payload(entity, "/blog/post", config)
print(payload.render_html())
const payload = buildSeoPayload(entity, "/blog/post", config);
console.log(payload.renderHtml());

Output:

<title>Introducing easeo - Example</title>
<meta name="description" content="Deterministic SEO payloads for content platforms.">
<link rel="canonical" href="https://example.com/blog/introducing-easeo">
<meta name="robots" content="index,follow">
<meta property="og:type" content="article">
<meta property="og:title" content="Introducing easeo - Example">
...
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  ...
}
</script>

Escape before injecting

render_html() returns an HTML string. In a template engine, mark it as safe (Jinja2 |safe, Django mark_safe, Svelte {@html}) only because the payload has already escaped the values it renders. Do not hand-interpolate raw entity fields into your own <head>.

Granular rendering

When you need to place sections separately, render them individually.

Python JavaScript Output
render_html() renderHtml() full <head> block
render_opengraph() renderOpengraph() og:* tags only
render_twitter() renderTwitter() twitter:* tags only
render_jsonld() renderJsonld() JSON-LD <script> only
head = (
    payload.render_opengraph()
    + payload.render_twitter()
    + payload.render_jsonld()
)
const head =
  payload.renderOpengraph() +
  payload.renderTwitter() +
  payload.renderJsonld();

Using it in a template

<head>
  {{ payload.render_html() | safe }}
</head>
{% load easeo_tags %}
<head>
  {% easeo_head entity request.path %}
</head>
import { EaseoHead } from "@easeo/react";

<EaseoHead entity={entity} route={route} config={config} />
<script>
  import { buildEaseoPayload, EaseoHead } from "@easeo/sveltekit";
  const seo = buildEaseoPayload(entity, route, config);
</script>

<EaseoHead {seo} />

Why rendering lives in the core

The HTML is generated by the Rust core, not by a template. That means the same payload produces the same markup in Python and JavaScript, and the escaping rules are identical everywhere. See Payload Model for the exact tag order.

Recap

  • render_html() produces a complete, escaped <head> block.
  • Granular renderers are available for custom layouts.
  • Framework adapters call these for you.

Next: Contracts.