archtechx/laravel-seo
ArchTech's Laravel SEO package gives your app a simple, fluent API for managing meta tags, titles, descriptions, Open Graph and Twitter card data, canonical URLs and robots directives, so every page renders clean, customizable SEO tags without extra boilerplate.
Install the package: composer require archtechx/laravel-seo.
Publish the config: php artisan vendor:publish --tag="seo-config". This creates config/seo.php, where you set the site name, site URL, default title/description/image, and the templates used to compose titles (e.g. appending the site name, or falling back to parent defaults).
If you want per-record SEO (blog posts, products, landing pages), publish and run the migrations, then add the HasSEO trait to the relevant Eloquent models. The trait wires up a polymorphic relationship so metadata lives in its own table rather than in columns on your content tables.
Drop <x-seo::meta /> into your main layout's <head>. This one component renders the <title>, meta description, canonical URL, Open Graph tags, Twitter card tags, and any JSON-LD you've attached — nothing else needs to be in the head for basic use.
First use case: a blog post. Add use HasSEO; to Post, then in your controller (or a view composer):
$post->seo()
->title($post->title)
->description($post->excerpt)
->image($post->cover_url);
View-source the page — the tags should render with no further wiring.
Where to look first: config/seo.php for defaults and templating, the SeoData class for the full fluent API, and the package's published views if you need to understand exactly which tags are emitted.
Site defaults plus per-page overrides. Set sensible defaults in config/seo.php (site name, image fallback, title template). Pages that need something specific override only the fields they care about — you should never be forced to re-specify og:site_name or the fallback image on every page.
Two flavours of SEO, pick per route. Persistent, model-backed SEO (HasSEO + ->seo()) is right for content that editors manage or that changes per record. Ephemeral, request-scoped SEO (Seo::make()->title(...)) is right for static pages, landing pages, and search results where there's no meaningful database row to attach metadata to.
Render once, in the layout. Put <x-seo::meta /> in your single base layout. Per-page data flows in from the controller/model; the component handles the rest. This keeps tag ordering consistent and avoids duplicate-tag bugs.
Templating for titles. Use the config title template for the common "Page Title — Site Name" pattern rather than string-concatenating in every controller. Templates keep the separator and fallback behaviour in one place.
Eager load the relationship. On index/listing pages that iterate models, add ->with('seo') (or the equivalent relationship name) to the query. Without it you'll get an N+1 for every card.
Structured data. For articles and products, attach JSON-LD alongside the meta tags so @type, headline, author, and image are emitted from the same source of truth as your OG tags.
Testing. Assert rendered output rather than internal state — $response->assertSee('<meta property="og:title"', false) catches regressions in the component or template config that unit tests on SeoData would miss.
php artisan config:cache has been run, edits to config/seo.php are ignored until you clear and re-cache. This is the single most common "my defaults aren't applying" cause.php artisan migrate, ->seo() will fail with a missing-table error that's easy to misread as a package bug.<title> or hand-written OG tags from your layout when you add the component, or you'll ship two of everything.<head>; rendering it in <body> will usually still work in browsers but breaks validators and some crawlers.laravel/framework constraint and re-run the test suite — the bump is additive, but validate your Blade layouts still render the component unchanged.intervention/image explicitly in composer.json before upgrading, otherwise Composer may resolve a version your image code doesn't expect. Check composer why intervention/image after the bump to confirm what pulled it in.SeoData is the natural place to put app-specific helpers (e.g. a ->article(Post $post) shortcut) rather than repeating ten chained calls per controller.How can I help you explore Laravel packages today?