메인 콘텐츠로 건너뛰기

시작하며

이 가이드에서는, Laravel 10 이전의 구 애플리케이션 구조(Kernel 클래스나 여러 서비스 프로바이더를 가진 구조)에서, Laravel 11 이후의 Slim Application Skeleton으로 이관하는 절차를 해설합니다.
공식 문서는 이 이관을 권장하고 있지 않습니다.
  • Laravel 10의 구 구조는 Laravel 11 이후에서도 그대로 동작합니다. Laravel 13을 포함한 현재 버전에 이르기까지, 서포트 종료의 예정은 없습니다.
  • 이관은 완전히 임의입니다. 강제도 권장도 아닙니다.
  • 구 구조와 새 구조의 차이를 깊이 이해하고 있지 않은 경우에는, 이관을 피하는 것을 강하게 권합니다. 프레임워크 내부에 정통한 개발자용의 작업입니다.
  • 이관 전에 반드시 백업을 하고, 테스트가 모두 통과되는지를 확인해 주세요.

이관이 필요해지는 장면

이하와 같은 경우에 이관을 검토할 수 있습니다.
  • 신규 참가 멤버가 공식 문서와 대조하기 쉽도록, 프로젝트를 최신의 표준 구조에 맞추고 싶음
  • Laravel 11 이후에서 신규 작성한 패키지나 스타터 킷과의 정합성을 유지하고 싶음
  • 구 구조 유래의 설정 파일이나 클래스를 줄이고, 코드베이스를 심플하게 하고 싶음

전제 조건

이 가이드에서는, 이하의 상태를 전제로 합니다.
  • Laravel 버전 업그레이드가 완료되어 있음(laravel/framework ^11.0 이후)
  • 기존의 테스트가 모두 통과하고 있음
  • Laravel 11 이후의 애플리케이션 구조(Laravel 11 이후의 애플리케이션 구조를 참조)를 이해하고 있음

이관 예

Laravel 10 + Breeze(Blade 스택)로 작성한 프로젝트를 Breeze 그대로 유지하면서, 애플리케이션 구조만을 새 구조로 이관하는 절차를 보입니다.
1

bootstrap/app.php를 치환한다

bootstrap/app.php$app 인스턴스를 생성하여 커널을 등록하는 형식이었습니다. 이것을 Application::configure() 체인으로 치환합니다.구(Laravel 10):
<?php

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;
신(Laravel 11 이후):
<?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',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();
withMiddleware()withExceptions()의 콜백은, 다음 스텝에서 삭제하는 커널 파일의 설정을 이식하는 장소입니다. 우선은 비운 채로 하고, 후속 스텝에서 추가 기재합니다.
2

HTTP 커널(app/Http/Kernel.php)을 삭제한다

app/Http/Kernel.php에는 글로벌 미들웨어, 미들웨어 그룹, 미들웨어 별칭이 정의되어 있었습니다.구(app/Http/Kernel.php):
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    protected $middleware = [
        \App\Http\Middleware\TrustProxies::class,
        \Illuminate\Http\Middleware\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
        'api' => [
            \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    protected $middlewareAliases = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        // ...
    ];
}
Laravel 11에서는, 위의 미들웨어는 프레임워크 내부에 기본값으로서 짜넣어져 있습니다. 커스터마이즈가 없는 경우에는, app/Http/Kernel.php를 그대로 삭제할 수 있습니다.커스터마이즈가 있는 경우(독자 미들웨어를 추가·제외하고 있는 경우)에는, bootstrap/app.phpwithMiddleware()로 이식한 뒤 삭제해 주세요.
->withMiddleware(function (Middleware $middleware) {
    // グローバルミドルウェアの追加
    $middleware->append(\App\Http\Middleware\MyCustomMiddleware::class);

    // web グループにミドルウェアを追加
    $middleware->web(append: [
        \App\Http\Middleware\HandleInertiaRequests::class,
    ]);

    // ミドルウェアエイリアスの追加
    $middleware->alias([
        'role' => \App\Http\Middleware\CheckRole::class,
    ]);
})
이식이 완료되면 app/Http/Kernel.php를 삭제합니다.
Laravel 11에서는 TrustProxies, EncryptCookies, VerifyCsrfToken 등의 기본 미들웨어 클래스도 app/Http/Middleware/에서 삭제할 수 있습니다. 프레임워크 측에 내장되어 있으므로, 커스터마이즈가 불필요하다면 파일 자체가 불필요해집니다.
3

