Create dynamic sitemap.xml in Elixir and Phoenix
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! :)