Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Laravel Sitemap Generator SEO Best Practices

By Kokil Thapa | Last reviewed: September 2026

Search engines cannot rank pages they never discover, and on a dynamic Laravel application that gap shows up fast when routes, slugs, and published content change weekly. Laravel sitemap generator SEO best practices turn your route model, database records, and Laravel SEO foundations into a machine-readable XML sitemap that stays current without manual edits. Whether you run a legal-tech portal, a booking system, or a multi-vendor marketplace, the goal is the same: expose canonical, indexable URLs and keep junk out of Google's crawl budget. This guide covers package choice, URL inclusion rules, performance, submission, and deployment automation for production Laravel 12 and 13 applications on PHP 8.3+.

How do you generate a sitemap in Laravel for SEO?

A Laravel sitemap is not a Blade view you hand-edit. It is generated output that conforms to the Sitemaps XML protocol and lists URLs search engines should crawl. In practice you wire a generator command into your application lifecycle so the file reflects live data.

Install and configure Spatie Laravel Sitemap

On real client projects I reach for spatie/laravel-sitemap because it integrates cleanly with Eloquent, supports sitemap indexes for large sites, and publishes a dedicated Artisan command. Install with Composer 2.10:

composer require spatie/laravel-sitemap
php artisan vendor:publish --provider="Spatie\Sitemap\SitemapServiceProvider"

Create a generator class that assembles URLs from static routes and database-backed content:

<?php

namespace App\Console\Commands;

use App\Models\Post;
use App\Models\Service;
use Illuminate\Console\Command;
use Spatie\Sitemap\Sitemap;
use Spatie\Sitemap\Tags\Url;

class GenerateSitemap extends Command
{
    protected $signature = 'sitemap:generate';
    protected $description = 'Build sitemap.xml for SEO';

    public function handle(): int
    {
        $sitemap = Sitemap::create()
            ->add(Url::create(route('home'))
                ->setPriority(1.0)
                ->setChangeFrequency(Url::CHANGE_FREQUENCY_WEEKLY))
            ->add(Url::create(route('about'))
                ->setPriority(0.6));

        Post::query()
            ->published()
            ->select(['slug', 'updated_at'])
            ->orderByDesc('updated_at')
            ->chunkById(500, function ($posts) use ($sitemap) {
                foreach ($posts as $post) {
                    $sitemap->add(
                        Url::create(route('blog.show', $post->slug))
                            ->setLastModificationDate($post->updated_at)
                            ->setChangeFrequency(Url::CHANGE_FREQUENCY_MONTHLY)
                            ->setPriority(0.8)
                    );
                }
            });

        Service::query()
            ->where('is_indexable', true)
            ->each(fn ($service) => $sitemap->add(
                Url::create(route('services.show', $service->slug))
                    ->setLastModificationDate($service->updated_at)
            ));

        $sitemap->writeToFile(public_path('sitemap.xml'));

        return self::SUCCESS;
    }
}

Register the command in routes/console.php (Laravel 11+) or your scheduler so it runs nightly and after bulk imports:

use Illuminate\Support\Facades\Schedule;

Schedule::command('sitemap:generate')
    ->dailyAt('02:15')
    ->withoutOverlapping()
    ->onOneServer();

Expose the sitemap at a stable URL

Serve the generated file from public/sitemap.xml or stream it through a route only if you need dynamic headers. Static files behind your web server are faster and simpler for page-speed work. Add a route fallback only when generation is fully dynamic:

Route::get('/sitemap.xml', function () {
    abort_unless(file_exists(public_path('sitemap.xml')), 404);
    return response()->file(public_path('sitemap.xml'), [
        'Content-Type' => 'application/xml; charset=UTF-8',
    ]);
});
Laravel Sitemap Generator FlowEloquentPosts, PagesRoutesNamed URLsFiltersCanonical rulessitemap:generate CommandSpatie Sitemap builderpublic/sitemap.xmlLinked in robots.txt
Laravel sitemap generator SEO best practices: collect indexable URLs from models and routes, apply filters, write XML to a public path.

Which Laravel sitemap package should you use in 2026?

