Weave 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

Fine Seo Laravel Package

ali-awwad/fine-seo

Fine SEO is a Laravel package that simplifies page-level SEO management. It helps you set titles, meta descriptions, canonical URLs, Open Graph and Twitter Card tags through a clean, configurable API, making it easy to add consistent metadata across your application.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and publish.

    composer require ali-awwad/fine-seo
    php artisan vendor:publish --tag="fine-seo-config"
    

    Check the package README for the exact tag names, migration/asset publishing flags, and whether the service provider is auto-discovered (Laravel 11+ package discovery usually covers it).

  2. Read config/fine-seo.php first. In an SEO package the config is the API surface: default title/description, title separator, Open Graph defaults, Twitter card type, canonical settings, robots defaults, and — as of v0.2.2 — the sitemap section (output path, URL source, caching, ping/search-engine settings if present). Every day-to-day decision flows from here, so skim it before writing any markup.

  3. Wire the <head> once. Add the package's render call to your single base layout (resources/views/layouts/app.blade.php) — typically a Blade component, directive, or {!! fineSeo()->render() !!} style call. Doing this once in the layout rather than per-page is what makes the rest of the package pleasant.

  4. First use case — a static page. Set title, description, and canonical on your home page (via controller, facade, or a Blade component), then view-source to confirm tags render before </head> and that escaping is correct.

  5. First use case — the new sitemap (v0.2.2). This is the headline feature of the release. Register the sitemap route (or hit the artisan command the README documents), confirm /sitemap.xml returns application/xml, then add your static marketing routes first before attempting model-driven entries. Validate the output with a linter or xmllint --noout before submitting anywhere.

  6. Verify the version. v0.2.2 is pre-1.0 and skips v0.2.1 in the changelog link (v0.2.0...v0.2.2). Pin it exactly ("ali-awwad/fine-seo": "0.2.2") until you've read the full diff for the "few bugs" it mentions — the notes do not say which bugs, so treat the diff as required reading if you were on 0.2.0.

Implementation Patterns

Layout-level defaults + per-page overrides. Configure sensible site-wide defaults in config/fine-seo.php (title suffix, default OG image, Twitter handle) and let individual pages override only what differs. This keeps controllers clean and prevents every new page from shipping a blank <title>.

Per-model SEO via a dedicated columns/relations approach. For blog posts, products, and categories, store seo_title, seo_description, and og_image either as columns or in a JSON seo column (or a polymorphic seo_meta table). Expose them through accessors with model-attribute fallbacks, so a missing seo_title degrades to the post title instead of rendering nothing. This is the single highest-leverage pattern: it makes SEO editable by non-developers and testable by you.

Blade component for the page-level call. Rather than calling the facade from every controller, use a <x-seo :title="..." :description="..." /> style component at the top of each view (or in the layout with slots). Views already know their content; controllers usually don't. Reserve facade calls for cases where the controller genuinely owns the data (redirects, API-backed pages).

Sitemap as a scheduled, cached artifact. Generate the sitemap on a schedule rather than per-request:

// routes/console.php
Schedule::command('fine-seo:sitemap') // confirm exact signature in README
    ->hourly()
    ->withoutOverlapping();

Then serve the cached file from public storage or a thin controller. For large sites this keeps response times flat and avoids hammering the DB on every crawler hit. Wire cache invalidation into model events (saved/deleted on Post, Product) if you prefer near-real-time updates — but debounce it, or a bulk import will regenerate the sitemap 5,000 times.

Separate sitemap indexes early. Even if v0.2.2's sitemap is a single-file generator, structure your URL sources by type (pages, posts, products, categories). When you cross the 50,000-URL or 50MB limit you'll need a sitemap index, and having the sources already separated makes that a config change rather than a rewrite. Check whether the package supports indexes; if not, plan to emit per-type sitemaps yourself and hand-write the index.

Multilingual and canonical discipline. For localized routes, emit hreflang alternates and a self-referencing canonical per locale. Canonical should always point at the canonical host + path, never the current query string — strip UTM/pagination params explicitly. Paginated archives should be noindex,follow with self-canonicals or proper rel=prev/next depending on your SEO stance; pick one and enforce it centrally so it doesn't drift page by page.

Environment and staging protection. Add noindex globally when app()->environment() !== 'production' (a middleware or a config override in AppServiceProvider). Staging sites leaking into search results is the most common and most embarrassing failure mode of any SEO package.

