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
/rss.xml— Site-wide feed/blog/rss.xml— Section feed (if enabled)
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 (한국어)").
Template Links
<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
- LLMs.txt — AI/LLM crawler instructions
- Multilingual — Hreflang and canonical tags for i18n
- Configuration — Full config reference