DEV Community

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And that's all! :)

Discussion

pic
Editor guide
Collapse
michaeljones profile image
Michael Jones

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

Collapse
tmartin8080 profile image
Troy Martin

πŸ‘