> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 패키지의 라우트 등록과 캐시

> Laravel 13의 구현을 바탕으로 loadRoutesFrom의 역할, 미들웨어와 이름의 분리, 설정 변경을 라우트 캐시에 반영하는 절차를 해설합니다.

패키지가 HTTP 엔드포인트를 제공하는 경우, 개발 환경에서 라우트가 동작하는 것만으로는 부족하며 사용하는 애플리케이션이 라우트 캐시를 생성한 뒤에도 같은 계약으로 동작해야 합니다. URL 프리픽스나 활성화·비활성화를 설정으로 변경할 수 있는 설계라면, 그 변경을 언제 반영하는지도 사용자에게 안내합니다.

이 페이지에서는 [Laravel 패키지 개발](/ko/advanced/package-development)을 전제로 등록 처리와 캐시의 라이프사이클을 나누어 생각합니다. 공식 문서는 Laravel 13의 기본 브랜치 `13.x`, 프레임워크 구현은 최신 릴리스인 `v13.34.0`을 참조합니다.

## loadRoutesFrom은 파일을 로드할 뿐

`ServiceProvider::loadRoutesFrom()`은 애플리케이션이 `CachesRoutes`를 구현하고 `routesAreCached()`가 참인 경우에는 라우트 파일을 로드하지 않습니다. 그 외의 경우에는 지정한 파일을 `require`합니다.

이 메서드 자체는 URI나 라우트 이름의 프리픽스, 컨트롤러 네임스페이스, 미들웨어를 추가하지 않습니다. 파일 배포나 기존 캐시에 라우트를 추가하는 일도 하지 않습니다.

```mermaid theme={null}
flowchart TD
    A["프로바이더의 boot"] --> B["loadRoutesFrom 호출"]
    B --> C{"라우트 캐시가 있는가?"}
    C -->|아니요| D["패키지의 라우트 파일을 require"]
    C -->|예| E["패키지의 파일 로드를 건너뜀"]
    E --> F["Laravel의 RouteServiceProvider가<br>애플리케이션의 캐시를 로드"]
```

그림은 표준 Laravel 애플리케이션을 전제로 합니다. 패키지 전용 캐시가 있는 것이 아니라, 애플리케이션 전체의 라우트 캐시에 패키지의 라우트도 포함됩니다.

<Warning>
  패키지 내 파일 이름을 `routes/web.php`로 하더라도 그것만으로는 `web` 미들웨어가 적용되지 않습니다. 애플리케이션 쪽 표준 라우트 파일과는 로드 경로가 다르므로, 필요한 미들웨어를 패키지 쪽에서 명시하세요.
</Warning>

## 설정과 등록을 분리하기

다음 예시에서는 패키지가 응답 가능한지를 반환하는 공개 엔드포인트를 만듭니다. Composer의 PSR-4로 `Acme\Courier\`를 `src/`에 대응시키고, 프로바이더를 [자동 감지](/ko/advanced/package-discovery) 또는 수동으로 등록했다고 가정합니다.

```php config/courier.php theme={null}
<?php

return [
    'routes' => [
        'enabled' => true,
        'prefix' => 'acme-courier',
    ],
];
```

설정 병합은 `register()`, 라우트 로드는 `boot()`에서 수행합니다. HTTP 라우트를 등록하는 프로바이더를 `DeferrableProvider`로 만들지 마세요. 라우트가 필요한 시점에 프로바이더가 부팅된다는 보장이 없어지기 때문입니다.

```php src/CourierServiceProvider.php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->mergeConfigFrom(__DIR__.'/../config/courier.php', 'courier');
    }

    public function boot(): void
    {
        if (config('courier.routes.enabled')) {
            $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
        }
    }
}
```

이 조건은 라우트 등록만 제어합니다. 다른 서비스나 뷰도 등록하는 프로바이더에서는 그것들을 이 조건 안에 넣지 않도록 합니다. 설정을 사용자에게 배포하는 방법이나 중첩된 설정 병합에 관한 주의점은 [패키지 설정 병합과 캐시](/ko/advanced/package-config-merging)를 참조하세요.

```php routes/web.php theme={null}
<?php

use Acme\Courier\Http\Controllers\StatusController;
use Illuminate\Support\Facades\Route;

Route::middleware('web')
    ->prefix(config('courier.routes.prefix'))
    ->name('acme-courier.')
    ->group(function () {
        Route::get('/status', StatusController::class)->name('status');
    });
```

```php src/Http/Controllers/StatusController.php theme={null}
<?php

namespace Acme\Courier\Http\Controllers;

use Illuminate\Http\JsonResponse;

class StatusController
{
    public function __invoke(): JsonResponse
    {
        return response()->json(['available' => true]);
    }
}
```

기본 URI는 `/acme-courier/status`, 라우트 이름은 `acme-courier.status`입니다. `route('acme-courier.status')`로 URL을 생성하면 URI 프리픽스를 변경해도 호출하는 쪽은 같은 라우트 이름을 사용할 수 있습니다. 그룹의 `name()`은 문자열을 그대로 연결하므로 끝의 `.`도 지정합니다.

<Info>
  `web`은 인증·인가를 대신하지 않습니다. 이 예시는 기밀 정보를 포함하지 않는 공개 엔드포인트입니다. 사용자의 데이터를 반환하는 엔드포인트에는 사양에 맞는 인증 미들웨어와 인가 처리를 별도로 마련하세요.
</Info>

## URI와 라우트 이름의 충돌을 따로 방지하기

URI 프리픽스와 라우트 이름 프리픽스는 별개의 구조입니다. 한쪽만 붙여서는 다른 쪽의 충돌을 막을 수 없습니다.

| 대상 | 이 예시의 설계 | 유지보수상의 주의 |
| - | - | - |
| URI | `acme-courier`를 기본값으로 하고 설정으로 변경 가능 | 사용하는 애플리케이션의 기존 URL과 충돌하지 않는 값을 선택한다 |
| 라우트 이름 | `acme-courier.`로 고정 | 패키지 고유의 이름으로 하고 URL 생성의 계약으로 유지한다 |
| 컨트롤러 | 클래스 참조를 사용 | 앱 쪽 컨트롤러 네임스페이스에 의존시키지 않는다 |
| 미들웨어 | `web`을 명시 | 대상 앱의 미들웨어 구성과 맞춰 확인한다 |

`AbstractRouteCollection`은 캐시용 라우트 컬렉션을 만들 때 다른 라우트에 같은 이름이 붙어 있으면 `LogicException`을 던지는 처리를 가지고 있습니다. "일반 부팅에서 URL을 생성할 수 있었다"는 것만으로는 캐시 가능하다고 보장할 수 없습니다. URI가 다른 두 라우트라도 이름이 같으면 문제가 됩니다.

사용하는 애플리케이션의 라우트를 등록 순서로 덮어쓰는 것을 패키지의 확장 방법으로 삼지 마세요. 필요하다면 라우트를 비활성화하는 설정과, 사용자가 별도의 라우트에서 호출할 수 있는 서비스를 제공합니다.

## 캐시 생성 시의 설정이 라우트 정의에 남는다

`RouteCacheCommand`는 먼저 `route:clear`를 실행하고, 새 애플리케이션을 부팅하여 라우트를 수집합니다. 그 라우트를 직렬화할 수 있는 형태로 준비하고, 컴파일 결과를 캐시 파일에 기록합니다.

이때 패키지의 라우트 파일도 로드되므로, 프리픽스나 등록 여부는 **캐시 생성 시의 설정**으로 결정됩니다. 이후 부팅에서는 `loadRoutesFrom()`이 파일을 로드하지 않고 캐시된 라우트가 사용됩니다.

| 변경 | 오래된 라우트 캐시가 남은 경우 | 필요한 대응 |
| - | - | - |
| 패키지에 라우트 추가 | 추가한 라우트가 나타나지 않는다 | 라우트 캐시를 다시 생성한다 |
| `routes.prefix` 변경 | 원래 URI가 남는다 | 새 설정으로 다시 생성한다 |
| `routes.enabled`를 `false`로 변경 | 캐시에 있는 라우트는 사라지지 않는다 | 비활성화한 설정으로 다시 생성한다 |
| 패키지 삭제 | 삭제한 클래스를 참조하는 정의가 남을 수 있다 | 삭제 후의 구성으로 다시 생성한다 |

<Warning>
  `routes.enabled`는 등록을 제어하는 설정이며, 요청마다의 접근 거부가 아닙니다. 오래된 캐시가 남아 있는 상태에서 설정만 비활성화해도 엔드포인트를 중지한 것이 되지 않습니다.
</Warning>

사용자나 테넌트 등 요청마다 바뀌는 조건으로 라우트를 등록하지 마세요. 그 조건은 캐시 생성 시의 CLI 환경에서 평가됩니다. 라우트는 안정적인 구성으로 등록하고, 접근 가능 여부는 미들웨어나 컨트롤러 내의 인가로 판단합니다.

### 배포에서는 설정을 먼저 확정하기

코드와 설정의 업데이트를 마친 뒤, 설정 캐시를 사용하는 구성에서는 다음 순서로 다시 생성합니다. 사용하는 애플리케이션의 배포 처리에 포함시키세요.

```bash theme={null}
php artisan config:cache
php artisan route:cache
php artisan route:list --name=acme-courier -vv
```

오래된 설정 캐시가 남은 채로 `route:cache`를 실행하면 라우트도 오래된 설정으로 만들어집니다. `config:cache`만 다시 실행해도 라우트 캐시는 업데이트되지 않습니다. `-vv`로 미들웨어 그룹의 내용도 확인할 수 있습니다.

개발 중에 캐시 없이 동작을 확인하는 경우에는 필요에 따라 둘 다 지웁니다.

```bash theme={null}
php artisan config:clear
php artisan route:clear
```

라우트 파일은 캐시가 있는 부팅에서는 실행되지 않습니다. 그 안에서 이벤트 리스너나 컨테이너 바인딩을 등록하면 동작이 달라지므로, 라우트 정의 이외의 부작용을 갖게 하지 마세요. 장시간 실행되는 프로세스를 사용하는 환경에서는 캐시 업데이트 후의 다시 로드도 일반적인 배포 절차에 포함합니다.

## 릴리스 전에 확인할 조합

패키지 테스트에 더해, Laravel 13을 사용하는 애플리케이션에서 다음 조합을 확인합니다. 인메모리 라우트 등록뿐 아니라 Artisan이 새 애플리케이션을 부팅하는 경로도 대상으로 합니다.

* 캐시 없이 `/acme-courier/status`가 응답하고, 라우트 이름과 미들웨어가 예상대로이다.
* `route:cache`가 성공하고, 새로운 부팅에서도 같은 URI·라우트 이름으로 응답한다.
* 프리픽스를 변경하고 캐시를 다시 생성하면 새 URI가 응답하고, 이전 URI의 패키지 라우트가 사라진다.
* 비활성화하고 캐시를 다시 생성하면 `route:list --name=acme-courier`에 대상 라우트가 나오지 않는다.
* 사용하는 애플리케이션이나 다른 패키지와 URI·라우트 이름이 충돌하지 않는다.

오래된 캐시를 남기는 경우도 확인하면 "설정 파일은 바꿨는데 URL이 바뀌지 않는다"는 사용자의 보고를 재현할 수 있습니다. 캐시 재생성을 업그레이드 절차에 명시하고, 라우트 이름이나 미들웨어의 변경도 호환성 검토 대상으로 삼습니다.

## 관련 페이지

<Columns cols={2}>
  <Card title="라우팅" icon="route" href="/ko/routing">
    라우트 그룹, 이름 있는 라우트, 목록 표시의 기본을 확인합니다.
  </Card>

  <Card title="패키지 설정 병합과 캐시" icon="sliders" href="/ko/advanced/package-config-merging">
    배포된 설정과 설정 캐시를 고려한 업데이트 절차를 확인합니다.
  </Card>

  <Card title="지연 서비스 프로바이더" icon="clock" href="/ko/advanced/deferred-provider">
    라우트를 등록하는 프로바이더를 지연시키지 않는 이유를 확인합니다.
  </Card>

  <Card title="패키지의 버전 호환성 관리" icon="code-branch" href="/ko/advanced/package-versioning">
    공개 API의 변경을 릴리스 방침과 지속적인 검증에 연결합니다.
  </Card>
</Columns>

## 참조한 1차 자료

* [Laravel 공식 문서: 패키지의 라우트](https://github.com/laravel/docs/blob/156fc7fde114548640e13c39aa79b991291c3f91/packages.md#routes)
* [Laravel 공식 문서: 라우팅](https://github.com/laravel/docs/blob/156fc7fde114548640e13c39aa79b991291c3f91/routing.md)
* [ServiceProvider: loadRoutesFrom 구현](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Support/ServiceProvider.php)
* [RouteServiceProvider: 캐시 로드](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php)
* [RouteCacheCommand: 새 애플리케이션에서의 수집과 저장](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Console/RouteCacheCommand.php)
* [AbstractRouteCollection: 라우트 이름 중복 감지](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Routing/AbstractRouteCollection.php)


## Related topics

- [Laravel 패키지 개발](/ko/advanced/package-development.md)
- [고급 주제](/ko/advanced/index.md)
- [라우트](/ko/packages/laravel-bluesky/route.md)
- [패키지 자동 감지의 내부 구조](/ko/advanced/package-discovery.md)
- [Laravel Sentinel — 라우트 보호 미들웨어 조사](/ko/blog/sentinel-introduction.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.