Installation
composer require devrabiul/laravel-seo-manager
Publish the config file:
php artisan vendor:publish --provider="Devrabiul\SeoManager\SeoManagerServiceProvider" --tag="config"
Basic Usage
Inject the SeoManager facade into your controller or blade view:
use Devrabiul\SeoManager\Facades\SeoManager;
// Set meta tags for a route
SeoManager::setTitle('Page Title');
SeoManager::setDescription('Page description for SEO');
SeoManager::setKeywords(['keyword1', 'keyword2']);
SeoManager::setImage('https://example.com/image.jpg');
First Use Case: Dynamic Meta Tags In a controller, dynamically set SEO metadata for a blog post:
public function show(BlogPost $post)
{
SeoManager::setTitle($post->title);
SeoManager::setDescription($post->excerpt);
SeoManager::setCanonical(url()->current());
SeoManager::setOpenGraph([
'type' => 'article',
'article:published_time' => $post->published_at,
]);
return view('blog.post', compact('post'));
}
Blade Integration
Add this to your layout file (e.g., resources/views/layouts/app.blade.php):
<head>
{!! SeoManager::render() !!}
</head>
Route-Based SEO Configuration Use middleware to set default SEO metadata for routes:
// app/Http/Middleware/SetDefaultSeo.php
public function handle($request, Closure $next)
{
SeoManager::setTitle(config('app.name'));
SeoManager::setDescription(config('app.seo.default_description'));
return $next($request);
}
Register in app/Http/Kernel.php:
protected $middlewareGroups = [
'web' => [
// ...
\App\Http\Middleware\SetDefaultSeo::class,
],
];
Model-Based SEO Extend a base model to auto-set SEO metadata:
// app/Models/Concerns/HasSeo.php
trait HasSeo
{
public function getSeoTitle()
{
return $this->title . ' | ' . config('app.name');
}
}
Usage in a controller:
SeoManager::setTitle($post->getSeoTitle());
Structured Data (JSON-LD) Add schema markup for rich snippets:
SeoManager::setJsonLd([
'@context' => 'https://schema.org',
'@type' => 'Article',
'headline' => $post->title,
'datePublished' => $post->published_at->toISOString(),
'author' => [
'@type' => 'Person',
'name' => $post->author->name,
],
]);
Social Media Optimization Configure OpenGraph and Twitter Cards:
SeoManager::setOpenGraph([
'title' => $post->title,
'description' => $post->excerpt,
'url' => route('blog.post', $post),
'image' => $post->featured_image_url,
'site_name' => config('app.name'),
]);
SeoManager::setTwitterCard([
'card' => 'summary_large_image',
'site' => '@yourhandle',
]);
Caching: Cache SEO metadata for static pages to reduce database queries:
$seoCacheKey = 'seo:blog:'.$post->id;
$seoData = Cache::remember($seoCacheKey, now()->addHours(1), function () use ($post) {
return [
'title' => $post->getSeoTitle(),
'description' => $post->excerpt,
];
});
SeoManager::setTitle($seoData['title']);
Fallback Values: Use config defaults for missing metadata:
SeoManager::setTitle(config('seo.default_title', $post->title ?? 'Default Title'));
Dynamic Image Handling: Generate social media images on-the-fly using a service:
$imageUrl = route('social.image', ['post' => $post->id]);
SeoManager::setImage($imageUrl);
Localization: Support multi-language SEO:
$locale = app()->getLocale();
SeoManager::setTitle(__("seo.{$locale}.blog.title", ['post' => $post->title]));
Missing Facade Import
Forgetting to import the SeoManager facade or using the wrong namespace:
// Wrong:
use Devrabiul\SeoManager\SeoManager; // Missing Facades namespace
// Correct:
use Devrabiul\SeoManager\Facades\SeoManager;
Overwriting Metadata
SEO settings are global per request. If you set metadata in middleware and later overwrite it in a controller, the last call wins. Use SeoManager::reset() to clear all settings if needed:
SeoManager::reset(); // Clears all current SEO settings
Image Paths
Always use absolute URLs for setImage() or OpenGraph images. Relative paths may break social sharing previews.
JSON-LD Validation Invalid JSON-LD can break rich snippets. Validate your schema using Google's Rich Results Test:
$jsonLd = SeoManager::getJsonLd();
// Manually validate $jsonLd before rendering
Blade Escaping
The SeoManager::render() output is not escaped by default. If you encounter issues, wrap it in {!! !!}:
<head>
{!! SeoManager::render() !!}
</head>
Inspect Rendered Tags Add this to your layout to debug SEO output:
@if(config('app.debug'))
<pre>{!! htmlspecialchars(SeoManager::render()) !!}</pre>
@endif
Check Config Overrides
Ensure your .env or config/seo.php isn’t overriding critical settings:
# Example: Disable OpenGraph if not needed
SEO_ENABLE_OPENGRAPH=false
Middleware Conflicts
If SEO tags aren’t rendering, check for middleware that might be interfering (e.g., App\Middleware\TrimStrings modifying the response).
Caching Issues Clear Laravel and SEO-specific caches:
php artisan cache:clear
php artisan view:clear
Custom Meta Tags
Add arbitrary meta tags via the addMetaTag() method:
SeoManager::addMetaTag([
'name' => 'article:author',
'content' => $post->author->name,
]);
Dynamic Canonical URLs Override the canonical URL logic in a service:
// app/Services/CanonicalUrlService.php
public function getCanonicalUrl($request)
{
if ($request->routeIs('blog.post')) {
return route('blog.canonical', $request->post);
}
return url()->current();
}
Bind it in AppServiceProvider:
SeoManager::extend('canonical', function ($request) {
return app(CanonicalUrlService::class)->getCanonicalUrl($request);
});
Event-Based SEO Trigger SEO updates via events (e.g., after a post is published):
// In your event listener
SeoManager::setTitle($post->title);
SeoManager::setOpenGraph([
'title' => $post->title,
'published_time' => $post->published_at,
]);
Headless CMS Integration Fetch SEO metadata from a headless CMS API:
$seoData = $cms->getSeoData($post->slug);
SeoManager::setTitle($seoData['title']);
SeoManager::setJsonLd($seoData['schema']);
Testing SEO Output Use Laravel’s HTTP tests to assert SEO tags:
$response = $this->get('/blog/post-1');
$response->assertSee('<title>Post Title</title>');
$response->assertSee('<meta property="og:title" content="Post Title">');
How can I help you explore Laravel packages today?