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"]

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

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 (한국어)").

<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

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