artesaos/seotools
SEOTools provides Laravel and Lumen helpers for common SEO tasks: setting titles and meta tags, Twitter Cards, Open Graph, and JSON-LD structured data through a simple, friendly interface.
Architecture fit
SEOToolsServiceProvider, six facades (SEOMeta, OpenGraph, TwitterCard, JsonLd, JsonLdMulti, SEOTools) backed by container singletons. Zero coupling to our domain model — it is a string builder for the <head>.robots.txt, no canonical redirect middleware, no hreflang helper, no structured-data validation, no crawl or index tooling. Any business case that assumes those capabilities is mis-scoped at intake.Integration feasibility
composer require, register the provider (Laravel 11+ → bootstrap/providers.php; Lumen → $app->register(...)), optionally publish config/seotools.php, add one Blade partial, and call the facades. Realistic estimate for a mid-size app: 2–4 engineer-days including a presenter layer, tests, and backfill of existing pages.app('seotools.metatags') etc. — a real ergonomics penalty if any service is Lumen-based.defaults + webmaster per channel), which makes sitewide values declarative rather than duplicated in views.Technical risk
readme and releases: no changelog, contributing guide, or docs site in evidence. Treat the README's version claims as unverified.og:title, wrong JSON-LD on a competitor-facing page — a brand-damaging failure mode).{!! SEOTools::generate() !!} — raw output. If titles/descriptions originate from user-editable content, we own the escaping. Confirm the package's attribute escaping before shipping CMS-authored metadata.JsonLd/JsonLdMulti will emit structurally invalid schema.org silently. Google ignores bad structured data; we get no error, only lost rich results.Bottom line: low-risk, low-cost, high-commodity dependency. Approve as a tactical utility behind an internal abstraction; do not treat it as an SEO strategy investment.
Key questions
composer.json, and is Laravel 12 in the matrix?<head> server-rendered (Blade/Inertia root template) or client-rendered? If client-rendered, this package is the wrong tool.generate(), and does any metadata derive from user or CMS input?robots.txt, hreflang, or canonical redirects? Those are out of scope and require separate scoping.defaults fallback silently emit sitewide titles on every page, creating duplicate-title risk?Stack fit
app.blade.php renders the head — otherwise evaluate alternatives or emit tags as props.og:* produces conflicting tags and unpredictable crawler behavior.Migration path
@section('title'), hardcoded tags in app.blade.php, DB columns, any per-controller View::share.App\Support\Seo wrapping the facades, so the vendor is swappable and call sites stay stable if we fork.resources/views/partials/seo.blade.php calling SEOTools::generate(); delete legacy tag blocks in the same PR to prevent duplication.HasSeoMetadata model concern — controllers should not accumulate six facade calls per action.config/seotools.php with sitewide defaults (site name, default og:type, Twitter handle, webmaster verification tags) and make defaults explicit rather than implicit.Compatibility
php artisan config:cache is safe for the config file, but any runtime config([...]) mutation becomes fragile — prefer passing values through the facades.bootstrap/providers.php; facade aliases still live in config/app.php. Document both paths in the runbook.Sequencing
Seo wrapper + Blade partial + defaults (1 day) → 3. Octane/reset hardening if applicable (0.5 day) → 4. route backfill and legacy removal (1–2 days) → 5. automated head-tag assertions in the test suite (0.5 day) → 6. Search Console baseline capture two weeks pre- and post-launch.Maintenance
Support
Scaling
Failure modes
Ramp-up
og:type should be article, and why duplicate descriptions matter. Budget that separately — it is a content/marketing partnership, not an engineering task.Observability
title, canonical, and one og:* tag — that catches 80% of regressions for near-zero cost.How can I help you explore Laravel packages today?