Laravel SEO: Blade or Inertia first, then titles, slugs, 301s, canonicals and a sitemap

How a Laravel 13 site gets its HTML to crawlers, and the six settings the framework leaves for you to write.

Published by AI Knows Us · Updated 21 October 2026

Laravel SEO comes down to one decision and a short list of settings. The decision is how your pages render: Blade and Livewire views are built on the server, so a search engine receives finished HTML, while the React, Vue and Svelte starter kits use Inertia, which needs server-side rendering switched on before crawlers see your text. The settings are yours to write: a unique title and meta description per page, slug URLs, 301 redirects for moved pages, a canonical tag that uses the right scheme behind a load balancer, a sitemap and cache headers. Everything below comes from the Laravel 13 documentation and the Inertia 3 documentation, read on 22 October 2026. Laravel 13 was released on 17 March 2026.

Blade or Inertia: the choice that decides most of it

Laravel itself does not stop a page from ranking. What matters is whether the first HTML response already holds the words you want found. Google's JavaScript SEO guide says crawling and parsing the HTML "works well" for server-side rendered pages, and adds that "server-side or pre-rendering is still a great idea" because it is faster for users and crawlers and "not all bots can run JavaScript".

How the page is builtWhat a crawler gets firstWhat to do
Blade views, or the Livewire starter kitFull HTML from the serverAdd titles, descriptions and canonicals in the layout
Inertia (React, Vue or Svelte kit) with SSR offA shell, with content filled in by JavaScriptTurn SSR on for any page you want indexed
Inertia with SSR onFull HTML rendered by a Node processUse the Head component and keep the SSR server running

Laravel's starter kit docs say the React, Svelte and Vue kits are compatible with Inertia SSR, and you build the SSR bundle with npm run build:ssr. Inertia's own docs say its SSR server needs Node.js 22 or higher on your server. Laravel's Vite docs give php artisan inertia:start-ssr as the command to start it. If you plan a content-heavy marketing site, a Blade or Livewire front end gives you server HTML with no extra process to keep alive.

Titles and meta descriptions in a Blade layout

Put the head tags in your main layout once, and let each view fill them in. Blade's @yield and @section directives are the simplest way.

<!-- resources/views/layouts/app.blade.php -->
<head> <title>@yield('title', config('app.name'))</title> <meta name="description" content="@yield('description')"> <link rel="canonical" href="{{ url()->current() }}">
</head> <!-- resources/views/posts/show.blade.php -->
@extends('layouts.app')
@section('title', $post->title)
@section('description', $post->excerpt)

Write each title for the search it should win, and keep each description specific to its page. Blade escapes {{ }} output, so a quote mark in a post title will not break the tag.

Inertia pages: the Head component and head-key

Inertia's docs explain that an Inertia page renders inside the document body, so it cannot write to the head on its own. Inertia ships a <Head> component for React and Vue that sets the title and meta tags. The Svelte adapter does not include it, because Svelte has its own <svelte:head>.

One detail catches people: Inertia renders only one <title>, but other tags stack. If your layout sets a default description and the page sets another, you get two. Give both the same head-key so only one is rendered.

import { Head } from '@inertiajs/react' <Head title={post.title}> <meta head-key="description" name="description" content={post.excerpt} />
</Head>

Inertia also accepts a title callback in createInertiaApp, which is the place to add your site name to every title. If your Vue app runs on its own rather than inside Laravel, our guide to Vue and Nuxt SEO covers the same rendering choice from the Vue side.

Slug URLs with route model binding

Readable URLs such as /posts/laravel-seo-checklist are easy to build. Laravel can resolve a model by a column other than id directly in the route, and the Str::slug helper turns a title into a URL-friendly slug when you save the post.

use App\Models\Post; Route::get('/posts/{post:slug}', function (Post $post) { return view('posts.show', ['post' => $post]);
});

If every route for a model should use the slug, Laravel 13 lets you put the #[RouteKey('slug')] attribute on the model instead. When no record matches, Laravel returns a 404. If you would rather send visitors from an old slug somewhere useful, the route's missing method takes a closure that can redirect.

Redirects and status codes

This is the setting most often wrong on Laravel sites. The docs say Route::redirect returns a 302 by default. A 302 tells search engines the move is temporary. For a page that has moved for good, use a 301.

Route::permanentRedirect('/old-pricing', '/pricing'); // the same thing, with the status code passed in
Route::redirect('/old-pricing', '/pricing', 301);

Two more status code habits help:

  • Keep 404s real. Route::fallback runs when nothing else matches. If you use it, make it return a 404 status, not a friendly page with a 200, or every mistyped URL becomes a thin indexable page.
  • Trailing slashes are handled on Apache. The default public/.htaccess in the Laravel application skeleton redirects a trailing slash URL to the version without it, with a 301, unless the path is a real folder. On Nginx, add an equivalent rule yourself so each page has one address.

Canonical tags, and the load balancer problem

The url() helper builds URLs from the scheme and host of the current request. Behind a load balancer that ends TLS, the request reaching Laravel can arrive over plain HTTP, and the docs warn that your app may then fail to generate HTTPS links. That turns your canonical tag into an http:// address, which points search engines at the wrong version of every page.

