Create dynamic sitemap.xml in Elixir and Phoenix

Ricardo Ruwer
2 min readJul 8, 2019

--

I created a website for a construction company where they can sell their properties, I built it using Elixir and the Phoenix framework.

So I was searching on Google how to implement a sitemap.xml but all I can find was this lib called sitemap, so I tried it but…

On my website, I have a single page for each property that is created, and in this lib, I have to run a command to generate a new XML file every time I create a property. Also, I can't change the <lastmod> because it always uses the date where I generated the sitemap.

So I thought to create my own dynamic sitemap.xml and I will share with you how I did it, it's very simple :)

First of all, I created my new route on the file router.ex:

get "/sitemap.xml", SitemapController, :index

And then I created a new file lib/myapp_web/controllers/sitemap_controller.ex with this code:

defmodule MyAppWeb.SitemapController do
use MyAppWeb, :controller
plug :put_layout, false alias MyApp.Properties def index(conn, _params) do
properties = Properties.list_properties()
conn
|> put_resp_content_type("text/xml")
|> render("index.xml", properties: properties)
end
end

Cool :) Now we have to create the file lib/myapp_web/templates/sitemap/index.xml.eex that will be our sitemap:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc><%= Routes.home_url(@conn, :index) %></loc>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc><%= Routes.about_url(@conn, :index) %></loc>
<changefreq>never</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc><%= Routes.apartment_url(@conn, :index) %></loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc><%= Routes.house_url(@conn, :index) %></loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc><%= Routes.lot_url(@conn, :index) %></loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc><%= Routes.contact_url(@conn, :index) %></loc>
<changefreq>never</changefreq>
<priority>0.5</priority>
</url>
<%= for property <- @properties do %>
<url>
<loc><%= Routes.property_url(@conn, :show, property.slug) %></loc>
<lastmod><%= format_date(property.updated_at) %></lastmod>
<changefreq>weekly</changefreq>
<priority>1</priority>
</url>
<% end %>
</urlset>

You can see that I added manually all the routes I have on my website: home, about, apartments, houses, lots and contact. And then I added all the dynamic pages for each property I have created.

And I have the <lastmod> with the correct date, using the updated_at . But we still have to create this function called format_date so we create the file lib/myapp_web/views/sitemap_view.ex:

defmodule MyAppWeb.SitemapView do
use MyAppWeb, :view
def format_date(date) do
date
|> DateTime.from_naive!("Etc/UTC")
|> DateTime.to_date()
|> to_string()
end
end

And that's it! Now you can now add this URL: yoursite.com/sitemap.xml on the Google Console and it'll just work fine :)

But don't forget to add some tests for this! So we can do this on the file test/myapp_web/controllers/sitemap_controller_test.exs :

defmodule MyAppWeb.SitemapControllerTest do
use MyAppWeb.ConnCase
describe "GET /sitemap.xml" do
test "accesses the sitemap in format xml", %{conn: conn} do
property = property_fixture()
conn = get(conn, "/sitemap.xml") assert response_content_type(conn, :xml)
assert response(conn, 200) =~ ~r/<loc>.*#{property.slug}<\/loc>/
end
end
end

In this function property_fixture() you have to create a new property.

And then a test to our view in test/myapp_web/views/sitemap_view_test.exs:

defmodule MyAppWeb.SitemapViewTest do
use MyAppWeb.ConnCase, async: true
alias MyAppWeb.SitemapView test "format_date/1" do
assert SitemapView.format_date(~N[2019-07-08 13:15:00]) == "2019-07-08"
end
end

And that's all! :)

--

--