Maptain
A Rails gem that writes your XML sitemaps from your records: one small class per kind of page, the hreflang links of every language, files on disk or S3, IndexNow for Bing. After a few changes, a build runs hundreds of times faster than regenerating everything.
gem "maptain"
How it works
1. Describe a kind of page
One small class per kind of page: its records, and the URL of a record in a language. Its blocks have your route helpers.
app/sitemaps/product_sitemap.rb
class ProductSitemap < Maptain::Source
scope { Product.published }
url { |product, locale| product_url(product, locale:) }
end
2. Build
maptain:check shows what looks wrong before anything is written. maptain:build writes public/sitemap.xml and the files it lists.
bin/rails maptain:check
bin/rails maptain:build
Every language of a page lists all the others and itself. Search engines ignore hreflang links that the page they point to does not return: written from the record, they cannot be wrong.
What comes out, for a site in English and French
<url>
<loc>https://example.com/fr/products/42</loc>
<lastmod>2026-11-03T09:12:44Z</lastmod>
<xhtml:link rel="alternate" hreflang="en"
href="https://example.com/en/products/42"/>
<xhtml:link rel="alternate" hreflang="fr"
href="https://example.com/fr/products/42"/>
<xhtml:link rel="alternate" hreflang="x-default"
href="https://example.com/en/products/42"/>
</url>
3. Run it every hour
The first build writes everything. The next ones ask the database what moved, and rewrite only those files: most of the time, nothing.
# config/recurring.yml
production:
sitemap:
command: "Maptain.build"
schedule: every hour
The guide covers the rest: records in some languages only, dates from several columns, files on S3, IndexNow.
How long it takes
The same sitemap built by Maptain and by sitemap_generator, on the same machine: 30,000 records in 24 languages, 720,000 URLs, each with the 25 hreflang links of its page. Seconds depend on the machine; how many times faster holds from one to the next.
What you get
Languages built in
One <url> per language, each with the hreflang links of all of them and x-default. A record that exists in three languages out of twenty-four lists those three.
Files that stay put
Each file holds a fixed range of ids, so a record is in the same file at every build, and crawlers only fetch again the files that changed.
No callback in your models
A build asks the database for the count and the latest change of each range. update_all, imports and deletions are seen like any other change.
Dates that are true
lastmod is the only hint search engines still read: the date the content changed, in the files and in the index. priority and changefreq are not written.
IndexNow
Each build tells Bing, Yandex, Seznam and Naver which URLs changed, appeared or went away, and sends again what they refused.
Any bucket Active Storage reaches
Files go to public/, or through a service of config/storage.yml to S3, Google Cloud Storage or Azure, with the credentials the app already has.
Is it for you?
Maptain helps most on a site with records in a database, translated, changing a little every day: a shop, a catalogue, a blog in several languages. A site of a few hundred pages in one language does not need it; sitemap_generator or a view that lists them will do.
It is young (0.1), it needs Ruby 3.3 and Rails 8.0 or newer, and its only runtime dependency is Active Support. It does not write video or news sitemaps yet, and a model needs an integer primary key. Coming from sitemap_generator, the guide maps one to the other.
Where it comes from
Maptain was written for a site of about 30,000 pages in 24 languages, 720,000 URLs. At that size, a script that lists every URL again at each run spends nearly all its time on pages that did not change, and the links between languages are easy to get wrong by hand.
Sitemaps changed in the meantime. Google stopped reading priority and changefreq, the ping endpoints closed, IndexNow appeared, and an honest lastmod became the one signal worth sending. Maptain writes what search engines read today, and nothing else.
The name is a sitemap with a captain's cap on: it keeps the files in order, and leaves alone the ones that did not change.