{"id":572,"date":"2025-09-01T11:19:16","date_gmt":"2025-09-01T11:19:16","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=572"},"modified":"2025-09-01T11:21:46","modified_gmt":"2025-09-01T11:21:46","slug":"how-to-build-an-xml-sitemap-generator-in-laravel","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/how-to-build-an-xml-sitemap-generator-in-laravel\/","title":{"rendered":"How to Build an XML Sitemap Generator in Laravel"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\"><strong>How to Build an XML Sitemap Generator in Laravel<\/strong><\/h1>\n\n\n\n<p>An XML sitemap tells search engines which pages to index and when they were last updated. Laravel makes it simple to <strong>generate a sitemap dynamically<\/strong> from your database. In this guide, we\u2019ll build a <strong>Laravel sitemap dynamic generate<\/strong> feature that covers posts, pages, and categories, outputs valid XML, supports toggling inclusion, and also shows how to export a <code>sitemap.xml<\/code> file and automatically regenerate it when new content is added.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Create the Sitemap Route and Controller<\/strong><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">SitemapController<\/span>;\n\nRoute::get(<span class=\"hljs-string\">'\/sitemap-preview.xml'<\/span>, &#91;SitemapController::class, <span class=\"hljs-string\">'index'<\/span>]);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here we expose the sitemap at <code>\/sitemap-preview.xml<\/code>. This controller-based version is ideal for testing or development, but in production you will serve a static <code>sitemap.xml<\/code> file instead (explained below).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Controllers\/SitemapController.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">Post<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">Page<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">Category<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">SitemapController<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Controller<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">index<\/span><span class=\"hljs-params\">()<\/span>\n    <\/span>{\n        $posts = Post::where(<span class=\"hljs-string\">'published'<\/span>, <span class=\"hljs-keyword\">true<\/span>)-&gt;get();\n        $pages = Page::where(<span class=\"hljs-string\">'published'<\/span>, <span class=\"hljs-keyword\">true<\/span>)-&gt;get();\n        $categories = Category::all();\n\n        <span class=\"hljs-keyword\">return<\/span> response()\n            -&gt;view(<span class=\"hljs-string\">'sitemap.index'<\/span>, compact(<span class=\"hljs-string\">'posts'<\/span>, <span class=\"hljs-string\">'pages'<\/span>, <span class=\"hljs-string\">'categories'<\/span>))\n            -&gt;header(<span class=\"hljs-string\">'Content-Type'<\/span>, <span class=\"hljs-string\">'application\/xml'<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The controller collects resources and renders XML via a Blade view. This is the core of the <em>Laravel sitemap dynamic generate<\/em> flow, but for production Google indexing we\u2019ll rely on a static file.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Blade View for Sitemap XML<\/strong><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">&lt;!-- resources\/views\/sitemap\/index.blade.php --&gt;\n<span class=\"hljs-meta\">&lt;?<\/span>xml version=<span class=\"hljs-string\">\"1.0\"<\/span> encoding=<span class=\"hljs-string\">\"UTF-8\"<\/span><span class=\"hljs-meta\">?&gt;<\/span>\n&lt;urlset xmlns=<span class=\"hljs-string\">\"http:\/\/www.sitemaps.org\/schemas\/sitemap\/0.9\"<\/span>&gt;\n  @<span class=\"hljs-keyword\">foreach<\/span>($posts <span class=\"hljs-keyword\">as<\/span> $post)\n    &lt;url&gt;\n      &lt;loc&gt;{{ url(<span class=\"hljs-string\">'\/posts\/'<\/span>.$post-&gt;slug) }}&lt;\/loc&gt;\n      &lt;lastmod&gt;{{ $post-&gt;updated_at-&gt;toAtomString() }}&lt;\/lastmod&gt;\n      &lt;changefreq&gt;weekly&lt;\/changefreq&gt;\n      &lt;priority&gt;<span class=\"hljs-number\">0.8<\/span>&lt;\/priority&gt;\n    &lt;\/url&gt;\n  @<span class=\"hljs-keyword\">endforeach<\/span>\n\n  @<span class=\"hljs-keyword\">foreach<\/span>($pages <span class=\"hljs-keyword\">as<\/span> $page)\n    &lt;url&gt;\n      &lt;loc&gt;{{ url(<span class=\"hljs-string\">'\/'<\/span>.$page-&gt;slug) }}&lt;\/loc&gt;\n      &lt;lastmod&gt;{{ $page-&gt;updated_at-&gt;toAtomString() }}&lt;\/lastmod&gt;\n      &lt;changefreq&gt;monthly&lt;\/changefreq&gt;\n      &lt;priority&gt;<span class=\"hljs-number\">0.6<\/span>&lt;\/priority&gt;\n    &lt;\/url&gt;\n  @<span class=\"hljs-keyword\">endforeach<\/span>\n\n  @<span class=\"hljs-keyword\">foreach<\/span>($categories <span class=\"hljs-keyword\">as<\/span> $category)\n    &lt;url&gt;\n      &lt;loc&gt;{{ url(<span class=\"hljs-string\">'\/categories\/'<\/span>.$category-&gt;slug) }}&lt;\/loc&gt;\n      &lt;lastmod&gt;{{ $category-&gt;updated_at-&gt;toAtomString() }}&lt;\/lastmod&gt;\n      &lt;changefreq&gt;weekly&lt;\/changefreq&gt;\n      &lt;priority&gt;<span class=\"hljs-number\">0.5<\/span>&lt;\/priority&gt;\n    &lt;\/url&gt;\n  @<span class=\"hljs-keyword\">endforeach<\/span>\n&lt;\/urlset&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The Blade view outputs valid XML with <code>loc<\/code>, <code>lastmod<\/code>, <code>changefreq<\/code>, and <code>priority<\/code> for each URL.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Export a Static <code>sitemap.xml<\/code> File<\/strong><\/h2>\n\n\n\n<p>The best practice is to generate a static <code>public\/sitemap.xml<\/code> file. It\u2019s served directly by your web server or CDN and is the file you\u2019ll submit to Google Search Console. Let\u2019s create an Artisan command that renders the XML and saves it:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Console\/Commands\/GenerateSitemap.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Console<\/span>\\<span class=\"hljs-title\">Commands<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Console<\/span>\\<span class=\"hljs-title\">Command<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">Post<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">Page<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">Category<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">File<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">GenerateSitemap<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Command<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">protected<\/span> $signature = <span class=\"hljs-string\">'sitemap:generate'<\/span>;\n    <span class=\"hljs-keyword\">protected<\/span> $description = <span class=\"hljs-string\">'Generate static sitemap.xml in public\/'<\/span>;\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handle<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">int<\/span>\n    <\/span>{\n        $posts = Post::where(<span class=\"hljs-string\">'published'<\/span>, <span class=\"hljs-keyword\">true<\/span>)-&gt;where(<span class=\"hljs-string\">'include_in_sitemap'<\/span>, <span class=\"hljs-keyword\">true<\/span>)-&gt;get();\n        $pages = Page::where(<span class=\"hljs-string\">'published'<\/span>, <span class=\"hljs-keyword\">true<\/span>)-&gt;get();\n        $categories = Category::all();\n\n        $xml = view(<span class=\"hljs-string\">'sitemap.index'<\/span>, compact(<span class=\"hljs-string\">'posts'<\/span>, <span class=\"hljs-string\">'pages'<\/span>, <span class=\"hljs-string\">'categories'<\/span>))-&gt;render();\n\n        File::put(public_path(<span class=\"hljs-string\">'sitemap.xml'<\/span>), $xml);\n        <span class=\"hljs-keyword\">$this<\/span>-&gt;info(<span class=\"hljs-string\">'sitemap.xml generated at public\/sitemap.xml'<\/span>);\n\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">self<\/span>::SUCCESS;\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This generates <code>public\/sitemap.xml<\/code>, which is now a static file. <strong>This is the file you should submit to Google Search Console<\/strong>. The dynamic version (<code>\/sitemap-preview.xml<\/code>) is helpful for debugging or previewing changes before exporting.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Auto-Regenerate on Content Changes<\/strong><\/h2>\n\n\n\n<p>Whenever a post, page, or category changes, you can dispatch a job to regenerate the sitemap so the static file stays fresh.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Jobs\/RegenerateSitemap.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Jobs<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Bus<\/span>\\<span class=\"hljs-title\">Queueable<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Contracts<\/span>\\<span class=\"hljs-title\">Queue<\/span>\\<span class=\"hljs-title\">ShouldQueue<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Bus<\/span>\\<span class=\"hljs-title\">Dispatchable<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Queue<\/span>\\<span class=\"hljs-title\">InteractsWithQueue<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Queue<\/span>\\<span class=\"hljs-title\">SerializesModels<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Artisan<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">RegenerateSitemap<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">ShouldQueue<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Dispatchable<\/span>, <span class=\"hljs-title\">InteractsWithQueue<\/span>, <span class=\"hljs-title\">Queueable<\/span>, <span class=\"hljs-title\">SerializesModels<\/span>;\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handle<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span>\n    <\/span>{\n        Artisan::call(<span class=\"hljs-string\">'sitemap:generate'<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Attach this job to model events like <code>saved<\/code> and <code>deleted<\/code> to ensure your sitemap is always current. For high traffic, debounce by adding a small delay.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Wrapping Up<\/strong><\/h2>\n\n\n\n<p>You built a complete sitemap system in Laravel: dynamic preview via a controller, a static <code>sitemap.xml<\/code> exporter, and automatic regeneration with jobs and scheduling. The <strong>Laravel sitemap dynamic generate<\/strong> preview is useful for testing, but the static <code>sitemap.xml<\/code> is what you should serve to crawlers and submit to Google Search Console.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What\u2019s Next<\/strong><\/h2>\n\n\n\n<p>Continue strengthening your Laravel SEO setup with these guides:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/blog\/laravel-seo-guide-optimizing-meta-slugs-and-sitemaps\">Laravel SEO Guide: Optimizing Meta, Slugs, and Sitemaps<\/a><\/li>\n<li><a href=\"\/blog\/how-to-generate-seo-friendly-urls-and-slugs-in-laravel\">How to Generate SEO-Friendly URLs and Slugs in Laravel<\/a><\/li>\n<li><a href=\"\/blog\/creating-json-ld-structured-data-in-laravel-for-seo\">Creating JSON-LD Structured Data in Laravel for SEO<\/a><\/li>\n<\/ul>\n\n","protected":false},"excerpt":{"rendered":"<p>How to Build an XML Sitemap Generator in Laravel An XML sitemap tells search engines which pages to index and when they were last updated. Laravel makes it simple to generate a sitemap dynamically from your database. In this guide, we\u2019ll build a Laravel sitemap dynamic generate feature that covers posts, pages, and categories, outputs [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":579,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[96,119,109,117,118],"class_list":["post-572","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-blade","tag-controller","tag-seo","tag-sitemap","tag-xml"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/572","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/comments?post=572"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/572\/revisions"}],"predecessor-version":[{"id":575,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/572\/revisions\/575"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/579"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=572"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=572"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=572"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}