You can build XML by hand, but maintained packages reduce bugs around escaping, lastmod formatting, and sitemap index splitting. Compare the two approaches teams still use on PHP 8.3+ Laravel apps:

OptionBest forProsCons
spatie/laravel-sitemapMost Laravel 12/13 appsActive maintenance, chunk-friendly, sitemap index support, video/image tagsRequires scheduled generation for large catalogs
Custom XML builderTiny static sitesZero dependency, full controlEasy to get charset, escaping, or 50k URL limits wrong
CMS plugins (WordPress)WordPress/WooCommerce stacksAutomatic on publishNot applicable to custom Laravel routing logic

For Laravel 13.x on PHP 8.3 or PHP 8.5, Spatie remains the pragmatic default I've used alongside other Spatie packages such as sluggable and media library on production apps. If your site exceeds 50,000 URLs or a 50 MB uncompressed file, split into a sitemap index:

use Spatie\Sitemap\SitemapIndex;
use Spatie\Sitemap\Sitemap;

SitemapIndex::create()
    ->add('/sitemaps/pages.xml')
    ->add('/sitemaps/blog.xml')
    ->add('/sitemaps/products.xml')
    ->writeToFile(public_path('sitemap.xml'));

E-commerce builds like Nepal Gift Card and directory platforms such as Lawyers Pokhara benefit from split sitemaps so product or listing regeneration does not block the entire crawl signal. Pair this with SEO-optimized Laravel eCommerce patterns when faceted navigation creates many thin URLs.

What URLs should a Laravel sitemap include and exclude?

Including every registered route is a common mistake. A sitemap should list URLs you want indexed—not every endpoint Laravel knows about. Apply explicit inclusion rules at the model and route level.

Include these URL types

  1. Public marketing pages: home, about, contact, pricing, service landing pages.
  2. Indexable detail pages: blog posts, product pages, lawyer profiles, itinerary pages—each with a stable slug and 200 HTTP status.
  3. Paginated archives only when paginated URLs carry unique value; otherwise canonicalise to page one and omit pages 2+.
  4. Localised URLs when you ship hreflang alternates for Nepali and English content.
  5. Media sitemap entries for video landing pages if video is core to rankings; see video SEO optimisation for metadata alignment.

Exclude these URL types

  • Authentication flows: login, register, password reset, email verification.
  • Authenticated areas: dashboards, checkout steps behind session, client portals on apps like Mijar Law Associates.
  • Internal search results, sort/filter query strings, and tracking parameters (?utm_*, session IDs).
  • Duplicate paths: trailing-slash variants, uppercase slugs, print views, API routes.
  • Noindex pages: thank-you screens, cart, paginated duplicates already consolidated by canonical tags.
  • Soft-deleted or draft records—scope Eloquent with ->published() or ->where('status', 'published').
URL Inclusion Decision TreeCandidate URLReturns 200?Not redirectIndexable?No noindex tagCanonical?Self-referencingExcludeAuth, filters, dupesIncludeAdd to sitemap.xmlValidate with Screaming Frog or GSC URL inspection before bulk submit
Laravel sitemap URL rules: only add self-canonical, indexable 200 responses; exclude auth, parameters, and duplicates.

Align sitemap URLs with your canonical tags and on-page SEO checklist. If a blog tag page is noindex, do not list it. If /services/web-design canonicalises to /services, list only the canonical target. Mismatch between sitemap and canonical is a recurring issue I've fixed on legal-tech portals where paginated practice-area archives leaked into XML.

Set accurate lastmod values

Google treats misleading lastmod dates as noise. Bind lastmod to real content changes—updated_at on meaningful edits, not automatic touch() on unrelated saves. For mostly static pages, omit lastmod or refresh only when copy changes. Use your JSON formatter to inspect API payloads when debugging CMS-driven lastmod sync.

How do you connect a Laravel sitemap to robots.txt and Search Console?

A generated sitemap nobody can find helps nothing. Wire discovery through robots.txt, internal links, and Google Search Console.

robots.txt declaration

Publish public/robots.txt or serve it dynamically. Point crawlers to your sitemap index:

