Route caching — php artisan route:cache internals
Concept
php artisan route:cache serializes the entire route collection into a single PHP file at bootstrap/cache/routes-v7.php (the filename version bumps with Laravel releases). On subsequent requests, the bootstrapper Illuminate\Foundation\Bootstrap\LoadRoutes detects the cached file and loads it instead of executing routes/web.php and routes/api.php. This skips all route registration code, which can be a significant saving in large applications with hundreds of routes and complex provider chains.
The serialization format is a PHP array returned from bootstrap/cache/routes-v7.php — not a serialized PHP object, not JSON. The array contains the compiled route data: URI patterns, compiled regexes, HTTP methods, action arrays (controller FQCN + method name), middleware lists, parameter constraints, name mappings, and domain configurations. When Laravel loads this cache file, it passes the array to Illuminate\Routing\RouteCollection::fromCachedRoutes(), which reconstitutes Route objects without re-executing any registration closures.
Because the cache file stores fully compiled regexes, route matching becomes an O(1) array lookup by method and URI literal for static routes, and a compiled pattern match for parameterized ones. This avoids re-compiling {param} patterns on every request, which is the main performance gain.
There are two critical constraints: (1) All routes must use class-based actions (controller FQCN + method). Closure-based routes — Route::get('/ping', function () { ... }) — cannot be serialized and will throw LogicException: Unable to prepare route... when you run route:cache. (2) The cache file reflects the state of routes at cache time. Any changes to route files require re-running route:cache. In production deployments, this command runs in the deployment pipeline after code is deployed.
The complement commands are route:clear (deletes the cache file, routes are re-parsed on each request) and route:list (shows the current effective routes, reading from cache if present). Never run route:cache in development — the cache won't reflect your edits until you manually clear it.
Code Example
<?php
// =============================================
// CORRECT: Controller-based route — cacheable
// =============================================
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
Route::get('/users', [UserController::class, 'index'])->name('users.index');
Route::post('/users', [UserController::class, 'store'])->name('users.store');
// =============================================
// WRONG: Closure route — NOT cacheable
// =============================================
Route::get('/ping', function () {
return response()->json(['status' => 'ok']);
});
// Running route:cache with this present throws:
// LogicException: Unable to prepare route [/ping] for serialization.
// Use a controller instead.
// =============================================
// Replace closure with an invokable controller
// =============================================
// app/Http/Controllers/PingController.php
class PingController extends Controller
{
public function __invoke(): JsonResponse
{
return response()->json(['status' => 'ok']);
}
}
Route::get('/ping', PingController::class)->name('ping');
// =============================================
// Deployment pipeline commands
// =============================================
// composer install --no-dev --optimize-autoloader
// php artisan config:cache
// php artisan route:cache
// php artisan view:cache
// =============================================
// What the cache file looks like (simplified)
// bootstrap/cache/routes-v7.php
// =============================================
// <?php
// return [
// 'compiled' => [
// 'GET' => [
// '/users' => ['action' => ['App\Http\Controllers\UserController', 'index'], ...],
// ],
// ],
// 'attributes' => [ ... ],
// ];Interview Q&A
Q: Why can't closure-based routes be cached, and what is the technical reason serialization fails?
PHP closures (anonymous functions) cannot be serialized to a PHP array or string in a way that can be reconstituted without re-running the original code. When route:cache serializes the route collection, it iterates every route's action and attempts to serialize it. For closure actions, PHP's serialize() throws a LogicException because closures capture variable bindings and reference the runtime bytecode, neither of which is representable as plain data. The solution is always to use named controller methods, which are serializable as ['App\Http\Controllers\UserController', 'index'] — a plain array of strings.
Q: After running route:cache in production, a developer adds a new route to routes/api.php. What happens when someone requests the new URL?
The new route does not exist in the cached file. The router only loads routes from bootstrap/cache/routes-v7.php — the actual route files are never executed. The new URL returns a 404 until php artisan route:cache is run again on the production server. This is why route caching must be part of every deployment step, not a one-time setup. In practice, this is the most common gotcha with route caching: developers forget that the cache is stale after a deploy that adds routes but omits route:cache.
Q: Does route caching affect route model binding or middleware execution?
Route model binding and middleware are fully supported with cached routes. The cache file stores the action (controller class + method), which carries the type-hinted parameters that drive implicit binding. The middleware list is also serialized — each middleware is stored as its class name or alias string. At dispatch time, the kernel resolves these strings to actual middleware instances through the container just as it does without caching. The only difference is that route registration closures in routes/*.php are never executed; everything else — binding, middleware, URL generation — works identically.