DEV Community

Ghulam Muhammad
Ghulam Muhammad

Posted on

How to Build an SEO-Friendly XML Sitemap System in Laravel

An XML sitemap is one of the simplest ways to help search engines discover important URLs on a website.

For a small website, manually maintaining a sitemap may be enough. But as a Laravel application grows and starts generating hundreds or thousands of dynamic URLs, maintaining a static sitemap becomes difficult.

In this tutorial, we'll build a practical XML sitemap system in Laravel that generates URLs dynamically from database records.

We'll cover:

  • What an XML sitemap is
  • Why Laravel applications benefit from dynamic sitemaps
  • Creating a sitemap controller
  • Generating XML responses
  • Adding database URLs
  • Creating a sitemap index
  • Handling multiple content types
  • Adding a sitemap to robots.txt
  • Using canonical URLs
  • Handling large datasets
  • Caching sitemap data
  • Testing the final sitemap

What Is an XML Sitemap?

An XML sitemap is a file containing URLs that you want search engines to discover.

A basic sitemap looks like this:

<?xml version="1.0" encoding="UTF-8"?>

<url>
    <loc>https://example.com/</loc>
</url>

<url>
    <loc>https://example.com/about</loc>
</url>

<url>
    <loc>https://example.com/blog/my-first-post</loc>
</url>
Enter fullscreen mode Exit fullscreen mode

The <loc> element contains the URL.

A sitemap does not guarantee that a page will be indexed or ranked. Its main purpose is to help search engines discover URLs efficiently.

Why Use a Dynamic Sitemap in Laravel?

Laravel applications frequently generate pages from database records.

For example, an application may contain:

  • Blog posts
  • Products
  • Authors
  • Categories
  • Books
  • Courses
  • Documentation
  • Tools

Instead of manually adding every URL to a sitemap, Laravel can generate the URLs from your application's database.

This means that when new public content is published, it can automatically become part of the sitemap.

Likewise, unpublished or deleted content can be removed from the sitemap.

Step 1: Create a Sitemap Controller

Create a controller using Artisan:

php artisan make:controller SitemapController

Then create a basic sitemap method:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Response;

class SitemapController extends Controller
{
public function index(): Response
{
$xml = '<?xml version="1.0" encoding="UTF-8"?>';

    $xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';

    $xml .= '<url>';
    $xml .= '<loc>' . url('/') . '</loc>';
    $xml .= '</url>';

    $xml .= '</urlset>';

    return response($xml)
        ->header('Content-Type', 'application/xml');
}
Enter fullscreen mode Exit fullscreen mode

}

This creates a simple XML response containing your homepage.

Step 2: Add the Sitemap Route

Open:

routes/web.php

Add:

use App\Http\Controllers\SitemapController;

Route::get('/sitemap.xml', [SitemapController::class, 'index'])
->name('sitemap');

Now open:

https://example.com/sitemap.xml

You should see the XML sitemap.

Step 3: Generate URLs From the Database

The real benefit of a Laravel sitemap comes from generating URLs dynamically.

Imagine that your application has a Post model.

Import the model:

use App\Models\Post;

Then retrieve published posts:

$posts = Post::where('status', 'published')
->latest('updated_at')
->get();

Now add each post to the sitemap:

foreach ($posts as $post) {
$xml .= '';
$xml .= '' . e(route('posts.show', $post->slug)) . '';
$xml .= '' . $post->updated_at->toAtomString() . '';
$xml .= '';
}
The complete controller could look like this:

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Response;

class SitemapController extends Controller
{
public function index(): Response
{
$posts = Post::where('status', 'published')
->latest('updated_at')
->get();

    $xml = '<?xml version="1.0" encoding="UTF-8"?>';

    $xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';

    foreach ($posts as $post) {
        $xml .= '<url>';
        $xml .= '<loc>' . e(route('posts.show', $post->slug)) . '</loc>';
        $xml .= '<lastmod>' . $post->updated_at->toAtomString() . '</lastmod>';
        $xml .= '</url>';
    }

    $xml .= '</urlset>';

    return response($xml)
        ->header('Content-Type', 'application/xml');
}
Enter fullscreen mode Exit fullscreen mode

}

Step 4: Add Multiple Content Types

Most real Laravel applications have several types of public content.

For example:

/posts/...
/products/...
/authors/...
/categories/...

You can retrieve each type separately:

$posts = Post::where('status', 'published')->get();

$products = Product::where('status', 'active')->get();

$authors = User::where('is_author', true)->get();

You can then generate sitemap URLs for each content type.

This approach is useful for content-driven platforms and marketplaces.

For example, a platform such as WebDecky contains multiple types of public resources, including SEO tools, web utilities, digital products, blog content, and community features.

Step 5: Use a Sitemap Index for Larger Websites

As a website grows, separating sitemap files by content type can make the system easier to maintain.

For example:

sitemap-index.xml
sitemap-pages.xml
sitemap-posts.xml
sitemap-products.xml
sitemap-authors.xml
sitemap-categories.xml

A sitemap index can look like this:

<?xml version="1.0" encoding="UTF-8"?>

<sitemap>
    <loc>https://example.com/sitemap-pages.xml</loc>
</sitemap>

<sitemap>
    <loc>https://example.com/sitemap-posts.xml</loc>
</sitemap>

<sitemap>
    <loc>https://example.com/sitemap-products.xml</loc>
</sitemap>

<sitemap>
    <loc>https://example.com/sitemap-authors.xml</loc>
</sitemap>
Enter fullscreen mode Exit fullscreen mode

This structure allows different content types to be generated independently.

Step 6: Create the Sitemap Index

You can create a dedicated controller method:

public function index(): Response
{
$sitemaps = [
url('/sitemap-pages.xml'),
url('/sitemap-posts.xml'),
url('/sitemap-products.xml'),
url('/sitemap-authors.xml'),
];

$xml = '<?xml version="1.0" encoding="UTF-8"?>';

$xml .= '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';

foreach ($sitemaps as $sitemap) {
    $xml .= '<sitemap>';
    $xml .= '<loc>' . e($sitemap) . '</loc>';
    $xml .= '</sitemap>';
}

$xml .= '</sitemapindex>';

return response($xml)
    ->header('Content-Type', 'application/xml');
Enter fullscreen mode Exit fullscreen mode

}

Then add the route:

Route::get('/sitemap-index.xml', [
SitemapController::class,
'index'
]);

Step 7: Add the Sitemap to robots.txt

Your robots.txt can point search engines to the sitemap index:

User-agent: *
Allow: /

Sitemap: https://example.com/sitemap-index.xml

For example, WebDecky provides its sitemap index at:

https://webdecky.com/sitemap-index.xml

This gives crawlers another way to discover the site's public URLs.

Step 8: Only Include Public URLs

A sitemap should contain URLs that you actually want search engines to discover.

Avoid adding private or administrative URLs such as:

/admin
/login
/register
/cart
/checkout
/user/settings
/private-content

Instead, focus on useful public pages.

For a digital marketplace, examples might include:

/products/laravel-admin-panel
/products/seo-tool
/authors/example-author
/categories/laravel

For a blog:

/blog/laravel-seo-guide
/blog/xml-sitemap-guide
/blog/php-performance-guide

The goal is to help search engines discover your important public content.

Step 9: Use Canonical URLs

The URLs in your sitemap should normally represent the preferred canonical versions of your pages.

For example, suppose these URLs show the same page:

https://example.com/product/tool
https://example.com/product/tool?ref=facebook

The sitemap should generally contain the preferred URL:

https://example.com/product/tool

rather than tracking variations.

Canonicalization is important when multiple URLs can represent the same content.

Step 10: Handle Large Datasets

If your application contains thousands of records, avoid loading everything into memory unnecessarily.

Instead of:

$posts = Post::all();

you can process records in chunks:

Post::where('status', 'published')
->chunkById(1000, function ($posts) use (&$xml) {

    foreach ($posts as $post) {
        $xml .= '<url>';
        $xml .= '<loc>' . e(route('posts.show', $post->slug)) . '</loc>';
        $xml .= '</url>';
    }

});
Enter fullscreen mode Exit fullscreen mode

Chunking can reduce memory usage when processing large datasets.

