Create a sitemap in Phoenix 1.7+
A guide on maintaining your own sitemap.
originally published
Recently I’ve been working on some SEO stuff for a site of mine (an RSS reader app, catnip.vip ), and wanted to set up a sitemap.
I went looking for ideas online, and found a lot of people using packages that would construct their sitemaps dynamically (usually based on their Ecto models or something).
The content I was interested in indexing was a small, set series of pages, so I didn’t really have any need of that. I figured it’d be simple enough to set up a sitemap by hand.
Phoenix 1.7 (and the lack of views)
Phoenix 1.7 includes some radical changes to how the view layer functions (by getting rid of it). Here's how you may go about building a sitemap in Phoenix 1.7.
Although we’ll be maintaining this list by hand instead of generating it dynamically, we’ll also make use of an amazing feature of Phoenix 1.7: compile-time verified routes.
Instead of a controller and a view, we’re going to need a controller and an HTML module. Make them:
defmodule ExampleWeb.SitemapController do
use ExampleWeb, :controller
def index(conn, _) do
# todo
end
end
sitemap_controller.ex
defmodule ExampleWeb.SitemapHTML do
use ExampleWeb, :html
embed_templates "sitemap_html/*"
end
sitemap_html.ex
Then let’s put the shell of our template in:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<%= show_pages() %>
</urlset>
sitemap_html/index.xml.eex
We’ll define a function called show_pages in SitemapHTML to loop through a list of pages, and create some XML:
# list pages with sigil_p for compile-time checking
def pages do
[
# application
~p"/",
~p"/posts",
~p"/feeds",
# general pages
~p"/privacy",
~p"/terms",
# billing
~p"/pricing",
]
end
def show_pages do
for path <- pages() do
route = ExampleWeb.Endpoint.url() <> path
"""
<url>
<loc>#{route}</loc>
<priority>0.5</priority>
<changefreq>weekly</changefreq>
</url>
"""
end
end
sitemap_html.ex
This is where the verified routes come in. If we were to make a mistake in this list, we’d know right away, which makes maintaining this list yourself a lot less daunting.
Next, let’s add in a compile-time macro to set the last modified time:
defmacro today do
quote do
Date.utc_today()
end
end
def show_pages do
for path <- pages() do
route = ExampleWeb.Endpoint.url() <> path
"""
<url>
<loc>#{route}</loc>
<lastmod>#{today()}</lastmod>
<priority>0.5</priority>
<changefreq>weekly</changefreq>
</url>
"""
end
end
sitemap_html.ex
Now, we may fill in the index route in the controller:
defmodule ExampleWeb.SitemapController do
use ExampleWeb, :controller
def index(conn, _) do
xml = ExampleWeb.SitemapHTML.index(%{})
conn
|> put_resp_content_type("text/xml")
|> text(xml)
end
end
sitemap_controller.ex
Finally, add this controller to your routes:
scope "/", ExampleWeb do
pipe_through(:browser)
get("/sitemap", SitemapController, :index)
end
router.ex
And that’s pretty much it!
You can check out an example of the output on one of my sites.