The fix is the TrustProxies middleware, configured in bootstrap/app.php. You list the proxy addresses, or use '*' on a cloud load balancer whose addresses you do not know. Check the result by viewing the source of a live page and reading the canonical.

->withMiddleware(function (Middleware $middleware): void { $middleware->trustProxies(at: '*');
})

Watch query strings too. Laravel's paginator puts the page number in a page query string, and url()->current() leaves the query string out. That suits most filters and tracking tags. For paginated archives, give page 2 and onwards a canonical that includes the page number, so Google indexes the posts listed there rather than treating every page as page 1.

Sitemap and robots.txt

The Laravel skeleton ships a public/robots.txt that allows every crawler: User-agent: * followed by an empty Disallow:. Add a Sitemap: line pointing at your sitemap's full URL, and block only what you truly want kept out, such as admin paths.

For the sitemap itself, one package built for the job is spatie/laravel-sitemap. Its README says it can crawl your site to build the sitemap, take URLs you add by hand with a last-modified date, or do both. For a site whose pages live in the database, building it by hand from your models is usually cleaner than a crawl, because you control exactly which URLs appear.

use Spatie\Sitemap\Sitemap;
use Spatie\Sitemap\Tags\Url; $sitemap = Sitemap::create();
foreach (Post::published()->get() as $post) { $sitemap->add(Url::create(route('posts.show', $post)) ->setLastModificationDate($post->updated_at));
}
$sitemap->writeToFile(public_path('sitemap.xml'));

Run that from a scheduled command so the file stays current, then submit the sitemap URL in Search Console.

Speed and caching

Laravel includes a cache.headers middleware that sets Cache-Control for a group of routes. Add etag to the directives and Laravel sets an ETag from a hash of the response. Public pages that rarely change, such as legal pages and older articles, are good candidates. The Vite docs say a production build versions your assets, so CSS and JavaScript can be cached for a long time without stale files.

Check what a crawler receives

Fetch a page with curl and search the response for its heading and first paragraph. On a Blade site they will be there. On an Inertia site, if you see only a root element and a JSON blob, SSR is not running for that route. Then open the URL Inspection tool in Search Console and compare the rendered HTML with what you expect. Do this after every deploy that touches the SSR server or the proxy settings, because those are the two things that fail silently.

Questions about Laravel SEO

Is Laravel good for SEO?

Yes. Blade renders full HTML on the server, routes give you clean URLs, and you control every status code and header. The work is in the settings above, not in the framework.

Does Inertia hurt SEO in Laravel?

Only with SSR off. Without it, the first response is a shell and your text depends on the crawler running JavaScript. Turn SSR on with npm run build:ssr and keep the Node process running.

Why does my Laravel redirect show as 302?

Route::redirect returns 302 by default. Use Route::permanentRedirect, or pass 301 as the third argument, for pages that moved for good.

Why does my canonical tag say http instead of https?

Your app sits behind a proxy that ends TLS and Laravel does not trust it yet. Configure trustProxies in bootstrap/app.php.

How do I add a sitemap to Laravel?

Use a package such as spatie/laravel-sitemap, or write a route that returns XML built from your models. Rebuild it on a schedule and list it in robots.txt.

A rendered page is also a quotable page

Server HTML, clean slugs and honest status codes help more than Google. AI assistants that fetch a page while answering a question read the same first response, and a Laravel site that serves full text is easy for them to quote. Whether an assistant actually recommends your product when a buyer asks for one is a separate question that no rank tracker answers. Measuring that, engine by engine, is the job of a newer kind of tool, and AI Knows Us is built for it.

Read next

Laravel SEO packages compared: Laravel Head, SEOTools, laravel-seo and the rest, by what each renders and which Laravel it needsSix head packages and three helpers, with the Laravel versions each supports, read from Packagist and their own docs on 22 October 2026.Simon-Kucher pricing strategy, what the firm publishes about its own method and what a Singapore buyer should ask before briefing itThe firm publishes its approach, its scale and four named service lines, and publishes no fee, so the useful work before any call is reading what it claims and deciding which decision you are buying.International pricing strategies for a Singapore company, and the corridor that stops one market undercutting anotherA converted home price is the one answer that is almost always wrong, so set a price per market and keep the gaps narrower than the cost of moving a unit between them.Premium pricing strategy for a Singapore business, and the fence that makes the higher number holdCharging permanently more than the credible alternatives works when the buyer can name what the extra money buys, and the published Singapore writing ladder shows exactly how that is built.Red Antler publishes no price, so here is what its own site does publish and how to cost the same scope from SingaporeRead on 29 October 2026, three pages of redantler.com carry a complete deliverable list, three business stages and not one figure about what any of it costs.Hiring a full service content agency, the five stages a retainer has to cover and the two handoffs where the money leaksMost content retainers buy production and quietly leave strategy and distribution with you, so price the stages rather than the word count.

Or browse the whole archive.

See what AI says about you.

The first scan is free, it needs no account, and it asks the questions your buyers ask.

Free. No card. We ask 5 real buyer questions on 2 AI apps.

Already sure you want this? Start free. No card needed, and the free plan stays free. Pay only when you want the work done. Or talk to a person if you have a question first. Or read how we fix what it finds.