For very large applications, generating multiple sitemap files is usually a cleaner architecture than producing one enormous XML response.

Step 11: Cache Generated Sitemap Data

If your sitemap contains thousands of URLs, generating it from the database on every request may create unnecessary database queries.

Laravel's cache system can help.

For example:

$xml = Cache::remember(
'sitemap-posts',
now()->addHours(6),
function () {
// Generate sitemap XML here.
}
);

You can refresh or clear the cache when important content changes.

This can reduce repeated database work.

Step 12: Test Your Sitemap

After implementing the system, test your sitemap URLs.

For example:

WebDecky Sitemap Index

You should also test individual sitemap files if your application provides them.

Check:

  • XML is valid
  • URLs are publicly accessible
  • URLs return appropriate HTTP responses
  • URLs are canonical
  • Unpublished content is excluded
  • Private content is excluded
  • Duplicate URLs are avoided
  • Sitemap files are not unnecessarily large

A Practical Laravel Sitemap Architecture

For a growing Laravel application, I prefer an architecture like:

sitemap-index.xml

├── sitemap-pages.xml
├── sitemap-posts.xml
├── sitemap-products.xml
├── sitemap-authors.xml
├── sitemap-categories.xml
└── sitemap-other-content.xml

The sitemap index becomes the central entry point.

Each sitemap can then be responsible for one particular type of content.
This makes the system easier to maintain as the application grows.

Example Route Structure

Your routes could look like this:

Route::get('/sitemap-index.xml', [
SitemapController::class,
'index'
]);

Route::get('/sitemap-pages.xml', [
SitemapController::class,
'pages'
]);

Route::get('/sitemap-posts.xml', [
SitemapController::class,
'posts'
]);

Route::get('/sitemap-products.xml', [
SitemapController::class,
'products'
]);

Route::get('/sitemap-authors.xml', [
SitemapController::class,
'authors'
]);

This keeps sitemap generation organized and makes it easier to add additional content types later.

Connecting Your Sitemap to Google Search Console

After your sitemap is working, you can submit the sitemap index through Google Search Console.

For example:

sitemap-index.xml

You can also include the sitemap location in robots.txt.

Remember that submitting a sitemap does not guarantee that every URL will be indexed or ranked.

It simply gives search engines another way to discover your site's URLs.

A Real-World Laravel Example

I use this type of sitemap architecture for WebDecky, a Laravel-based platform combining SEO tools, web utilities, a digital marketplace, blog content, and community features.

Because different parts of the platform generate different types of URLs, separating sitemap generation by content type makes the SEO infrastructure easier to maintain.

For example, the public website contains different types of pages that can be discovered through the site's sitemap system.

You can explore the live platform here:

Visit WebDecky

And you can inspect the sitemap index here:

View WebDecky's XML Sitemap

The important lesson is that you don't need to manually edit a sitemap every time new content is published.

Laravel can generate sitemap URLs directly from your application's data.

SEO Benefits of a Well-Designed Sitemap

A properly implemented sitemap can help with:

  • Discovering new URLs
  • Discovering updated content
  • Organizing large websites
  • Helping crawlers find important public pages
  • Managing different content types
  • Making technical SEO easier to maintain

However, a sitemap is only one part of technical SEO.

You should also pay attention to:

  • Page quality
  • Internal linking
  • Canonical URLs
  • HTTP status codes
  • Mobile usability
  • Page performance
  • Structured data where appropriate
  • Crawlable navigation
  • Useful, original content

Final Thoughts

A dynamic XML sitemap is a relatively small Laravel feature that can make a large application much easier to manage.

The basic workflow is:

 Database

Laravel Models

Sitemap Controller

XML Sitemap

Sitemap Index

Search Engines

For a small website, one sitemap may be enough.

For a larger Laravel application, separating sitemaps by content type provides a cleaner and more scalable architecture.

Most importantly, remember that a sitemap is about discoverability, not guaranteed rankings.

Build useful content, maintain good internal linking, use canonical URLs, keep your public pages accessible, and use your sitemap as one part of a broader technical SEO strategy.

If you're building a Laravel application with many dynamic pages, implementing the sitemap system early can save you a lot of maintenance later.

Top comments (0)