콘솔 커널(app/Console/Kernel.php)을 삭제한다

app/Console/Kernel.php는 스케줄의 정의와 커맨드의 자동 로드를 담당했습니다.구(app/Console/Kernel.php):
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    protected function schedule(Schedule $schedule): void
    {
        // $schedule->command('inspire')->hourly();
    }

    protected function commands(): void
    {
        $this->load(__DIR__.'/Commands');
        require base_path('routes/console.php');
    }
}
커맨드의 자동 로드는 Laravel 11 이후에서는 app/Console/Commands/ 디렉토리가 자동적으로 스캔되므로, $this->load()의 기술은 불필요해졌습니다.스케줄의 정의routes/console.php 또는 bootstrap/app.phpwithSchedule()로 이관합니다.
// routes/console.php(Laravel 11 以降)
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->daily();
이관 후에 app/Console/Kernel.php를 삭제합니다.
app/Console/ 디렉토리 내의 커스텀 Artisan 커맨드 파일은 삭제하지 마세요. 커맨드 파일은 그대로 남기고, 커널 클래스의 파일만을 삭제합니다.
4

예외 핸들러(app/Exceptions/Handler.php)를 삭제한다

app/Exceptions/Handler.php는 예외 리포트와 렌더링의 설정을 담당했습니다.구(app/Exceptions/Handler.php):
<?php

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;

class Handler extends ExceptionHandler
{
    protected $dontFlash = [
        'current_password',
        'password',
        'password_confirmation',
    ];

    public function register(): void
    {
        $this->reportable(function (Throwable $e) {
            //
        });
    }
}
커스터마이즈가 있는 경우에는 bootstrap/app.phpwithExceptions()로 이식한 뒤 삭제합니다.
use Throwable;
use Illuminate\Http\Request;

->withExceptions(function (Exceptions $exceptions) {
    // 例外レポートのカスタマイズ
    $exceptions->report(function (Throwable $e) {
        // ...
    });

    // 例外レンダリングのカスタマイズ
    $exceptions->render(function (\App\Exceptions\InvalidOrderException $e, Request $request) {
        return response()->view('errors.invalid-order', status: 500);
    });
})
$dontFlash에 독자의 항목을 추가하고 있던 경우에는 마찬가지로 이식할 수 있습니다.
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->dontFlash([
        'my_sensitive_field',
    ]);
})
이식이 완료되면 app/Exceptions/Handler.php를 삭제합니다.
5

RouteServiceProvider를 삭제하고 라우트 등록을 이관한다

app/Providers/RouteServiceProvider.php는 라우트 파일의 로드와 레이트 리밋의 설정을 하고 있었습니다.구(app/Providers/RouteServiceProvider.php):
<?php

namespace App\Providers;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;

class RouteServiceProvider extends ServiceProvider
{
    public const HOME = '/home';

    public function boot(): void
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });

        $this->routes(function () {
            Route::middleware('api')
                ->prefix('api')
                ->group(base_path('routes/api.php'));

            Route::middleware('web')
                ->group(base_path('routes/web.php'));
        });
    }
}
라우트 파일의 등록bootstrap/app.phpwithRouting()으로 이관합니다.
->withRouting(
    web: __DIR__.'/../routes/web.php',
    api: __DIR__.'/../routes/api.php', // API ルートを使う場合
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
)
레이트 리밋AppServiceProvider::boot()로 이관합니다.
// app/Providers/AppServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });
}
HOME 상수를 사용하고 있는 곳이 있는 경우에는, 직접 URL 문자열로 치환하거나, AppServiceProvider에 상수를 이동해 주세요.이관 후에 app/Providers/RouteServiceProvider.php를 삭제합니다.
6

