Every public route emits a per-page SEO block — title,
description, canonical URL, Open Graph card, Twitter card, and
JSON-LD structured data — without any per-page work in the
React layer. Buyers running a white-label install inherit the
same machinery; replace the brand name in
/settings/branding and the meta block follows.
App\Support\SeoMeta::ROUTE_DEFAULTS. Tokens like
{brand} are interpolated at render time so a
renamed install never leaks the source product name into the
meta block.
<link rel="canonical">,
anchored on config('app.url') + the route path.
Search engines never have to guess which variant is the
master.
og:type, og:site_name,
og:title, og:description,
og:url, og:image, plus the
twitter:card="summary_large_image" companion.
The OG image defaults to public/og-image.png;
drop your own in there to override.
/admin, /app,
/api/, /settings, and the auth
flows; references the sitemap so crawlers find it on first
sniff.
Two places to touch when adding a new public page:
SeoMeta::ROUTE_DEFAULTS
with title / description /
path. Use {brand} for any place
the install's name should appear.
return Inertia::render('your/page', [
// ... your props
'seo' => SeoMeta::for('your-route-key'),
]);
The Inertia root layout
(resources/views/app.blade.php) reads
props.seo and emits the meta block automatically.
No per-page Blade work needed.
Need to vary the description per row (e.g. a per-version changelog page)? Pass overrides:
'seo' => SeoMeta::for('changelog.show', [
'title' => "v1.1.0 — what's new in {brand}",
'description' => "Released " . $entry->released_at_human . " — " . $entry->summary,
])
Drop a 1200×630 PNG (or JPG) at public/og-image.png.
Buyers running their own install can swap the file directly via
SFTP or the deploy host's file manager. The
SeoMeta resolver checks the file exists at render
time; if it doesn't, the meta block silently omits
og:image and twitter:image rather than
pointing at a 404.
The sitemap is cached for 1 hour under
seo:sitemap.xml. To force a refresh after publishing
a long-awaited changelog entry:
php artisan tinker --execute 'cache()->forget("seo:sitemap.xml");'
Crawler traffic on a busy install would otherwise be O(crawl rate) database hits; the cache flattens it to one rebuild per hour per node.
| Route prefix | Why excluded |
|---|---|
/admin/* |
Platform-admin surface. Tenant-aware data. |
/app/* |
Workspace dashboard. Auth-required, workspace-scoped. |
/api/* |
API surface — JSON, not for crawlers. |
/login, /register, password reset |
Auth flows — no SEO value, nothing for a crawler to index. |
/settings/* |
Auth-required user / platform settings. |