ralphjsmit/laravel-seo
Out-of-the-box SEO for Laravel models: automatically generates title, meta, OpenGraph, Twitter, structured data, robots, alternates and favicon tags. Store per-model SEO or derive it dynamically, then render everything with a simple seo()->for($model) call.
Installation:
composer require ralphjsmit/laravel-seo
php artisan vendor:publish --tag="seo-migrations"
php artisan vendor:publish --tag="seo-config"
php artisan migrate
Configure:
Edit config/seo.php to set site-wide defaults (e.g., site_name, favicon, robots).
First Use Case:
Add the HasSEO trait to a model (e.g., Post):
use RalphJSmit\Laravel\SEO\Support\HasSEO;
class Post extends Model
{
use HasSEO;
}
Then render SEO tags in a Blade view:
{!! seo()->for($post) !!}
Model Integration:
HasSEO trait to auto-associate a SEO model with your Eloquent model.seo relationship:
$post->seo->update([
'title' => 'Updated Title',
'description' => 'Updated description...',
]);
Dynamic SEO Data:
Override getDynamicSEOData() to fetch SEO data dynamically:
public function getDynamicSEOData(): SEOData
{
return new SEOData(
title: $this->title,
description: $this->excerpt,
image: $this->featuredImagePath,
);
}
Controller-Driven SEO:
Pass SEOData directly from a controller:
return view('page', [
'SEOData' => new SEOData(
title: 'Custom Title',
description: 'Custom description...',
),
]);
Render in Blade:
{!! seo($SEOData) !!}
Structured Data:
Use SchemaCollection to generate JSON-LD:
use RalphJSmit\Laravel\SEO\Support\Schema\Article;
$schema = new SchemaCollection([
new Article(
headline: $post->title,
description: $post->excerpt,
datePublished: $post->published_at,
),
]);
return new SEOData(schema: $schema);
Fallbacks:
Configure defaults in config/seo.php (e.g., title.suffix, description.fallback).
Blade Directives:
Use {!! seo()->for($model) !!} in layouts to avoid repetition.
For dynamic routes (e.g., Inertia.js), leverage enableTitleSuffix: false in SEOData.
Middleware: Attach SEO data in middleware for API responses or non-model pages:
public function handle($request, Closure $next)
{
$response = $next($request);
$response->headers->set('X-SEO-Data', json_encode($seoData));
return $response;
}
Testing:
Mock SEOData in tests:
$this->view->share('SEOData', new SEOData(title: 'Test Title'));
Image Paths:
Ensure image paths in SEOData are relative to public_path() or use absolute URLs. The package uses secure_url(), which resolves paths via public_path().
Title Suffix:
The title.suffix in config is not appended to the homepage by default. Set config('seo.title.homepage_title') explicitly if needed.
Robots Tag:
If robots.force_default is true, manual overrides via SEOData->robots are ignored. Use SEOData->markAsNoIndex() instead.
Dynamic Data Overrides: The priority order for SEO data is:
SEOManager::SEODataTransformer (lowest priority).getDynamicSEOData().SEO model.SEOManager::getSEOData() output.Alternate Tags:
Ensure alternates in SEOData use absolute URLs (e.g., https://example.com/en). Relative paths may break hreflang tags.
Schema Validation:
Structured data (e.g., Article) requires valid datePublished/modified_time (Carbon instances). Invalid dates may cause rendering errors.
Inspect Generated Tags:
Use SEOManager::getSEOData($model)->toArray() to debug raw data before rendering.
Disable Inertia Attribute:
Add enableInertia: false to SEOData if titles flicker in SPAs:
new SEOData(enableInertia: false)
Clear Cached Config:
If changes to config/seo.php aren’t reflected, clear the config cache:
php artisan config:clear
Favicon Issues:
Verify favicon path in config points to a valid file (e.g., public/favicon.ico). Supported formats: ico, png, svg.
Locale Handling:
Override locale in SEOData for multilingual sites:
new SEOData(locale: 'fr')
Custom Transformers:
Extend SEO data generation via SEOManager::SEODataTransformer:
SEOManager::SEODataTransformer(function ($data, $model) {
if ($model instanceof Post) {
$data->tags = ['laravel', 'seo'];
}
return $data;
});
Custom Schema Types:
Extend SchemaCollection for new JSON-LD types:
class CustomSchema extends Schema
{
public function toJsonLd(): array
{
return ['@type' => 'CustomType', 'property' => 'value'];
}
}
Override Defaults: Dynamically modify config values in a service provider:
SEOManager::setConfig('robots.default', 'noindex,nofollow');
Event Listeners: Trigger actions on SEO updates via model observers:
class PostObserver
{
public function saved(Post $post)
{
if ($post->wasChanged('title')) {
$post->seo->update(['title' => $post->title]);
}
}
}
How can I help you explore Laravel packages today?