Examples

SEO

Hwaro includes built-in SEO features: sitemaps, RSS feeds, robots.txt, and social sharing meta tags.

Sitemap

Automatically generates sitemap.xml for search engines.

Configuration

[sitemap]
enabled = true
filename = "sitemap.xml"
changefreq = "weekly"
priority = 0.5
exclude = ["/private", "/drafts"]
Key Type Default Description
enabled bool false Generate sitemap.xml
filename string "sitemap.xml" Output filename
changefreq string "weekly" Default change frequency for all pages
priority float 0.5 Default priority for all pages (0.0 to 1.0)
exclude array [] Path prefixes to exclude (e.g., ["/private"] excludes /private, /private/page.html)

Output

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://example.com/</loc>
    <lastmod>2024-01-15</lastmod>
  </url>
  <url>
    <loc>https://example.com/about/</loc>
  </url>
</urlset>

Excluding Pages

Set in_sitemap = false in front matter:

+++
title = "Private Page"
in_sitemap = false
+++

RSS Feeds

Generate RSS feeds for your site and sections.

Configuration

[feeds]
enabled = true
type = "rss"              # "rss" or "atom"
limit = 20                # Maximum number of items
truncate = 0              # Truncate to N characters (0 = no truncation)
full_content = true       # true = full HTML body, false = description/summary only
filename = ""             # Leave empty for default (rss.xml or atom.xml)
sections = []             # Limit to specific sections, e.g., ["posts"]
Option Default Description
enabled false Enable feed generation
type "rss" Feed format: "rss" or "atom"
limit 10 Maximum number of items in feed
truncate 0 Truncate content to N characters (0 = full content)
full_content true true = full HTML in feed, false = use front matter description or auto-generated summary
filename "" Custom filename (empty = rss.xml or atom.xml)
sections [] Limit feed to specific sections
default_language_only true Multilingual: main feed includes default language only

Section Feeds

Enable per-section feeds:

+++
title = "Blog"
generate_feeds = true
+++

Generates /blog/rss.xml.

Output

Multilingual Feeds

When the site is multilingual, feeds are generated per language automatically:

Language Feed Path Contents
Default (e.g., en) /rss.xml Default language pages only (configurable)
Non-default (e.g., ko) /ko/rss.xml Only Korean pages
Non-default (e.g., ja) /ja/rss.xml Only Japanese pages

By default, the main site feed includes only default language pages (default_language_only = true). Set default_language_only = false to include all languages in the main feed. Each non-default language with generate_feed = true gets its own separate feed regardless of this setting.

[feeds]
enabled = true
default_language_only = true   # true (default): main feed = default language only
                               # false: main feed includes all languages

Per-language feed control:

[languages.ko]
language_name = "한국어"
generate_feed = true    # Generates /ko/rss.xml (default: true)

[languages.ja]
language_name = "日本語"
generate_feed = false   # No /ja/rss.xml will be generated

Language feeds share the same sections, limit, truncate, and full_content settings from [feeds] config. RSS language feeds include a <language> tag, and Atom feeds include an xml:lang attribute. The feed title includes the language name (e.g., "My Site (한국어)").

<link rel="alternate" type="application/rss+xml" 
      href="{{ base_url }}/rss.xml" 
      title="{{ site.title }}">

{% if page.language and page.language != "en" %}
<link rel="alternate" type="application/rss+xml"
      href="{{ base_url }}/{{ page.language }}/rss.xml"
      title="{{ site.title }} ({{ page.language }})">
{% endif %}

Robots.txt

Control search engine crawling.

Configuration

[robots]
enabled = true

With custom rules:

[robots]
enabled = true
rules = [
  { user_agent = "*", disallow = ["/admin", "/private"] },
  { user_agent = "GPTBot", disallow = ["/"] }
]
Key Type Default Description
enabled bool true Generate robots.txt
filename string "robots.txt" Output filename
rules array [] List of user-agent rules with allow and disallow paths

When no rules are configured, Hwaro generates a default allow-all rule. If a rule has both allow and disallow empty, an explicit Allow: / is added to prevent ambiguous behavior.

Output

User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml

LLMs.txt

Generate instruction files for AI/LLM crawlers following the llms.txt standard.

[llms]
enabled = true
instructions = "This site's content is provided under the MIT license."
full_enabled = true

See LLMs.txt for full configuration and output details.


OpenGraph Tags