Testing. Assert on rendered head output in feature tests: title present, description length sane, canonical absolute, OG tags not duplicated. For the sitemap, assert the route returns 200, Content-Type is XML, a known URL appears, and a noindex model does not appear. Snapshot-test the sitemap for a small fixture set — it catches accidental ordering and escaping regressions cheaply.

Integration notes. With Inertia or a JS frontend, server-rendered head tags from a Laravel package won't reach the client unless you serialize them into the page props and render them in your root template — crawlers that don't execute JS will see nothing. With Livewire, per-page tags set in a component can persist across navigations; force a reset on navigation. If you run a CDN, remember the sitemap is a cacheable asset and needs a purge hook on regeneration.

Gotchas and Tips

  • Pre-1.0 semantics. At 0.2.x, any minor release can rename a config key or facade method. Pin the exact version in composer.json, read the diff before bumping, and wrap calls behind a small app-level helper (app/Support/Seo.php) so a breakage is one file to fix rather than fifty.
  • "Fix few bugs" is not a changelog. v0.2.2 tells you nothing about what changed beyond the sitemap. Review the GitHub compare link before upgrading, particularly if you already shipped 0.2.0 to production.
  • Config cache. After changing config/fine-seo.php, run php artisan config:clear (and remember config:cache in deploy scripts). Sitemap path and URL-source changes silently won't apply otherwise — this is the number one "why is nothing happening" cause.
  • Sitemap limits and memory. The protocol caps a single file at 50,000 URLs / 50MB uncompressed. Generating model-driven entries with Model::all() will OOM on large tables — use lazy()/chunkById(), and check whether the package already streams. Also verify the sitemap only contains 200-status, canonical, indexable URLs; noindex pages in a sitemap is a self-inflicted warning in Search Console.
  • Sitemap and route/cache collisions. The sitemap route may conflict with an existing /sitemap.xml in public/ or a catch-all route. Route order matters. And if you serve a cached file, ensure regeneration actually overwrites it — stale sitemaps pointing at deleted URLs are worse than no sitemap.
  • Escaping and raw output. Head rendering typically requires {!! !!}. That means any user-supplied meta title/description is an XSS vector. Sanitize or e() at the point of storage/accessor, not at render time where it's easy to forget.
  • Duplicate tags. Plugins like Debugbar won't catch this, but duplicate <title>/<meta name="description"> from both your layout and the package is common. Grep your layout for hardcoded SEO tags after installing, and add a test that asserts exactly one <title>.
  • Absolute URLs only. OG images, canonicals, and sitemap entries must be absolute with the correct scheme/host. Set APP_URL correctly per environment, and beware proxies/load balancers: use URL::forceScheme('https') in AppServiceProvider behind TLS-terminating infrastructure, or you'll ship http:// canonicals.
  • Title/description length. Don't rely on the package to truncate sensibly — a hard character cut produces mid-word garbage. Use a multibyte-safe truncation (Str::limit respects mb_ via helpers; verify) and prefer word boundaries. Description guidance is ~150–160 chars; titles ~50–60.
  • Debugging workflow. When tags don't appear: (1) confirm the render call is inside <head> of the rendered layout, not a child view that isn't extending it; (2) curl -s https://site.test/page | grep -i '<meta' to bypass browser caching and JS; (3) dump the resolved SEO object (dd(fineSeo()->toArray()) or equivalent) to see whether values are set but not rendered, or never set; (4) check storage/logs/laravel.log — many SEO packages log validation/URL warnings there rather than throwing.
  • Extension points worth knowing. Look for a custom driver/renderer or tag-registry hook before forking. Adding JSON-LD structured data (Article, Product, BreadcrumbList) is usually a custom partial registered with the package rather than a supported feature at 0.2.x — plan to push raw JSON-LD through your own Blade partial and reference it from the package's custom-tags hook if one exists.
  • Robots.txt is likely separate. Adding a sitemap generator does not necessarily mean robots.txt management exists. Verify before you delete whatever generates your robots.txt, and add the Sitemap: line manually if not.
  • Search Console submission is manual. Nothing in this release automates submission; after deploying v0.2.2, submit the sitemap URL yourself and re-check coverage errors a few days later. Don't build ping/search-engine automation on 0.2.x APIs — they tend to churn most.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console