Laravel

How to build a Sitemap for your Laravel App

Sitemap is basically a blueprint of a website's structure, providing a comprehensive list of its pages, their relationships, and metadata. In this article we're going to take a look at how I used the Spatie Sitemap package to build sitemaps for my blog.

How to build a Sitemap for your Laravel App

There are two primary types of sitemaps, XML and HTML. The XML variant is designed for search engines, offering a machine-readable format that aids in efficient crawling and indexing. On the other hand, the HTML sitemap caters to human visitors, presenting a user-friendly navigation tool that simplifies the browsing experience.

Spatie's laravel-sitemap package helps us generate the first type (XML). You can learn more about how this package works by reading its documentation.

Generally, before you generate the sitemap for your website, you need to have a clear idea of what URLs you want search index to crawl and index. In my case, I mainly want to index topics (categories) and posts (articles) on my blog. To achieve that, I decided to break them out into different sitemap indexes (sitemap_posts.xml and sitemap_categories.xml) and then merge them into one single file (sitemap.xml) along with some static URLs.

First, let's install the package via composer:

composer require spatie/laravel-sitemap

Once the back is installed, I proceed to create a custom class app/Actions/BuildSitemap.php that looks like the following:

<?php
 
namespace App\Actions;
 
use App\Models\Category;
use App\Models\Post;
use Spatie\Sitemap\Sitemap;
use Spatie\Sitemap\Tags\Url;
 
class BuildSitemap
{
public function build(): void
{
Sitemap::create()
->add($this->build_index(Post::all(), 'sitemap_posts.xml'))
->add($this->build_index(Category::all(), 'sitemap_categories.xml'))
->add(Url::create(route('home'))->setPriority(1.0)->setChangeFrequency(Url::CHANGE_FREQUENCY_ALWAYS))
->add(Url::create(route('resources'))->setPriority(0.5)->setChangeFrequency(Url::CHANGE_FREQUENCY_MONTHLY))
->add(Url::create(route('about'))->setPriority(0.3)->setChangeFrequency(Url::CHANGE_FREQUENCY_YEARLY))
->writeToFile(public_path('sitemap.xml'));
}
 
public function build_index($model, $path): string
{
Sitemap::create()
->add($model)
->writeToFile(public_path($path));
 
return $path;
}
}

In this class, the build() method takes care of building the indexes for the different sections I want a sitemap for. While the build_index() method takes in the model and the path, writes into the file and then returns the path.

After that, we need to instruct our models to create the content of the sitemap files from the routes, just like the documentation mentions. To do that, we need to implement the Sitemapable interface and then add the toSitemapTag() method.

use Spatie\Sitemap\Contracts\Sitemapable;
use Illuminate\Support\Carbon;
use Spatie\Sitemap\Tags\Url;
 
class Post extends Model implements Sitemapable
{
// Your model's code...
 
public function toSitemapTag(): Url|string|array
{
return Url::create(route('post.show', $this->slug))
->setLastModificationDate(Carbon::create($this->updated_at))
->setChangeFrequency(Url::CHANGE_FREQUENCY_DAILY)
->setPriority(0.9);
}
}

Same goes for the Category model:

use Spatie\Sitemap\Contracts\Sitemapable;
use Illuminate\Support\Carbon;
use Spatie\Sitemap\Tags\Url;
 
class Post extends Model implements Sitemapable
{
// Your model's code...
 
public function toSitemapTag(): Url|string|array
{
return Url::create(route('category.show', $this->slug))
->setLastModificationDate(Carbon::create($this->updated_at))
->setChangeFrequency(Url::CHANGE_FREQUENCY_MONTHLY)
->setPriority(0.5);
}
}

P.S. If you're wondering how to set up the Frequency and Priority of your URLs, check out this great blog post to understand how they work.

We're pretty much set, all we need now is a way to invoke our custom class to build our sitemaps. For this, I created a custom artisan command:

php artisan make:command GenerateSitemap

My app/Console/Commands/GenerateSitemap.php looks like the following:

<?php
 
namespace App\Console\Commands;
 
use App\Actions\BuildSitemap;
use Illuminate\Console\Command;
 
class GenerateSitemap extends Command
{
protected $signature = 'sitemap:generate';
 
protected $description = 'Generate the sitemap.';
 
public function handle(): void
{
try {
 
$this->info('Generating sitemap...');
 
(new BuildSitemap())->build();
 
$this->info('Sitemap generated successfully!');
 
} catch (\Exception $exception) {
 
$this->info('Sitemap generation failed!');
 
$this->error($exception->getMessage());
 
}
}
}

I'm now able to use php artisan sitemap:generate in my terminal to generate my app's sitemap. The generated files (sitemap.xml, sitemap_categories.xml and sitemap_posts.xml) will be located under the public directory.

Of course, it's not fully automated yet. I could schedule this command to run every day or so, but instead, I decided to run it everytime I create, update or delete an article. You can do this by observing the model's events:

use Illuminate\Support\Facades\Artisan;
 
protected static function booted(): void
{
static::created(function ($model) {
// Check if the article's status is published
if ($model->status === PostStatus::Published) {
Artisan::call('sitemap:generate');
}
});
 
static::updated(function ($model) {
// Check if anything was changed in the article
if ($model->wasChanged()) {
Artisan::call('sitemap:generate');
}
});
 
static::deleted(function ($model) {
Artisan::call('sitemap:generate');
});
}

I hope this helps you to create a sitemap for your own Laravel application.

Until next time. ✌️