Social sharing meta tags for Facebook, LinkedIn, etc.

Configuration

[og]
default_image = "/images/og-default.png"
type = "website"
fb_app_id = "your_fb_app_id"
Key Description
default_image Fallback image when page has none
type OpenGraph type (website, article)
fb_app_id Facebook App ID (optional)

Page-Level Override

+++
title = "My Article"
description = "Article description"
image = "/images/article-cover.png"
+++

Template Usage

<head>
  {{ og_tags | safe }}
</head>

Output

<meta property="og:title" content="My Article">
<meta property="og:type" content="article">
<meta property="og:url" content="https://example.com/my-article/">
<meta property="og:description" content="Article description">
<meta property="og:image" content="https://example.com/images/article-cover.png">

Twitter Cards

Twitter-specific sharing tags.

Configuration

[og]
twitter_card = "summary_large_image"
twitter_site = "@yourusername"
twitter_creator = "@authorusername"
Key Description
twitter_card Card type: summary, summary_large_image
twitter_site Site's Twitter handle
twitter_creator Author's Twitter handle

Template Usage

<head>
  {{ twitter_tags | safe }}
</head>

Or include both OG and Twitter:

<head>
  {{ og_all_tags | safe }}
</head>

Output

<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="My Article">
<meta name="twitter:description" content="Article description">
<meta name="twitter:image" content="https://example.com/images/article-cover.png">
<meta name="twitter:site" content="@yourusername">

JSON-LD Structured Data

Hwaro automatically generates Article and BreadcrumbList JSON-LD for every page.

<head>
  {{ jsonld | safe }}
</head>

Additional schema types (FAQ, HowTo, WebSite, Organization) are also available. See Structured Data for all types, configuration, and output examples.


Template Variables

Pre-rendered HTML

These variables output ready-to-use HTML tags:

Variable Description
og_tags OpenGraph meta tags
twitter_tags Twitter Card meta tags
og_all_tags Both OG and Twitter tags
canonical_tag Canonical link tag
hreflang_tags Hreflang alternate link tags
jsonld Article + BreadcrumbList JSON-LD
jsonld_article Article JSON-LD only
jsonld_breadcrumb BreadcrumbList JSON-LD only
page_description Page description (fallback: site)
page_image Page image (fallback: og.default_image)

Structured SEO Object

The seo object provides individual field access for building custom meta tags:

Property Type Description
seo.canonical_url String Full canonical URL
seo.og_type String OpenGraph type (default: "article")
seo.og_image String Resolved absolute image URL
seo.twitter_card String Twitter card type
seo.twitter_site String Twitter site handle
seo.twitter_creator String Twitter creator handle
seo.fb_app_id String Facebook App ID
seo.hreflang Array Language translation links
<head>
  <link rel="canonical" href="{{ seo.canonical_url }}">
  <meta property="og:title" content="{{ page.title }}">
  <meta property="og:type" content="{{ seo.og_type }}">
  <meta property="og:url" content="{{ seo.canonical_url }}">
  {% if page.description %}
  <meta property="og:description" content="{{ page.description }}">
  {% endif %}
  {% if seo.og_image %}
  <meta property="og:image" content="{{ seo.og_image }}">
  {% endif %}
  {% if seo.fb_app_id %}
  <meta property="fb:app_id" content="{{ seo.fb_app_id }}">
  {% endif %}
  <meta name="twitter:card" content="{{ seo.twitter_card }}">
  <meta name="twitter:title" content="{{ page.title }}">
  {% if seo.twitter_site %}
  <meta name="twitter:site" content="{{ seo.twitter_site }}">
  {% endif %}
</head>

Complete Example

config.toml

title = "My Site"
description = "A great site"
base_url = "https://example.com"

[sitemap]
enabled = true

[feeds]
enabled = true
limit = 20

[robots]
enabled = true

[og]
default_image = "/images/og-default.png"
type = "website"
twitter_card = "summary_large_image"
twitter_site = "@mysite"

templates/base.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>{{ page.title }} - {{ site.title }}</title>
  <meta name="description" content="{{ page.description | default(value=site.description) }}">
  {{ og_all_tags | safe }}
  {{ canonical_tag | safe }}
  {{ hreflang_tags | safe }}
  {{ jsonld | safe }}
  <link rel="alternate" type="application/rss+xml" href="{{ base_url }}/rss.xml">
</head>
<body>
  {% block content %}{% endblock %}
</body>
</html>

See Also