Sitemap (Optional)
Serve /sitemap.xml from Laravel using the five public routes from Page Stubs. Keep an explicit URL list; no crawler, package, generated file, or scheduled task is needed.
Sitemap Controller
bash
php artisan make:controller SitemapController --invokable --no-interactionFile: app/Http/Controllers/SitemapController.php
php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Response;
class SitemapController extends Controller
{
public function __invoke(): Response
{
return response()
->view('sitemap', [
'urls' => [
route('home'),
route('about'),
route('contact'),
route('privacy'),
route('terms'),
],
])
->header('Content-Type', 'application/xml; charset=UTF-8');
}
}Keep only public, canonical, indexable pages. Leave login, registration, Dashboard, and Settings out. Update the list when adding public pages; sitemap omission is not access control.
XML View
Return a standalone Blade view, not an Inertia page or the application shell.
File: resources/views/sitemap.blade.php
bash
touch resources/views/sitemap.blade.phpblade
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@foreach($urls as $url)
<url>
<loc>{{ $url }}</loc>
</url>
@endforeach
</urlset>Keep the escaped Blade output so query-string ampersands become &. The controller sets the XML content type. No lastmod is included; add it only when a real content modification date is available.
Route
Add the SitemapController import and named GET route outside the authenticated group. Keep the existing public pages, contact POST route, Dashboard, and settings routes.
File: routes/web.php
php
<?php
use App\Http\Controllers\ContactFormController;
use App\Http\Controllers\SitemapController;
use Illuminate\Support\Facades\Route;
use Laravel\Fortify\Features;
Route::inertia('/', 'Home', [
'canRegister' => Features::enabled(Features::registration()),
])->name('home');
Route::inertia('about', 'About')->name('about');
Route::inertia('contact', 'Contact')->name('contact');
Route::inertia('privacy', 'Privacy')->name('privacy');
Route::inertia('terms', 'Terms')->name('terms');
Route::get('sitemap.xml', SitemapController::class)->name('sitemap');
Route::post('contact', [ContactFormController::class, 'store'])
->middleware('throttle:5,1')
->name('contact.store');
Route::middleware(['auth', 'verified'])->group(function () {
Route::inertia('dashboard', 'Dashboard')->name('dashboard');
});
require __DIR__.'/settings.php';Do not keep a generated public/sitemap.xml alongside this route: the web server may serve that file instead. The Spatie Sitemap guide is an alternative, not an additional setup step.
Robots
Create the robots file:
bash
touch public/robots.txtAllow all crawlers and add the sitemap URL with the real production domain:
File: public/robots.txt
text
User-agent: *
Disallow:
Sitemap: https://example.com/sitemap.xmlKeep APP_URL set to the canonical production URL. For this HTTP route, URL generation also uses the request host and scheme; check HTTPS/proxy configuration if the output is wrong.
Sitemap Tests
Check guest access, the XML content type and namespace, the exact public URL list, and escaping without changing URL values. PHP's SimpleXML extension must be enabled.
File: tests/Feature/SitemapTest.php
bash
touch tests/Feature/SitemapTest.phpphp
<?php
test('guests receive a valid XML sitemap containing only the public pages', function () {
$response = $this->get(route('sitemap'));
$response->assertOk()
->assertHeader('Content-Type', 'application/xml; charset=UTF-8');
$sitemap = simplexml_load_string($response->getContent());
expect($sitemap)->toBeInstanceOf(SimpleXMLElement::class);
expect($sitemap->getName())->toBe('urlset');
expect($sitemap->getDocNamespaces())->toBe([
'' => 'http://www.sitemaps.org/schemas/sitemap/0.9',
]);
expect(array_map(
fn (SimpleXMLElement $location): string => (string) $location,
$sitemap->xpath('//*[local-name()="loc"]'),
))->toBe([
url('/'),
url('/about'),
url('/contact'),
url('/privacy'),
url('/terms'),
]);
});
test('sitemap URLs are XML escaped without changing their values', function () {
$url = 'https://example.com/search?category=books&sort=newest';
$content = view('sitemap', ['urls' => [$url]])->render();
$sitemap = simplexml_load_string($content);
expect($sitemap)->toBeInstanceOf(SimpleXMLElement::class);
expect((string) $sitemap->url->loc)->toBe($url);
});Run in the Laravel application:
bash
php artisan test --compact tests/Feature/SitemapTest.php
php artisan route:list --path=sitemap- Open
/sitemap.xmlas a guest: XML with exactly the five public URLs, no application layout or login redirect. - Check that every URL uses the production host and HTTPS and opens the intended page; do not publish
.testor localhost URLs. - Open
/robots.txtand confirm its sitemap URL matches the live endpoint.
Commit
bash
git add -A
git commit -m '09 - Sitemap'