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
exclude = ["/private", "/drafts"]
enabled: Generatesitemap.xml.exclude: List of 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
limit = 20
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, and truncate 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
Output
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
LLMs.txt
Generate instruction files for AI/LLM crawlers following the llms.txt standard.
Configuration
[llms]
enabled = true
filename = "llms.txt"
instructions = "This site's content is provided under the MIT license."
full_enabled = true
full_filename = "llms-full.txt"
| Key | Type | Default | Description |
|---|---|---|---|
| enabled | bool | false | Generate `llms.txt` |
| filename | string | "llms.txt" | Output filename |
| instructions | string | "" | Instructions for LLM crawlers |
| full_enabled | bool | false | Generate full content version |
| full_filename | string | "llms-full.txt" | Full version filename |
Output
/llms.txt— Instructions text only/llms-full.txt— Full site content with metadata (title, URL, source path per page)
The full version includes all rendered pages sorted by URL, separated by --- delimiters.
See LLMs.txt for detailed documentation.
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">
Template Variables
| Variable | Description |
|---|---|
| og_tags | OpenGraph meta tags |
| twitter_tags | Twitter Card meta tags |
| og_all_tags | Both OG and Twitter tags |
| page_description | Page description (fallback: site) |
| page_image | Page image (fallback: og.default_image) |
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 }}
<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