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.

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
Laravel Idea for PhpStorm logo

Laravel Idea for PhpStorm

Ultimate PhpStorm plugin for Laravel developers, delivering lightning-fast code completion, intelligent navigation, and powerful generation tools to supercharge productivity.

Laravel Idea for PhpStorm
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
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

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
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate logo

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate

Build your SaaS application in hours. Out-of-the-box multi-tenancy and seamless Stripe integration. Supports subscriptions and one-time purchases, allowing you to focus on building and creating without repetitive setup tasks.

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate
JetShip - Laravel Starter Kit logo

JetShip - Laravel Starter Kit

A Laravel SaaS Boilerplate and a starter kit built on the TALL stack. It includes authentication, payments, admin panels, and more. Launch scalable apps fast with clean code, seamless deployment, and custom branding.

JetShip - Laravel Starter Kit
Rector logo

Rector

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

Rector
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
Streamlining Route Parameters in Laravel Using URL Defaults image

Streamlining Route Parameters in Laravel Using URL Defaults

Read article
Flexible Docker Images with PHP INI Environment Variables image

Flexible Docker Images with PHP INI Environment Variables

Read article
Dynamic Form Validation in Laravel with prohibited_if image

Dynamic Form Validation in Laravel with prohibited_if

Read article
Add Approvals to Your Laravel Application image

Add Approvals to Your Laravel Application

Read article
Enhancing Data Processing with Laravel's transform() Method image

Enhancing Data Processing with Laravel's transform() Method

Read article
Get Xdebug Working With Docker and PHP 8.4 in One Minute image

Get Xdebug Working With Docker and PHP 8.4 in One Minute

Read article