User-agent: *
Disallow: /admin/
Disallow: /api/
Disallow: /login
Disallow: /cart

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

Keep robots rules aligned with your broader crawl strategy in the robots.txt complete guide. Disallowing a path in robots does not remove it from index if external links exist; excluding it from the sitemap reduces crawl priority signals.

Search Console submission and monitoring

After deploy, open Google Search Console → Sitemaps → submit https://yourdomain.com/sitemap.xml. Monitor:

  • Discovered URLs vs expected count from your database.
  • Indexed lag on new sections—common after large migrations.
  • Errors for 404s, redirects, or blocked URLs accidentally included.

For staging environments, block indexing entirely and skip GSC submission. A sitemap on a staging subdomain that leaks into production DNS has caused accidental indexation more than once in my deployment work.

Sitemap Update StrategiesManual EditsStale within daysHuman error on slugsMissed new routesAutomated GenerateScheduler + deploy hookEvent-driven on publishMatches live DB stateRecommended: deploy hook + nightly reconcilephp artisan sitemap:generate after symlink swapQueue regen on model publish/unpublish events
Automated Laravel sitemap generation beats manual XML edits for SEO accuracy on content-heavy applications.

How do you automate sitemap updates in a Laravel deployment pipeline?

On sister sites I maintain with Deployer 7 and GitLab CI, sitemap generation runs as a post-deploy task so the live symlink always serves fresh XML. Hook it after artisan migrate and cache clears:

task('artisan:sitemap', function () {
    run('{{bin/php}} {{release_path}}/artisan sitemap:generate');
});

after('deploy:symlink', 'artisan:sitemap');

For high-churn content—news, deals on Ajako Deal, or booking inventory on Adventure Himalaya Nepal—also dispatch regeneration from model observers:

class PostObserver
{
    public function saved(Post $post): void
    {
        if ($post->wasChanged(['slug', 'status', 'published_at'])) {
            GenerateSitemapJob::dispatch()->delay(now()->addMinutes(2));
        }
    }
}

Debounce queued jobs so ten rapid edits do not spawn ten full rebuilds on a 40,000-URL catalog. A two-minute delay coalesces bursts.

Performance and caching considerations

Generating a massive sitemap synchronously on every HTTP request will tank response times. Precompute to disk or object storage and serve statically. If you front the site with a CDN, as described in Cloudflare CDN setup, purge /sitemap.xml after regeneration or set a modest TTL (15–60 minutes). MySQL 9.7 and PostgreSQL 18 both handle the underlying queries fine when you select() only needed columns and chunk results—patterns that mirror database best practices in Laravel.

Multi-language and Nepali content

Law-firm and government-adjacent sites often ship Nepali and English URLs. Emit hreflang inside HTML; in sitemaps, list each language URL as its own entry pointing to the same content cluster, or use dedicated language sitemaps referenced from a sitemap index. When dates display in Bikram Sambat for users, keep sitemap lastmod in W3C datetime (ISO 8601) format—crawlers expect Gregorian timestamps. Content tools like the Nepali Unicode converter help editors publish UTF-8 clean copy that matches indexed URLs.

Deploy Pipeline + Sitemap RegenerationGit PushGitLab CIBuildComposer 2.10ReleaseDeployer 7SymlinkZero downtimephp artisan sitemap:generateWrites public/sitemap.xml on live pathPurge CDN cachesitemap.xml URLGSC resubmitIf URL set changed
Post-deploy sitemap generation in a Laravel CI/CD pipeline keeps XML aligned with migrations and new routes.

Testing before production

Validate output against Google's guidelines and the official Google Search Central sitemap documentation. Locally:

php artisan sitemap:generate
xmllint --noout public/sitemap.xml
grep -c '<loc>' public/sitemap.xml

Cross-check a sample of <loc> values with curl -I for 200 status and self-referencing canonical headers. On apps using Meilisearch, ensure deindexed records disappear from the sitemap on the next run—search index and sitemap should share the same publish scope.

