Laravel 11's Slim Application Structure: Why We're Not Going Back
Laravel 11 removed tons of boilerplate. Here's what changed, what we love, and the one thing we miss from Laravel 10.
We've been building Laravel apps at amillionmonkeys since version 5, and Laravel 11 just did something we didn't know we desperately needed: it deleted half our boilerplate.
When we upgraded our first production app from Laravel 10 to 11, we expected the usual "minor improvements" changelog. What we got was Laravel's most aggressive cleanup in years. Taylor Otwell and the team stripped out config files, middleware, service providers, and exception handlers that had been sitting untouched in every project since forever.
Here's what actually changed, what we've learned after migrating a dozen apps, and—being honest—the one feature we genuinely miss from Laravel 10.
What Laravel 11 Actually Removed
The headline is "streamlined application structure," but let's be specific about what disappeared:
Goodbye to 9 Config Files
Laravel 10 shipped with 11 config files by default. Laravel 11 ships with 2: app.php and database.php.
Gone:
broadcasting.phpcors.phphashing.phpsanctum.phpview.phplogging.phpqueue.phpfilesystems.phpsession.php
These aren't deleted from Laravel—they're just not generated anymore. The sensible defaults now live in framework code. If you need to customize something, you can still publish the config file:
php artisan config:publish corsWhy this matters: We had projects with 8 config files that had never been touched. Every new developer had to mentally parse 400+ lines of config on day one. Now they see 2 files, both actually relevant to the project.
No More Middleware Directory (Sort Of)
The entire app/Http/Middleware directory is gone from fresh installs. The 6 middleware classes that used to live there are now... well, they still exist, but they're registered in bootstrap/app.php using one-liners:
Laravel 10 way:
// app/Http/Middleware/TrustProxies.php (entire file)
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
protected $proxies;
protected $headers = Request::HEADER_X_FORWARDED_FOR;
}Laravel 11 way:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->trustProxies(at: '*');
})40 lines become 1. We're not complaining.
Bootstrap/App.php Is Now Everything
The new bootstrap/app.php file is where routing, middleware, exception handling, and service providers get configured:
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
//
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();It's Laravel's new single source of truth. Everything that used to be scattered across 5 different classes is now in one 30-line file.
No More Kernel Files
app/Http/Kernel.php and app/Console/Kernel.php are gone. Middleware groups, route middleware, and scheduled tasks are now configured in:
- HTTP middleware:
bootstrap/app.php - Console commands:
routes/console.php(using the newSchedulefacade)
Laravel 10 scheduled tasks:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
$schedule->command('emails:send')->daily();
}Laravel 11 scheduled tasks:
// routes/console.php
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')->daily();Same functionality, 80% less boilerplate.
Exception Handler Simplified
The app/Exceptions/Handler.php file that everyone copy-pasted from Stack Overflow? Gone. Exception handling is now in bootstrap/app.php:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (InvalidOrderException $e) {
// Custom reporting logic
});
})We actually use this one. It's much clearer what's custom exception logic vs. framework defaults.
What We Love About the Changes
After migrating our internal tools, 3 client projects, and a side project that'll probably never launch, here's what actually improved our workflow:
1. Onboarding Got Easier
We brought a junior developer onto a Laravel 11 project last month. They asked 40% fewer "what does this file do?" questions than on Laravel 10 projects. Less boilerplate = less confusion.
2. Reviewing PRs Is Faster
When someone submits a PR that adds middleware or an exception handler, the diff is tiny now. We're not scrolling through 50 lines of boilerplate to find the 3 lines that matter.
3. Fresh Installs Are Actually Fresh
Running composer create-project laravel/laravel gives you an app with 12 fewer files than Laravel 10. It feels like you're starting with a clean slate, not someone else's config decisions.
4. Bootstrap/App.php Is Surprisingly Readable
We were skeptical about cramming everything into one file. But in practice, it's easier to scan 30 lines in one place than hunt through 5 separate classes. The fluent API helps:
->withMiddleware(function (Middleware $middleware) {
$middleware->redirectGuestsTo('/login');
$middleware->trustProxies(at: '*');
$middleware->throttleApi();
})That's just... clearer than the old way.
The One Thing We Miss: Per-Route Rate Limiting Flexibility
Laravel 11 introduced per-second rate limiting, and it's legitimately useful:
Route::middleware('throttle:10,1')->group(function () {
// 10 requests per second (not per minute)
});Great feature. But here's what we lost: in Laravel 10, customizing rate limiting meant editing app/Http/Kernel.php. You could see all your rate limiters in one place, modify them, and add custom logic.
In Laravel 11, you define rate limiters in bootstrap/app.php, and it works fine for simple cases. But when we tried to add a custom rate limiter that checked user subscription tier, we hit a wall. The new fluent API doesn't expose the same level of customization.
We had to do this:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->throttleApi(); // Basic only
})Then create a custom service provider anyway to register our subscription-based rate limiter the old way.
Not a dealbreaker, but it's the one place where the new structure actually added friction for us. For 90% of projects, the built-in rate limiting is fine. For the 10% that need custom logic, you're back to creating files.
Migration: Easier Than Expected
We've migrated a dozen projects so far. Here's the honest timeline:
Small projects (< 20 routes): 1-2 hours Medium projects (50+ routes, custom middleware): Half a day Large projects (100+ routes, custom providers): 1-2 days
The Laravel upgrade guide is thorough. The hardest part was manually merging bootstrap/app.php if you had customizations in the old app/Providers/RouteServiceProvider.php.
One gotcha we hit: If you have custom middleware registered in app/Http/Kernel.php, you need to manually move them to bootstrap/app.php. The upgrade guide mentions this, but it's easy to miss if you're skimming.
Our process:
- Read the entire upgrade guide (seriously, don't skip this)
- Upgrade dependencies:
composer update - Merge
bootstrap/app.phpchanges - Move custom middleware/exception logic
- Test everything (we caught 2 issues with custom rate limiters)
- Deploy to staging, test again
No breaking changes in production across a dozen migrations. Laravel's backward compatibility here is solid.
Who Should Upgrade?
Upgrade now if:
- You're starting a new project (it's the default, you're already on 11)
- You have Laravel 10 projects with mostly untouched config files
- Your team frequently onboards new developers
- You're tired of maintaining boilerplate
Wait if:
- You have heavily customized exception handlers or middleware
- Your project relies on Laravel packages that haven't updated yet
- You're mid-deployment on a critical feature (don't upgrade during a sprint)
We've moved all new projects to Laravel 11 and are slowly migrating existing ones. The benefits outweigh the migration effort for us.
The Bigger Picture: Laravel Is Growing Up
This isn't just about deleting files. Laravel 11's cleanup reflects a mature framework that knows what developers actually use.
Most Laravel apps don't need custom CORS config. Most don't need to modify session drivers. Most don't touch the exception handler. So why generate those files by default?
The old structure made sense when Laravel was teaching PHP developers best practices. Now it's optimizing for teams shipping production apps quickly.
We're fans. The new structure removes friction without removing power. That's hard to pull off.
Key Takeaways
- 11 config files → 2: Laravel 11 only generates
app.phpanddatabase.phpby default - No more Kernel files: Middleware and scheduling are now in
bootstrap/app.php - Exception handling simplified: Custom exception logic lives in
bootstrap/app.php, not a separate class - Per-second rate limiting added:
throttle:10,1limits to 10 requests per second - Migration is smooth: 1-2 hours for small projects, 1-2 days for large ones
- One gotcha: Custom rate limiters are less flexible than Laravel 10
If you're on Laravel 10 and wondering if the upgrade is worth it: yes, especially if you value clean codebases. We've migrated a dozen apps with zero regrets.
Need help migrating to Laravel 11? We've done this enough times to have a checklist. Get in touch if you'd like our help avoiding the gotchas.