loading...

Create dynamic sitemap.xml in Elixir and Phoenix

ricardoruwer profile image Ricardo Ruwer ・3 min read

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! :)

Discussion

markdown guide
 

Thank you for sharing this. Super useful! Just adding it to my site now.