Key Takeaways

  • Generate sitemap.xml from published Eloquent records and named routes with spatie/laravel-sitemap, not hand-maintained XML.
  • Include only self-canonical, indexable 200 URLs; exclude auth, filters, API paths, and noindex pages.
  • Declare the sitemap in robots.txt, submit once in Search Console, and monitor discovered vs indexed counts.
  • Run sitemap:generate on a schedule and after deploy; debounce event-driven rebuilds on busy catalogs.
  • Split into a sitemap index when you approach 50,000 URLs or 50 MB uncompressed.
  • Keep lastmod honest—bind it to real content changes, not automatic timestamp touches.

People Also Ask

Does Laravel have a built-in sitemap generator?

No. Laravel 12 and 13 ship routing, scheduling, and queues—but not XML sitemap generation. Use a package such as Spatie's or a custom Artisan command that writes to public/sitemap.xml. The Laravel task scheduling docs cover how to automate the command.

Where should sitemap.xml live in a Laravel project?

Place the generated file in public/sitemap.xml so it is available at https://yourdomain.com/sitemap.xml. Do not store it in storage/ unless you add a dedicated route to stream it—static public files are faster and easier for crawlers and CDNs.

How often should you regenerate a Laravel sitemap?

Nightly regeneration is enough for most brochure and legal-content sites. Daily or event-driven updates suit news, eCommerce, and marketplaces where products or listings change frequently. Always regenerate after deployments that add routes or migrations affecting slugs.

Can a sitemap fix pages that are not indexed?

A sitemap helps discovery; it does not guarantee indexation. Thin content, duplicate clusters, manual penalties, and server errors still block indexing. Fix canonicalisation, content quality, and crawl errors first—then ensure the URL appears in your Laravel-generated sitemap.

Ship a sitemap that matches your Laravel app—not a static afterthought

Laravel sitemap generator SEO best practices boil down to treating XML as compiled output from your real indexation rules: the same scopes that drive public menus, canonical tags, and published flags. Automate generation, keep robots.txt honest, validate after every major release, and split large catalogs before you hit protocol limits. If you want this wired correctly into a new build or an existing Laravel 12/13 codebase—alongside technical SEO and Laravel web developmentcontact us to review your URL architecture and deploy pipeline. For related reading, see modern Laravel architecture, zero-downtime migrations, and the Notary Nepal portfolio for a legal-tech site where crawl hygiene directly affects lead quality.

Frequently Asked Questions

No. Laravel 12 and 13 provide routing, scheduling, and queues but not XML sitemap generation. Use spatie/laravel-sitemap or a custom Artisan command writing to public/sitemap.xml.

For most Laravel 12 and 13 apps on PHP 8.3+, spatie/laravel-sitemap is the pragmatic default. It integrates with Eloquent, supports sitemap indexes, chunk-friendly generation, and video or image tags. Custom XML builders suit tiny static sites but are error-prone around escaping, charset, and the 50,000 URL limit. WordPress CMS plugins are irrelevant for custom Laravel routing. I've used Spatie alongside sluggable and media library on production apps where sitemap accuracy must track live slugs and publish flags without manual XML edits.

Generate it to public/sitemap.xml so crawlers reach https://yourdomain.com/sitemap.xml. Static public files are faster than streaming from storage/ via a route.

Install spatie/laravel-sitemap with Composer 2.10, publish its config, then create an Artisan command such as sitemap:generate. Assemble URLs from named routes and Eloquent models scoped to published or indexable records. Set priority, changeFrequency, and lastmod from updated_at. Write output with writeToFile(public_path('sitemap.xml')). Register the command in routes/console.php and schedule it nightly with withoutOverlapping and onOneServer for multi-server setups. Treat sitemap.xml as compiled output from your indexation rules, not a hand-edited Blade view.

Include public marketing pages, indexable detail pages with stable slugs returning HTTP 200, and localised URLs when you ship hreflang. Exclude login, register, dashboards, checkout behind sessions, internal search and filter query strings, API routes, duplicate trailing-slash paths, and anything marked noindex. Scope Eloquent with published() or where status is published. List only self-canonical URLs that match your canonical tags. Mismatch between sitemap and canonical is a recurring SEO bug I've fixed on legal-tech portals where paginated archives leaked into XML.

