How to Generate a Simple Sitemap with Ruby on Rails

I wanted to share how I created a dynamic sitemap that I don't have to manually update. Below is an explanation of why and how I set it up but you can also skip to the final code here.


Why do you need a sitemap?

Your sitemap tells Google where to find all of your content.

Without one, some of your blog posts and website pages might not be indexed and won't show up in search results.

I built this website with Ruby on Rails and have started to build up a good number of blog posts.

And I definitely want to make sure Google can find them and share them with people!

You can manually generate a sitemap pretty easily, but that can lead to a lot of work depending on how frequently you update your website.

So, I think it's about time that I generate a sitemap dynamically.

Since I'm using Ruby on Rails I could use an existing Gem, but I found a lot of them to be overly complex.

Instead, I'm going to show you how I created a dynamic sitemap with just one new controller action and a new view.

You can check out the resulting sitemap here.


Let's Get Started

We need to give Google an endpoint that tells their crawlers where to find all of our content.

One of the easiest ways to do this, is to generate an XML file with our URLs and date they were last updated.

We can start with our routing.

Adding a Sitemap Route

We need to expose our sitemap.xml to Google. We can do this by adding a route to our routes.rb.

get '/sitemap', to: 'pushschool#sitemap'

This tells our app to direct requests made to pushschool.com/sitemap to our PushschoolController class and to then call the sitemap method.

Let's add that code next.

Adding the Sitemap to the Controller

In my PushSchoolController I need to define the sitemap action now.

It should look like this.

class PushschoolController < ApplicationController
  def sitemap
    # We'll add to this next...
  end
end

Now the actual XML output will be handled in the view.

Rails will automatically look for a view matching the name of our method here, so we need to create the matching sitemap view.

Creating the Sitemap View

In our app/views we need to make sure we have a directory corresponding to our controller with a file matching the method the controller is calling.

In this case, that means Rails will look for app/views/pushschool/sitemap.

We know we want to return the sitemap in XML and we are going to want to generate the output with Ruby so we can make this an XML file with the ERB templating engine.

So we end up with app/views/pushschool/sitemap.xml.erb.

Here's the exact code I use in my sitemap view.

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://pushschool.com</loc>
    <lastmod><%= @posts[0].updated_at.strftime("%Y-%m-%d") %></lastmod>
  </url>
  <% @posts.each do |post| %>
    <url>
      <loc>https://pushschool.com/<%= post.slug %></loc>
      <lastmod><%= post.updated_at.strftime("%Y-%m-%d") %></lastmod>
    </url>
  <% end %>
</urlset>

A couple of things are going on here.

The first <url> entry is actually just my homepage. There's nothing dynamic here, except that I'm updating the last modified date or <lastmod> to whenever I last published a new article.

Next, we are mapping through all of my posts with:

<% @posts.each do |post| %>

This lets us create a new <url> record where I can add the custom slug and last updated date based on each individual post.

From here, my app will automatically generate the new sitemap every time I add or update a post.

This wraps up the view, but we still need to cover how we get our posts to the view.


Wiring Everything Together

The final step here is to revisit our controller so that we fetch all our blog posts from the database.

I already have a BlogPost model defined so I can query all my blog posts from that model.

I also only want my published blog posts to appear in my sitemap, and none that are reposts with canonical links.

I created a scope to find published posts and make sure the canonical link field is empty.

There are fields specific to my blog post model, but you can customize your query depending on your own website content.

My query looks like this.

@posts = BlogPost.published.where(canonical_link: [nil, ''])


Here's the Final Code

Addition in our config/routes.rb

get '/sitemap', to: 'pushschool#sitemap'

app/controllers/pushschool_controller.rb

class PushschoolController < ApplicationController
  def sitemap
    @posts = BlogPost.published.where(canonical_link: [nil, ''])
  end
end

app/views/pushschool/sitemap.xml.erb

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://pushschool.com</loc>
    <lastmod><%= @posts[0].updated_at.strftime("%Y-%m-%d") %></lastmod>
  </url>
  <% @posts.each do |post| %>
    <url>
      <loc>https://pushschool.com/<%= post.slug %></loc>
      <lastmod><%= post.updated_at.strftime("%Y-%m-%d") %></lastmod>
    </url>
  <% end %>
</urlset>
Resources
Interview Tips
Up your interview skills
Tips and tricks for all developers
About Me

Hey, I'm Nicholas Dill.

I help people become better software developers with daily tips, tricks, and advice.

Level up
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.