Route fallback — Route::fallback()
Concept
Route::fallback() registers a catch-all route that fires when no other route matches the incoming request. It's the 404 handler at the routing level — different from Laravel's exception handler's 404 page.
How it works: Route::fallback() must be defined last. Laravel tries all registered routes in order; if nothing matches, the fallback fires. If no fallback is registered and no route matches, Laravel's exception handler creates a \Symfony\Component\HttpKernel\Exception\NotFoundHttpException (HTTP 404).
Fallback vs exception handler: The exception handler (App\Exceptions\Handler) catches exceptions from anywhere in the application. The fallback route only catches "no route matched" cases. The fallback is preferable for custom 404 pages because it participates in the normal request pipeline (middleware runs, sessions are available, etc.) — unlike the exception handler which runs after the pipeline.
API fallback: Return a JSON 404 response for unmapped API routes:
Route::fallback(fn() => response()->json(['message' => 'Not Found'], 404));SPA fallback: Single-page applications handle routing client-side. The server should return index.html for any route that's not an API endpoint:
Route::fallback(fn() => view('app')); // React/Vue appMiddleware on fallback: The fallback route participates in middleware groups. To apply specific middleware to the fallback, chain it: Route::fallback([...]).middleware('api').
Code Example
<?php
use Illuminate\Support\Facades\Route;
// ===== WEB routes (routes/web.php) =====
Route::get('/', [HomeController::class, 'index']);
Route::get('/about', [PageController::class, 'about']);
// SPA fallback — serve the React/Vue app for all non-API routes
Route::fallback(function() {
return view('app'); // your SPA entry point
});
// ===== API routes (routes/api.php) =====
Route::prefix('v1')->group(function() {
Route::apiResource('users', UserController::class);
Route::apiResource('orders', OrderController::class);
});
// Custom JSON 404 for unmapped API routes
Route::fallback(function() {
return response()->json([
'message' => 'API endpoint not found',
'status' => 404,
], 404);
});
// Conditional fallback — different response based on request type
Route::fallback(function(\Illuminate\Http\Request $request) {
if ($request->expectsJson()) {
return response()->json(['message' => 'Not Found'], 404);
}
return response()->view('errors.404', [], 404);
});
// Custom 404 with logging
Route::fallback(function(\Illuminate\Http\Request $request) {
\Illuminate\Support\Facades\Log::warning('Route not found', [
'url' => $request->fullUrl(),
'method' => $request->method(),
'ip' => $request->ip(),
]);
if ($request->expectsJson()) {
return response()->json(['error' => 'Not found'], 404);
}
return response()->view('errors.404', [], 404);
});
// IMPORTANT: Register fallback LAST — after all other routes
// In routes/web.php, the fallback should be the final Route:: call