Nightly suits most brochure and legal-content sites. Daily or event-driven regeneration fits news, eCommerce, and marketplaces. Always regenerate after deploys that change routes or slugs.

Add Sitemap: https://example.com/sitemap.xml to public/robots.txt alongside Disallow rules for admin, API, login, and cart paths. After deploy, submit the sitemap URL once in Google Search Console under Sitemaps. Monitor discovered versus expected URL counts, indexing lag after migrations, and errors for 404s or blocked URLs. Block staging entirely from indexing and skip Search Console submission there. A staging sitemap leaking through DNS has caused accidental indexation more than once in my deployment work.

On Deployer 7 and GitLab CI setups I maintain, add a post-deploy task after deploy:symlink that runs php artisan sitemap:generate on the release path. For high-churn catalogs, dispatch regeneration from model observers when slug, status, or published_at changes, but debounce queued jobs with a two-minute delay so rapid edits do not trigger ten full rebuilds on a 40,000-URL site. Schedule nightly generation at off-peak hours such as 02:15 as a baseline safety net alongside event-driven updates.

Split when you approach 50,000 URLs or a 50 MB uncompressed file, per the Sitemaps protocol. Use Spatie's SitemapIndex to reference separate files such as pages.xml, blog.xml, and products.xml, with the index at public/sitemap.xml. E-commerce builds and directory platforms benefit because regenerating one section does not block crawl signals for the entire catalog. Pair split sitemaps with SEO patterns that keep faceted navigation thin URLs out of the index rather than listing every filter combination.

Bind lastmod to real content changes using updated_at on meaningful edits, not automatic touch() calls on unrelated saves. Google treats misleading lastmod as noise. For mostly static pages, omit lastmod or refresh only when copy actually changes. When debugging CMS-driven sync, inspect API payloads to confirm timestamps match editorial updates. Display Bikram Sambat dates in the UI if needed, but sitemap lastmod must stay in W3C ISO 8601 Gregorian format for crawlers.

No. A sitemap helps discovery; it does not guarantee indexation. Thin content, duplicate clusters, manual penalties, and server errors still block indexing. Fix canonicalisation, content quality, and crawl errors first, then ensure the URL appears in your Laravel-generated sitemap with a 200 response and matching canonical header. Treat the sitemap as a signal of what you want crawled, not an override for quality or technical problems elsewhere on the site.

Run php artisan sitemap:generate locally, validate XML with xmllint --noout public/sitemap.xml, and count loc entries with grep. Cross-check sample URLs using curl -I to confirm HTTP 200 and self-referencing canonical headers. Compare discovered URL counts against your database expectations. If you use Meilisearch or similar, ensure deindexed records share the same publish scope as the sitemap query so removed content disappears on the next generation run.

No. Generating a massive sitemap synchronously on each request will tank response times. Precompute to disk and serve the static file from public/sitemap.xml. If you front the site with a CDN, purge /sitemap.xml after regeneration or set a modest TTL between 15 and 60 minutes. Select only needed columns and chunk Eloquent results with chunkById(500) to keep MySQL 9.7 or PostgreSQL 18 queries efficient during nightly builds on large catalogs.

Law-firm and government-adjacent sites often ship Nepali and English URLs. Emit hreflang in HTML, and in sitemaps list each language URL as its own entry within the same content cluster, or use dedicated language sitemaps referenced from a sitemap index. Keep sitemap lastmod in ISO 8601 regardless of Bikram Sambat display in the UI. Ensure UTF-8 clean copy in indexed URLs so Nepali content matches what crawlers fetch and what appears in Search Console.

spatie/laravel-sitemap offers active maintenance, Eloquent integration, sitemap index support, chunk-friendly generation, and video or image tag helpers, ideal for most Laravel 12 and 13 apps. A custom XML builder gives zero dependencies and full control but makes it easy to get charset escaping, lastmod formatting, or 50k URL limits wrong. Hand-maintained Blade views are not sitemaps. XML must conform to the Sitemaps protocol and reflect live route and model data through a generator command wired into your deployment lifecycle.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: