Building a Search Engine Friendly Sitemap XML with Laravel

Published on by

Building a Search Engine Friendly Sitemap XML with Laravel image

A few years ago search engines recommended submitted sitemaps to help with indexing your website and now the importance of this is debatable.

I’m of the mindset creating and submitting can’t hurt, so I spent a little time putting one together and wanted to share how easy this is in Laravel.

What is a sitemap

If you are not familiar with a Sitemap, Google defines it as:

A sitemap is a file where you can list the web pages of your site to tell Google and other search engines about the organization of your site content. Search engine web crawlers like Googlebot read this file to more intelligently crawl your site.

They also outline the following reasons on why you need one:

  • Your site is really large. As a result, it’s more likely Google web crawlers might overlook crawling some of your new or recently updated pages.
  • Your site has a large archive of content pages that are isolated or well not linked to each other. If you site pages do not naturally reference each other, you can list them in a sitemap to ensure that Google does not overlook some of your pages.
  • Your site is new and has few external links to it. Googlebot and other web crawlers crawl the web by following links from one page to another. As a result, Google might not discover your pages if no other sites link to them.
  • Your site uses rich media content, is shown in Google News, or uses other sitemaps-compatible annotations. Google can take additional information from sitemaps into account for search, where appropriate.

Your site might not hit those criteria, but as I mentioned, I still believe it’s worth submitting one just to be safe.

The Sitemap Protocol

On the official Sitemaps website it outlines all the information you will need for building your own sitemap. Instead of reading through the whole spec here is a basic sample with a single url entered:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://www.example.com/</loc>
<lastmod>2005-01-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>

As you can see, it’s just an XML file with an individual <url> for each of your sites pages.

A single file can hold around 50,000 records, but you also have the ability to separate them out into multiple files and utilize an index file to point to the others.

The spec outlines this style like this:

<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>http://www.example.com/sitemap1.xml.gz</loc>
<lastmod>2004-10-01T18:23:17+00:00</lastmod>
</sitemap>
<sitemap>
<loc>http://www.example.com/sitemap2.xml.gz</loc>
<lastmod>2005-01-01</lastmod>
</sitemap>
</sitemapindex>

Inside each <loc> it points to a file that it includes the <url> items like in the first example.

For this tutorial, I’m going to use the index style because I have records coming from different tables. This allows me the opportunity to customize each list of URL’s in the correct format without having to do extra processing.

Building a Sitemap Controller.

My sitemap will have two primary sections, and they are blog posts, blog categories, and podcast episodes. Each of these will be in their own file, and the index will point to them.

To get started let’s create a new sitemap controller:

php artisan make:controller SitemapController

Now open this file and let’s create a sitemap index.

Creating the Sitemap Index

Create a new index method that will generate the XML needed:

public function index()
{
$post = Post::active()->orderBy('updated_at', 'desc')->first();
$podcast = Podcast::active()->orderBy('updated_at', 'desc')->first();
 
return response()->view('sitemap.index', [
'post' => $post,
'podcast' => $podcast,
])->header('Content-Type', 'text/xml');
}

The post and podcast queries are needed to generate the last modified timestamp in our index view as that informs the crawlers if new content has been added since they last looked.

Also, if you are not familiar with this return style what it is doing is returning a response object, with an assigned view, and setting the text/xml header. If you only return a view() then the header isn’t available so by including the response first this gives you access to include it.

Next our sitemap.index view file will look like this:

<?php echo '<?xml version="1.0" encoding="UTF-8"?>'; ?>
 
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://laravel-news.com/sitemap/posts</loc>
<lastmod>{{ $post->publishes_at->tz('UTC')->toAtomString() }}</lastmod>
</sitemap>
<sitemap>
<loc>https://laravel-news.com/sitemap/categories</loc>
<lastmod>{{ $post->publishes_at->tz('UTC')->toAtomString() }}</lastmod>
</sitemap>
<sitemap>
<loc>https://laravel-news.com/sitemap/podcasts</loc>
<lastmod>{{ $podcast->publishes_at->tz('UTC')->toAtomString() }}</lastmod>
</sitemap
</sitemapindex>

For this view, I’m just taking my custom publishes_at timestamp and using Carbon to set the timezone to UTC and finally using the Carbon Atom string helper to format it correctly.

Creating the Sitemap URL file

The next step is creating each URL file. The controller gets three new methods, and all are very similar. Here is an example:

<br></br>public function posts()
{
$posts = Post::active()->where('category_id', '!=', 21)->get();
return response()->view('sitemap.posts', [
'posts' => $posts,
])->header('Content-Type', 'text/xml');
}
 
public function categories()
{
$categories = Category::all();
return response()->view('sitemap.categories', [
'categories' => $categories,
])->header('Content-Type', 'text/xml');
}
 
public function podcasts()
{
$podcast = Podcast::active()->orderBy('updated_at', 'desc')->get();
return response()->view('sitemap.podcasts', [
'podcasts' => $podcast,
])->header('Content-Type', 'text/xml');
}

Once that is setup here is an example view file from sitemap.posts:

<?php echo '<?xml version="1.0" encoding="UTF-8"?>'; ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@foreach ($posts as $post)
<url>
<loc>https://laravel-news.com/{{ $post->uri }}</loc>
<lastmod>{{ $post->publishes_at->tz('UTC')->toAtomString() }}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
@endforeach
</urlset>

With that in place and then duplicated for each of your sections it should be ready to go as soon as you add your routes. Here is how mine are setup:

Route::get('/sitemap', 'SitemapController@index');
Route::get('/sitemap/posts', 'SitemapController@posts');
Route::get('/sitemap/categories', 'SitemapController@categories');
Route::get('/sitemap/podcasts', 'SitemapController@podcasts');

You may notice I elected to not use the XML file extension and that is not a requirement. However, if you would like to use it then you can add the extension to the route:

Route::get('/sitemap.xml', 'SitemapController@index');

Then just adjust the views to point to the proper location.

Wrap up

As you hopefully see building your sitemap is not that difficult with Laravel, especially when your site structure is simple like mine. If you’d like to automate this whole process packages do exist and it might save you time using those.

If you’d like to go further, you can also build image and video sitemaps, as well as create your own XSL stylesheet.

Happy SEOing!

Eric L. Barnes photo

Eric is the creator of Laravel News and has been covering Laravel since 2012.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Paragraph

Manage your Laravel app as if it was a CMS – edit any text on any page or in any email without touching Blade or language files.

Visit Paragraph
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
LoadForge logo

LoadForge

Easy, affordable load testing and stress tests for websites, APIs and databases.

LoadForge
Paragraph logo

Paragraph

Manage your Laravel app as if it was a CMS – edit any text on any page or in any email without touching Blade or language files.

Paragraph
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
DocuWriter.ai logo

DocuWriter.ai

Save hours of manually writing Code Documentation, Comments & DocBlocks, Test suites and Refactoring.

DocuWriter.ai
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Launch your Startup Fast with LaraFast image

Launch your Startup Fast with LaraFast

Read article
Embed Livewire Components on Any Website image

Embed Livewire Components on Any Website

Read article
Statamic announces next Flat Camp retreat (EU edition) image

Statamic announces next Flat Camp retreat (EU edition)

Read article
Laravel Herd releases v1.5.0 with new services. No more Docker, DBNGIN, or even homebrew! image

Laravel Herd releases v1.5.0 with new services. No more Docker, DBNGIN, or even homebrew!

Read article
Resources for Getting Up To Speed with Laravel 11 image

Resources for Getting Up To Speed with Laravel 11

Read article
Laravel 11 is now released! image

Laravel 11 is now released!

Read article