서비스 프로바이더를 정리한다

Laravel 10에서는 기본으로 5개의 서비스 프로바이더가 준비되어 있었습니다. 이것들을 AppServiceProvider.php 하나로 통합합니다.삭제할 프로바이더(내용을 AppServiceProvider로 옮긴 뒤 삭제):
파일이관처
app/Providers/AuthServiceProvider.phpAppServiceProvider::boot()Gate::policy()
app/Providers/BroadcastServiceProvider.phpBroadcasting을 사용하는 경우에는 bootstrap/app.phpwithBroadcasting()
app/Providers/EventServiceProvider.phpAppServiceProvider::boot()Event::listen()
app/Providers/RouteServiceProvider.php이전 스텝에서 대응 완료
구(app/Providers/AuthServiceProvider.php)의 내용을 이관하는 예:
// 旧: app/Providers/AuthServiceProvider.php
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    public function boot(): void
    {
        $this->registerPolicies();
    }
}
// 新: app/Providers/AppServiceProvider.php
// Laravelの標準的な命名規則に従っているなら自動検出されるので不要です
use Illuminate\Support\Facades\Gate;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Gate::policy(Post::class, PostPolicy::class);
    }
}
불필요해진 프로바이더 파일을 삭제하면, config/app.phpproviders 배열을 삭제합니다.
// config/app.php の providers 配列
// 全て削除して構いません
'providers' => ServiceProvider::defaultProviders()->merge([
App\Providers\AppServiceProvider::class,
// 削除したプロバイダーのエントリも削除
// App\Providers\AuthServiceProvider::class, // 削除
// App\Providers\EventServiceProvider::class, // 削除
// App\Providers\RouteServiceProvider::class, // 削除
])->toArray(),
또한, bootstrap/providers.php를 작성하여 새 구조에 대응합니다.
<?php

// bootstrap/providers.php
return [
    App\Providers\AppServiceProvider::class,
];
bootstrap/providers.php가 존재하는 경우, Laravel은 이쪽을 프로바이더 목록으로서 우선하여 로드합니다.
7

컨트롤러 베이스 클래스를 갱신한다

Laravel 10의 Controller 베이스 클래스는 AuthorizesRequestsValidatesRequests 트레이트를 사용하고 있었습니다. Laravel 11의 새 베이스 클래스는 이러한 트레이트를 가지지 않는 심플한 추상 클래스입니다.구(Laravel 10):
<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController
{
    use AuthorizesRequests, ValidatesRequests;
}
신(Laravel 11 이후):
<?php

namespace App\Http\Controllers;

abstract class Controller
{
    //
}
트레이트가 제공하고 있던 기능은 다음과 같이 치환합니다.
구의 방법(트레이트 경유)신의 방법
$this->validate($request, $rules)$request->validate($rules)
$this->authorize('update', $post)Gate::authorize('update', $post)
$this->authorizeResource(Post::class)Laravel11에서는 간단한 대체 수단이 없으므로 App\Http\Controllers\Controller를 Laravel10 사양 그대로 둡니다
기존의 컨트롤러가 트레이트의 메서드를 사용하고 있는 경우에는, 각 컨트롤러를 수정하거나, 트레이트를 Controller 베이스 클래스에 남기거나를 선택할 수 있습니다. 한꺼번에 모두를 변경하지 않아도 동작은 합니다.
Breeze나 Jetstream이 생성한 인증 컨트롤러가 $this->validate()$this->authorize()를 호출하고 있는 경우에는, 베이스 클래스를 변경하기 전에 반드시 동작 확인해 주세요.
8

