the-3labs-team/nova-redirector-seo
Manage SEO redirects directly from Laravel Nova. Create, edit, and track 301/302 redirects with a simple interface, keeping old URLs pointing to the right pages and preserving search rankings after site changes.
What you're getting. A Nova-side CRUD for redirect rules plus (almost certainly) the request-time machinery that turns those rows into 301/302 responses. The value proposition is that content editors manage redirects without a deploy, and you stop hand-maintaining a routes/web.php graveyard of Route::redirect() calls.
Before you install, sanity-check the repo. One star and zero dependents means you are the QA team. Open the GitHub page and confirm, in this order:
composer.json — supported Laravel/Nova/PHP ranges. If it doesn't list Nova 4 and 5 explicitly, assume it doesn't support your version.src/ — is there an HTTP middleware or a 404 handler? That file is the entire product; read it before you trust it.database/migrations/ — the redirect table shape tells you what features actually exist (status code column? hits counter? regex flag? domain column?).CHANGELOG / tags — a single 1.0.0 released in 2026 with no follow-up is a red flag for maintenance.Minimal install.
composer require the-3labs-team/nova-redirector-seo
# Discover what the provider actually publishes before guessing tags:
php artisan vendor:publish --provider="The3LabsTeam\NovaRedirectorSeo\NovaRedirectorSeoServiceProvider"
php artisan migrate
Then find the config file it dropped (likely config/nova-redirector-seo.php or config/redirector.php) and check three keys: the middleware toggle, the cache toggle, and the table name. If no config is published, the package is configuration-by-convention and you'll be reading source.
Register the middleware in bootstrap/app.php (Laravel 11+) or app/Http/Kernel.php:
// Laravel 11+
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\The3LabsTeam\NovaRedirectorSeo\Http\Middleware\RedirectMiddleware::class,
]);
})
The class name above is a guess — confirm from src/. Placement matters enormously; see Gotchas.
First use case. A marketing page moved from /pricing to /plans. In Nova, create a redirect row: from /pricing, to /plans, status 301. Load /pricing in an incognito window and confirm the Location header with curl -I:
curl -sI https://yourapp.test/pricing | grep -iE '^(HTTP|location)'
If that doesn't work, you have a middleware registration problem, not a data problem. Verify the row exists (php artisan tinker, query the table) before debugging further.
Pattern 1 — Editor-owned 301s, developer-owned 410s. Let Nova own 301/302 rules for content moves. Keep deletions (410 Gone) and permanent structural changes in code, because a content editor deleting a redirect row silently resurrects a 404 for traffic you already told Google about.
Pattern 2 — Auto-create redirects on slug change. This is where the package earns its keep. Hook your model's updating event so a slug rename writes a redirect row in the same transaction:
protected static function booted(): void
{
static::updated(function (Page $page) {
if ($page->wasChanged('slug')) {
Redirect::firstOrCreate(
['from' => '/'.ltrim($page->getOriginal('slug'), '/')],
['to' => '/'.$page->slug, 'status_code' => 301, 'is_active' => true],
);
}
});
}
Adjust the model/class names to whatever the package ships. The transaction matters: if the redirect insert fails, you don't want the slug update to commit.
Pattern 3 — Cache the lookup map, invalidate on write. A DB hit on every 404 is fine at low traffic and terrible at high. The middleware almost certainly resolves the target URL before it knows whether the route matched, which means a query per request. Warm a map into the cache and bust it from an observer:
public function saved(Redirect $redirect): void { Cache::forget('redirects.map'); }
public function deleted(Redirect $redirect): void { Cache::forget('redirects.map'); }
If the package has a redirects:cache command, prefer it in your deploy script — it's cheaper than cold-building on the first request. Note that Cache::forget from an observer won't fire for bulk Redirect::query()->update() or a raw SQL import; plan a manual purge for those paths.
Pattern 4 — Bulk import during a migration. For a site relaunch with 400 legacy URLs, don't use the Nova UI. Write an artisan command or a one-off seeder reading a CSV, chunked at 500, wrapped in a transaction. Assert uniqueness on the from column first, or you'll get a partial import with silent duplicates.
Pattern 5 — 404 logging to drive the backlog. You can't guess which URLs are broken. Log 404 paths with hit counts (a simple path, count, last_seen_at table populated from Handler::render or the exception handler) and let the Nova resource be where someone triages that list into redirects. This is the highest-leverage habit with any redirect tool and it's usually missing from the package.
Pattern 6 — Test it as an HTTP contract, not a unit. Redirect bugs are almost always integration bugs:
it('redirects the legacy pricing url', function () {
$this->get('/pricing')
->assertStatus(301)
->assertRedirect('/plans');
});
Add one test per redirect category (exact, wildcard, query-string passthrough, inactive, chain) rather than one per row.
Middleware ordering is the #1 failure mode. The redirect middleware must run after the request is normalized but before the router throws NotFoundHttpException. Appending to the web group gets you the former for routes that exist; for genuinely missing routes you may need to hook the exception handler's render() instead. Symptoms of a wrong placement: existing routes 404 correctly but legacy URLs also 404, or the redirect fires on /nova/* and locks you out of the admin panel. Add an explicit exclude for your Nova path and horizon, telescope, _ignition, and storage.
Never redirect non-GET requests. A POST /checkout catching a 301 turns into a GET to the new URL and drops the payload — silent data loss that looks like a client bug for weeks. Guard with if (! $request->isMethod('GET') && ! $request->isMethod('HEAD')) return $next($request);.
Query strings get dropped by default in most implementations. /search?q=x redirecting to /find loses q. Decide deliberately: preserve for 301s on filtered listing pages, drop for 301s on retired endpoints. Test both.
Redirect chains and loops. A→B followed by a B→C row means Google crawls two hops and your TTFB doubles for that hit. Add a validation rule in the Nova resource (or a model observer) that rejects a to value which itself appears in the from column. Also guard self-redirects (from == to), which produce ERR_TOO_MANY_REDIRECTS and are trivial to create by typo.
301 Is cached by browsers, apparently forever. During development you will 301 yourself into a hole and clearing it requires an incognito window or a cache wipe. Use 302 in staging, 301 only in production for confirmed-permanent moves. If you must undo a live 301, changing the row does nothing for browsers that already cached it — you need a new URL or a Cache-Control header on the redirect response.
Cache staleness after direct DB edits. Every UPDATE you run in tinker leaves the cached map lying. Either always go through Eloquent (so observers fire) or add a redirects:clear habit to your debug loop.
Trailing slashes and case. /About and /about are different keys. Normalize from on save (lowercase, strip trailing slash except root) and normalize the path before lookup — but check whether the package already does this, because double-normalizing can break intentional case-sensitive rules.
Authorization. Publish and edit the Nova policy. Redirect rules are an SEO-critical, publicly-visible lever; a mis-typed 301 .* → / in a wildcard row can deindex the site. If the package supports wildcards or regex, restrict that ability to a specific role.
Maintenance risk is the real assessment. With one star, one release, and no dependents, budget for: pinning the exact version in composer.json, vendoring or forking if a Laravel upgrade breaks it, and writing your own integration test suite around it so you can swap in a hand-rolled Redirect model + middleware (roughly 150 lines) if it goes unmaintained. The redirect table, the Nova resource, and the middleware are each small enough to own outright — treat the package as a scaffold, not infrastructure. If the source is under ~500 lines, read all of it on day one; it will take twenty minutes and save you a week of guessing.
How can I help you explore Laravel packages today?