Rendering a <urlset> or a <sitemapindex>: the parts no host varies.
GamendWeb.Sitemap.Source says which pages exist and GamendWeb.Sitemap.Lastmod
says when they changed; this turns the answers into bytes. Which pages, and
where their dates come from, stay the host's — this owns the XML, the
hreflang alternates and the locale URL shape, which are the sitemaps protocol
rather than anybody's content.
Both hosts had a byte-identical copy of every function here. They were not even a fork: the second was pasted from the first and then both were edited.
Iodata throughout
A large sitemap is ~5,000 URLs and ~19 MB, 90% of it the alternate set every
URL carries. Nothing is joined into a binary on the way out — alternate_links/2
is built once per page and shared by that page's 30 locale entries as a
pointer rather than a 3 KB copy each time, and Plug.Conn.send_resp/3 takes
the nested list directly.
What is not here
No <changefreq> and no <priority>: Google ignores both outright and Bing
effectively does, so they were bytes advertising nothing.
Summary
Types
A page to render. :loc is a path, not a URL — the host names the route and
this prefixes the endpoint.
Functions
One xhtml:link alternate.
The xhtml:link alternate set for one path, as shared iodata.
The endpoint's URL with any trailing slash removed, so <> path is well-formed.
Everything a URL entry needs that is the same for every URL in the file.
One <url> entry per page, or one per locale when the page is translated.
A <lastmod> line, or nothing at all when the date is unknown.
The URL a path has in one locale.
Every locale a translated page is advertised in, default first.
A page's own date: a literal one, the manifest's, or none.
A <sitemapindex> over {url, lastmod} children.
One <url> element.
Wraps rendered <url> entries in a <urlset>.
Types
@type page() :: %{ :loc => String.t(), optional(:lastmod) => String.t() | nil, optional(:lastmod_key) => String.t() }
A page to render. :loc is a path, not a URL — the host names the route and
this prefixes the endpoint.
Its date comes from :lastmod when the page carries a literal one (a blog
post's front matter), from :lastmod_key when the manifest tracks it, and is
absent otherwise. Absent is a real answer, not a gap to fill: a date derived
from a file mtime is a lie that costs the whole sitemap its credibility.
Functions
One xhtml:link alternate.
The xhtml:link alternate set for one path, as shared iodata.
Build it once per page, not once per entry: this is the 90%. x-default
points at the clean URL, which is what tells a crawler which one to serve
when it has no better signal.
@spec base_url() :: String.t()
The endpoint's URL with any trailing slash removed, so <> path is well-formed.
Everything a URL entry needs that is the same for every URL in the file.
Read once per sitemap rather than per entry: Source.manifest/0 stats the
file on every call and the locale list is rebuilt each time — times 5,000
URLs × 30 locales, that was the bulk of rendering a large one.
Options:
:manifest— a manifest already loaded, when the caller needs it too:locale_lastmod—(own_lastmod, locale -> lastmod), for a host whose translated pages change on their own schedule. The default returns the page's own date for every locale.:extra— merged into the returned map, for whatever else the host's entry building needs.
One <url> entry per page, or one per locale when the page is translated.
Whether a path is translated is LocalePath.localized_path?/1 — the same
answer the router gives, so a page cannot be advertised under a prefix that
does not serve it.
A <lastmod> line, or nothing at all when the date is unknown.
The URL a path has in one locale.
The default locale lives at the clean URL and every other is prefixed — the
root is the one path that takes no suffix, or it would render /fr/.
@spec locales() :: [String.t()]
Every locale a translated page is advertised in, default first.
Order matters: the default locale owns the clean URL, so it leads and the rest follow prefixed. A host that needs the list before it can build a context — to precompute something per locale and close over it — calls this rather than deriving it again.
A page's own date: a literal one, the manifest's, or none.
A <sitemapindex> over {url, lastmod} children.
Takes whole URLs rather than paths, because an index may point at another
host entirely — which the protocol allows and entries_for/2 does not.
One <url> element.
Wraps rendered <url> entries in a <urlset>.