skip to content

In Laravel, what happens when two routes share a name, and how does that surface differently with and without route caching?

level: seniorimportance: should knowfreq 25%

answer

  1. docs: names should be unique
  2. lookup keeps one route per name
  3. route() silently points at one
  4. caching throws LogicException
  5. unnamed routes get generated:: names

basics

~20 s

Without caching, Laravel's name lookup keeps just one of the routes, so route() silently points at it while the other stays reachable only by URL; route:cache refuses the duplicate with a LogicException naming the URI and the name.

solid answer

~40 s

The routing docs say route names should always be unique, but nothing stops you registering two. In Laravel 13 the route collection rebuilds its name lookup after boot and keeps **one** route per name (the first registered, with domain routes indexed first). Both routes still match their URLs, but `route('orders.show')` and redirects by name always build the URL of the winner, so links to the other silently go to the wrong page. Caching exposes it: compiling routes for the cache throws `LogicException: Unable to prepare route [uri] for serialization. Another route has already been assigned name [orders.show].` That often happens only in the deploy step. Unnamed routes are not a problem: they get random `generated::` names when cached. Catch duplicates early with a CI step that builds the route cache.

code

bash · 5 lines
bash
# more than one row means a duplicate name
php artisan route:list --name=orders.show

# CI: building the cache fails on duplicate names
php artisan route:cache

go deeper

for a junior

Remember that route names must be unique and that route() finds routes by name.

for a middle

Explain that duplicates are accepted at registration, that route() then resolves to only one route, and that caching throws a LogicException.

for a senior

Catch duplicates in CI with route:list checks or a cache build, and use group name prefixes so mirrored areas cannot collide.

for a principal

Own the naming scheme and its enforcement across teams and packages, so a rename or a new area never breaks links silently.

## The rule and why it exists A route name is a key the URL generator looks up. The routing docs state it plainly: route names should always be unique. Laravel does not enforce that when you register routes, so duplicates slip in easily: - two developers add `->name('orders.show')` in different route files; - a group's name prefix is forgotten, so `admin` and `franchise` areas both register `orders.show`; - a copied route block keeps its old name. ## Without the route cache: a silent wrong link The route collection keeps a **name lookup table**. In Laravel 13 it is rebuilt once the application has booted, walking domain routes first and then the others, and it **keeps the first route it meets for each name**. The consequences: 1. Both routes are still registered and still **match their own URLs**. 2. `route('orders.show', ...)`, `to_route('orders.show')` and every other by-name lookup resolve to **one** of them. 3. Links meant for the other route point to the wrong page, or throw `UrlGenerationException` if the winner has different placeholders. 4. No warning is logged. The bug looks like a broken link, not a routing error. Which route wins is an implementation detail of the collection, so the only safe position is: never rely on it. ## With the route cache: a loud failure When routes are compiled for caching, the collection converts every route into a Symfony route keyed by name. When a second route arrives with a name already taken, compilation stops with: ``` LogicException: Unable to prepare route [franchise/orders/{order}] for serialization. Another route has already been assigned name [orders.show]. ``` Two details from the same code path: - **Unnamed routes** are given a random `generated::...` name during compilation, so they never collide. - A name that only ends in a dot (a group name prefix applied to a route that never got its own `->name()`) is also replaced by a generated name when it collides, instead of failing. | | Without cache | With cache | |---|---|---| | Registration | succeeds | succeeds | | URL matching | both routes match | compile fails before serving | | `route('orders.show')` | resolves to one route silently | never reached | | When you notice | a user reports a wrong link | the deploy step fails | ## A worked example Two route files, owned by different teams, both define `orders.show`: ```php // routes/admin.php, registered with prefix('admin') Route::get('/orders/{order}', [AdminOrderController::class, 'show'])->name('orders.show'); // routes/franchise.php, registered with prefix('franchise') Route::get('/orders/{order}', [FranchiseOrderController::class, 'show'])->name('orders.show'); ``` Locally, both `/admin/orders/7` and `/franchise/orders/7` work when typed into the browser. But every `route('orders.show', 7)` in both areas now builds the same URL, so one team's links send users into the other team's area. Nothing fails until the deploy builds the route cache. The durable fix is a name prefix per file: `admin.orders.show` and `franchise.orders.show`. ## Finding and preventing duplicates 1. `php artisan route:list --name=orders.show` lists every route with that name; more than one row is the bug. 2. `php artisan route:list --json` lets a script count names and fail on repeats. 3. Building the route cache in CI surfaces the `LogicException` before deploy. 4. Group name prefixes (`Route::name('franchise.')`) keep areas from colliding in the first place. ## Fixing one - Rename one route and update its `route()` calls; a project-wide search for the old name finds them. - If both routes serve the same page, delete one; two URLs for one page belong in a redirect route, not a duplicate name. - For admin and franchise areas that mirror each other, give each area its own name prefix so `admin.orders.show` and `franchise.orders.show` coexist. ## Pitfalls interviewers probe - **Believing Laravel throws on a duplicate at registration.** It does not; only caching does. - **Believing the last definition wins.** Treat the winner as undefined; the current collection keeps the first. - **Blaming the cache** when the deploy fails: the cache only revealed a bug that was already there. - **Worrying about unnamed routes**: they get generated names and never collide.

  • Why does a Laravel app with a duplicate route name work locally but fail during deployment?
    Locally, routes are usually not cached, so the collection just keeps one route per name and `route()` quietly resolves to it. The deploy step typically builds the route cache, and compiling routes throws `LogicException: Unable to prepare route [...] for serialization. Another route has already been assigned name [...]`. The duplicate existed all along; caching made it visible.
  • Do unnamed routes cause the same LogicException when routes are cached?
    No. During compilation every route without a name is given a random `generated::` name, so unnamed routes never collide. The same applies to a route whose only name is a group prefix ending in a dot, when that prefix repeats. Only real, explicit duplicate names fail.

saying these in an interview costs you the question

  • Laravel throws an exception as soon as a duplicate name is registered
  • The last route registered with a name is guaranteed to win
  • Unnamed routes collide with each other when routes are cached
  • A duplicate name stops both routes from matching their URLs
  • The route cache causes the duplicate-name bug