불필요한 config 파일을 삭제한다

config/cors.php, config/hashing.php, config/view.php 등 기본에서 변경하지 않은 파일은 삭제할 수 있습니다. 변경하고 있는 파일은 남겨 주세요.
9

public/index.php를 갱신한다

새 구조에 맞춰 변경되어 있으므로 전면적으로 다시 씁니다.
<?php

use Illuminate\Foundation\Application;
use Illuminate\Http\Request;

define('LARAVEL_START', microtime(true));

// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}

// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';

// Bootstrap Laravel and handle the request...
/** @var Application $app */
$app = require_once __DIR__.'/../bootstrap/app.php';

$app->handleRequest(Request::capture());
10

artisan을 갱신한다

artisan 파일도 마찬가지로 모두 다시 씁니다.
#!/usr/bin/env php
<?php

use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;

define('LARAVEL_START', microtime(true));

// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';

// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';

$status = $app->handleCommand(new ArgvInput);

exit($status);
11

tests/TestCase.php를 갱신한다

CreatesApplication 트레이트가 불필요해지므로 변경합니다. tests/CreatesApplication.php는 삭제해도 상관없습니다.
<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    //
}
12

.env .env.example phpunit.xml을 갱신한다

CACHE_DRIVER에서 CACHE_STORE로 바뀌거나 항목이 늘어나 있으므로 필요하다면 변경합니다. 이 부근의 변경은 config 파일이나 본번 환경도 관계되므로 신중히 해 주세요. 무리하게 추종할 필요는 없습니다.
13

동작 확인

이관이 완료되면, 이하의 순서로 동작 확인을 합니다.
# 設定キャッシュのクリア
php artisan config:clear
php artisan route:clear
php artisan cache:clear

# ルートが正しく登録されているか確認
php artisan route:list

# テストを実行
php artisan test
문제가 발생한 경우에는, 삭제한 파일을 백업에서 복원하고, 에러 메시지를 확인해 주세요.

이관 후의 파일 구성

이관 후의 프로젝트는, 이하와 같은 구성이 됩니다(구 구조로부터의 변경점을 보임).
app/
├── Console/
│   └── Commands/          ← カスタムコマンドはそのまま残す
│   (Kernel.php は削除)
├── Exceptions/
│   (Handler.php は削除)
├── Http/
│   ├── Controllers/
│   │   └── Controller.php ← シンプルな抽象クラスに変更
│   └── Middleware/        ← カスタムミドルウェアはそのまま残す
│   (Kernel.php は削除)
├── Models/
└── Providers/
    └── AppServiceProvider.php ← 1 ファイルに統合
    (AuthServiceProvider.php は削除)
    (BroadcastServiceProvider.php は削除)
    (EventServiceProvider.php は削除)
    (RouteServiceProvider.php は削除)
bootstrap/
├── app.php                ← Application::configure() 形式に置き換え
└── providers.php          ← 新規作成
routes/
├── web.php
├── api.php                ← 必要な場合のみ
└── console.php            ← スケジュール定義もここに

정리

이관 내용구의 장소신의 장소
HTTP 미들웨어app/Http/Kernel.phpbootstrap/app.phpwithMiddleware()
예외 핸들링app/Exceptions/Handler.phpbootstrap/app.phpwithExceptions()
스케줄 정의app/Console/Kernel.phproutes/console.php
라우트 등록app/Providers/RouteServiceProvider.phpbootstrap/app.phpwithRouting()
서비스 프로바이더 목록config/app.phpproviders 배열bootstrap/providers.php
폴리시 등록app/Providers/AuthServiceProvider.php자동 등록. AppServiceProvider::boot()에서 수동 등록
이벤트 리스너app/Providers/EventServiceProvider.php자동 등록. AppServiceProvider::boot()에서 수동 등록

참고 링크

마지막 수정일 2026년 7월 13일