Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Seotools Laravel Package

artesaos/seotools

SEOTools adds SEO helpers for Laravel and Lumen: quickly set page titles, meta tags, Open Graph, Twitter Cards, and JSON-LD structured data via a simple, friendly API. Supports modern Laravel versions with package discovery.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require artesaos/seotools
    

    Laravel 5.5+ automatically registers the package via Package Discovery. For older versions, add the provider to config/app.php:

    'providers' => [
        Artesaos\SEOTools\Providers\SEOToolsServiceProvider::class,
    ],
    
  2. Publish Config (optional but recommended):

    php artisan vendor:publish --provider="Artesaos\SEOTools\Providers\SEOToolsServiceProvider"
    

    This generates config/seotools.php for customization.

  3. First Use Case: In a controller, set basic meta tags:

    use Artesaos\SEOTools\Facades\SEOMeta;
    use Artesaos\SEOTools\Facades\OpenGraph;
    
    public function index()
    {
        SEOMeta::setTitle('My Page Title');
        SEOMeta::setDescription('A concise description for SEO.');
        OpenGraph::setUrl(url()->current());
        OpenGraph::setTitle('My Page Title');
        OpenGraph::setImage(url('images/og-image.jpg'));
    }
    
  4. Blade Integration: Add this to your layout file (e.g., resources/views/layouts/app.blade.php):

    <head>
        {!! SEO::generate() !!}
        <!-- Other head elements -->
    </head>
    

Implementation Patterns

1. Controller-Level SEO

Use facades or the SEOTools facade to centralize SEO logic in controllers:

public function show(Post $post)
{
    SEOTools::setTitle($post->title)
            ->setDescription($post->excerpt)
            ->setCanonical(url()->current())
            ->opengraph()
                ->setUrl(url()->current())
                ->setImage($post->featured_image)
                ->addProperty('article:published_time', $post->published_at->toW3CString())
            ->twitter()
                ->setSite('@myhandle')
                ->setCardType('summary_large_image');
}

2. Dynamic Meta Tags

Leverage collections or loops to generate dynamic tags:

public function category(Category $category)
{
    SEOMeta::setTitle("Category: {$category->name}");
    SEOMeta::addKeyword($category->tags->pluck('slug')->toArray());

    OpenGraph::setTitle("Category: {$category->name}");
    OpenGraph::addImage($category->posts->take(3)->pluck('thumbnail'));
}

3. JSON-LD for Structured Data

Use JsonLd or JsonLdMulti for schema markup (e.g., articles, products):

// Single JSON-LD block
JsonLd::setTitle($product->name)
      ->setDescription($product->description)
      ->setType('Product')
      ->addProperty('price', $product->price)
      ->addProperty('availability', $product->stock > 0 ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock')
      ->addImage($product->images);

// Multiple JSON-LD blocks (e.g., for breadcrumbs + product)
JsonLdMulti::setTitle('Breadcrumb')
           ->setType('Breadcrumb')
           ->addProperty('itemListElement', [
               ['position' => 1, 'name' => 'Home', 'item' => url('/')],
               ['position' => 2, 'name' => $category->name, 'item' => url("/category/{$category->slug}")]
           ]);
if (!JsonLdMulti::isEmpty()) {
    JsonLdMulti::newJsonLd(); // Start a new block
    JsonLd::setType('Product')->setTitle($product->name);
}

4. Reusable SEO Services

Create a service class to encapsulate SEO logic:

// app/Services/SEOService.php
class SEOService
{
    public function configureBlogPost(Post $post)
    {
        SEOMeta::setTitle($post->title)
               ->setDescription($post->excerpt)
               ->addMeta('article:published_time', $post->published_at->toW3CString(), 'property');

        OpenGraph::setTitle($post->title)
                 ->setDescription($post->excerpt)
                 ->setUrl(url()->current())
                 ->addImage($post->featured_image)
                 ->addProperty('article:section', $post->category->slug);

        JsonLd::setType('BlogPosting')
              ->setTitle($post->title)
              ->addProperty('headline', $post->title)
              ->addProperty('description', $post->excerpt)
              ->addProperty('datePublished', $post->published_at->toISOString());
    }
}

Usage:

$this->seoService->configureBlogPost($post);

5. Middleware for Global SEO

Apply SEO defaults via middleware (e.g., app/Http/Middleware/SetSEODefaults.php):

public function handle(Request $request, Closure $next)
{
    SEOMeta::setDefaultTitle(config('app.name'));
    SEOMeta::setDefaultDescription(config('seotools.meta.defaults.description'));
    OpenGraph::setDefaultImage(config('seotools.opengraph.defaults.image'));

    return $next($request);
}

Register in app/Http/Kernel.php:

protected $middlewareGroups = [
    'web' => [
        // ...
        \App\Http\Middleware\SetSEODefaults::class,
    ],
];

6. Testing SEO Output

Use Laravel’s HTTP tests to verify SEO tags:

public function test_homepage_seo()
{
    $response = $this->get('/');
    $response->assertSee('<title>My Page Title</title>');
    $response->assertSee('property="og:title" content="My Page Title"');
    $response->assertJsonStructure([
        'script' => [
            ['type' => 'application/ld+json'],
        ],
    ]);
}

Gotchas and Tips

Pitfalls

  1. Facades in Lumen:

    • Lumen does not support facades. Use the container directly:
      $seotools = app('seotools');
      $seotools->setTitle('Lumen Page');
      
  2. Overwriting Defaults:

    • Defaults in config/seotools.php are not merged with dynamic values. Explicitly set values will override defaults, but unset values will fall back to defaults. To disable defaults entirely, set them to false or null:
      'meta' => [
          'defaults' => false, // Disables all default meta tags
      ],
      
  3. JSON-LD Validation:

    • Always validate your JSON-LD output using Google’s Rich Results Test. Common issues:
      • Missing required properties (e.g., name for Product schema).
      • Incorrect data types (e.g., passing a string for a DateTime field).
      • Duplicate @type declarations in JsonLdMulti.
  4. OpenGraph/Twitter Card Validation:

  5. Canonical URLs:

    • Always set a canonical URL to avoid duplicate content issues:
      SEOMeta::setCanonical(url()->current());
      
    • For dynamic routes, use url()->current() or route()->current() to avoid hardcoding.
  6. Performance Impact:

    • Excessive JsonLdMulti blocks or large OpenGraph images can slow down page rendering. Audit with Lighthouse or WebPageTest.
  7. Blade Cache Conflicts:

    • If using Blade caching (php artisan view:cache), clear the cache after changing SEO configurations:
      php artisan view:clear
      

Debugging Tips

  1. Inspect Generated HTML:

    • Use browser dev tools (Ctrl+U) to verify tags are rendered correctly. Look for:
      • <meta> tags in the <head>.
      • <script type="application/ld+json"> for JSON-LD.
      • OpenGraph/Twitter tags (e.g., property="og:image").
  2. Log SEO Output:

    • Temporarily log the generated HTML to debug:
      file_put_contents(
          storage_path('logs/seo-debug.html'),
          SEOTools::generate()
      );
      
  3. Validate Schema Markup:

  4. Check for Deprecated Methods:

    • Some older versions used methods like SEOMeta::addMeta('keywords', [...]).
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
syriable/filament-translator
hungnm28/livewire-form
wenprise/eloquent
crudly/encrypted
fadion/bouncy
cuci/prototurk-sdk
gos/pubsub-router-bundle
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle
codeflextech/permission-manager
karnoweb/livewire-datepicker
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk