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

Redirect Bundle Laravel Package

zenstruck/redirect-bundle

Symfony bundle for managing HTTP redirects directly in your app instead of web server config. Define source paths, targets, and status codes, then let the bundle match incoming requests and issue the redirects automatically.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require zenstruck/redirect-bundle gives you three things worth knowing about: a Redirect entity (source path → destination path, plus a timestamp for when it was last hit), a RedirectManager service that does the lookup and hands back a RedirectResponse, and a Doctrine mapping you point at your own entity class.

First, decide what "installing" means for you. This is a Symfony bundle. In a Symfony app, Flex registers it, you create an entity extending Zenstruck\RedirectBundle\Model\Redirect (or use the shipped concrete one), set zenstruck_redirect.redirect_class, and run a migration. In a Laravel app there is no bundle kernel to register — you get the PHP classes and are on your own for wiring, plus a second ORM (doctrine/orm) next to Eloquent. That's usually not worth it; the pattern (one table, one manager, one early-request check) is worth copying onto Eloquent directly.

Where to look first: RedirectManager::findAndUpdate(), RedirectManager::getRedirectResponse(), and the Model\Redirect base class. Everything else is config and admin sugar.

First use case: intercept a request before routing, look up the path, and return a 301 if there's a match. In Symfony that's a kernel.request listener; in Laravel a global middleware registered ahead of the router. Because Laravel's Illuminate\Http\Request extends Symfony\Component\HttpFoundation\Request, the manager's request-based methods accept it as-is — that's the one piece of real interop here.

Then seed 5–10 known-dead legacy URLs, curl them, and confirm you get a 301 with the right Location and that the hit timestamp moved.

Implementation Patterns

Intercept early, return immediately. The redirect check belongs before routing, not in a controller or a 404 handler — you want to skip the router, middleware stack, and any auth entirely for a redirect. Guard it to GET/HEAD and to non-asset, non-API prefixes so you're not doing a query per favicon.

Canonicalize before lookup. Pick one stored form (leading slash, no trailing slash, lowercase, query string stripped) and normalize the incoming path to match before calling the manager. Every "the redirect exists but doesn't fire" bug starts here.

Bulk import as a command, not a loop in a seeder. A console command that reads a CSV/old sitemap and upserts is the day-two need. Batch the flushes — a per-row flush on a 5,000-URL import is minutes of wall time. Make the command idempotent so re-running after a partial import is safe.

Use the hit timestamp as your analytics. It's the cheapest observability you'll get: which redirects are hot (keep), which have never been hit since import (delete candidates), which destinations are being hammered and deserve a real page again. A weekly query over that column is the whole reporting story.

Promote 404s into redirects. The bundle models "not found" entries alongside redirects so that a 404 becomes a row you can later fill in a destination for. If you build this on Eloquent, keep that split: log the miss cheaply, create the redirect deliberately. Don't auto-redirect on miss.

Cache the map, not the request. The table is small and read-mostly. Either cache the whole path => destination map and rebuild on write, or Cache::remember per path with a version-key bump on any write. Skipping this means a DB round-trip on every page view.

Chain with a hop limit. If a destination is itself a source, resolve it — but cap at 1–2 hops and bail on cycles. Better: forbid saving a destination that matches an existing source.

Multi-domain: if one app serves several hostnames, either store host-qualified sources or add a site/scope column. Retrofitting this later is a data migration.

Testing: a functional test asserting 301 + Location for a seeded row, and a unit test of the manager against an in-memory entity. The response-building method is the only part with interesting logic.

Gotchas and Tips

  • This release (v3.11.0) is a no-op for behavior. Symfony 8 support plus internal test-deprecation fixes. No new features, no breaking changes, no API surface change. Safe to take; nothing to migrate.
  • Symfony 8 requires PHP 8.4. If you're on 8.2/8.3 you're staying on Symfony 7 and this release does nothing for you. The signal is maintenance, not capability. Upgrade whenever; there's no urgency and no lock-in.
  • The foundry fix is test-tooling only (zenstruck/foundry is a fixture library). If you don't test against the bundle's fixtures, it cannot affect you.
  • It's a bundle, not a Laravel package. No service provider, no config publishing, no facade. Pulling doctrine/orm into a Laravel app to store one table of two strings is a bad trade — reimplement on Eloquent in ~50 lines and keep the design.
  • source is almost certainly unique-indexed. Duplicate seeds and concurrent inserts will throw. Use upsert, or catch the constraint violation and move on.
  • 301 is cached by browsers effectively forever. During a migration, ship temporary (302) redirects, verify the mapping in production for a week, then flip to permanent. Getting this backwards means telling users to clear their cache.
  • Hit tracking is a write per redirected request. Under load that's a write amplification you didn't budget for. Defer it, sample it, or drop the timestamp update and log instead.
  • Exact-path matching only. No wildcards, no regex, no pattern segments. If you need /blog/* behavior you're either adding a matcher yourself or storing every URL individually. Confirm this before you promise a URL structure to anyone.
  • Migrations: if you extend the model into your own entity, verify the bundle's expected column names and nullability before writing the migration by hand — the base class is the source of truth, not the docs.
  • Extension points: swap redirect_class, decorate/replace RedirectManager, or bypass matching entirely. The entity + manager split is clean enough that replacing the lookup is a contained change.
  • Don't put redirects in web-server config. That's the tempting alternative at small scale, and it's invisible to your app, unversioned, and unreviewable. Keeping them in the DB is the reason to use this at all.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource