Skip to main content
사용자가 화면이나 메일 템플릿을 커스터마이즈할 수 있는 패키지에서는 뷰를 배포하는 것만으로는 부족하며, 배포된 파일을 유지한 채로 업데이트할 수 있는 계약이 필요합니다. 패키지 쪽 Blade 파일을 수정하더라도 사용하는 애플리케이션이 그 파일을 렌더링하고 있다고는 단정할 수 없습니다. 이 페이지에서는 Laravel 패키지 개발을 전제로 뷰의 선택, 파일 배포, 캐시를 나누어 생각합니다. 공식 문서는 Laravel 13, 프레임워크 구현은 최신 릴리스인 v13.34.0을 참조합니다.

등록과 배포는 별개의 처리

loadViewsFrom()은 네임스페이스에 검색 경로를 등록합니다. publishes()는 복사 원본과 복사 대상을 등록하며, 실제 복사는 vendor:publish가 수행합니다. 다음 예시에서는 배포하지 않아도 courier::deliveries.show를 사용할 수 있습니다.
src/CourierServiceProvider.php
패키지 쪽 파일은 resources/views/deliveries/show.blade.php에 둡니다. 뷰 이름의 점은 검색 시 디렉터리 구분자로 변환됩니다.
resources/views/deliveries/show.blade.php
뷰의 네임스페이스는 Composer 패키지 이름이나 PHP 네임스페이스와는 별개입니다. 여기서는 loadViewsFrom()의 두 번째 인수로 지정한 courier가 뷰 참조와 덮어쓰기 대상 디렉터리의 계약이 됩니다.

파일 단위로 덮어쓰기 대상을 찾는다

ServiceProvider::loadViewsFrom()은 view가 해석될 때 설정의 view.paths를 순서대로 확인합니다. 각 경로에 vendor/courier 디렉터리가 존재하면 해당 디렉터리를 네임스페이스에 추가하고, 마지막으로 패키지 쪽 경로를 추가합니다. FileViewFinder는 해당 네임스페이스의 경로를 순서대로 검색하여 처음 발견한 파일을 반환합니다. 기본 resources/views를 사용하는 구성에서는 다음 순서입니다. 이는 디렉터리 전체를 전환하는 것이 아닙니다. 사용자가 deliveries/show.blade.php만 덮어써도, 덮어쓰지 않은 다른 뷰는 패키지 쪽에서 로드됩니다.
view.paths가 여러 개인 구성에서는 덮어쓰기 대상도 여러 곳이 될 수 있습니다. resource_path('views/vendor/courier')는 이 예시의 배포 대상일 뿐이며, 검색 대상을 그곳으로만 고정하는 처리가 아닙니다. 네임스페이스는 패키지 고유의 것으로 하고, 여러 프로바이더에서 같은 이름에 경로를 추가하는 설계는 피하세요.

필요한 뷰만 커스터마이즈하기

사용자는 다음 명령어로 템플릿을 복사할 수 있습니다. 프로바이더와 태그를 지정하여 다른 리소스까지 함께 복사되지 않도록 합니다.
이 등록에서는 뷰 디렉터리 전체가 배포됩니다. 모두 덮어쓸 필요가 없다면 내용을 확인하고 커스터마이즈할 파일만 남기거나, 필요한 파일만 같은 상대 경로로 수동 복사하는 방법도 있습니다. 편집하지 않은 복사본도 존재하는 한 덮어쓰기로 취급되기 때문입니다.
배포된 템플릿은 패키지 업데이트와 자동으로 동기화되지 않습니다. 오래된 복사본이 우선되는 상태에서 패키지 쪽만 수정해도 해당 뷰의 변경은 반영되지 않습니다. 표시 버그 수정이나 폼 변경 등도 덮어쓰기 파일과의 비교가 필요합니다.

재배포는 차이를 병합하지 않는다

VendorPublishCommand는 일반적으로 같은 이름의 배포 대상 파일이 존재하면 복사를 건너뜁니다. --force는 기존 파일을 덮어씁니다. 또한 --existing도 “배포된 파일을 덮어쓰는” 옵션이며, 사용자의 편집을 보존하는 모드가 아닙니다. 어떤 방법도 구버전, 신버전, 사용자의 편집을 비교한 병합이 아닙니다. 패키지에서 삭제된 뷰를 배포 대상에서 자동으로 제거하는 처리도 아닙니다. 업데이트 절차를 “같은 태그를 재배포하기만 하면 된다”로 만들지 마세요.

뷰도 공개 API로서 유지보수하기

뷰 이름뿐 아니라 전달받는 데이터나 참조하는 부품도 사용자의 커스터마이즈에 영향을 줍니다. 예를 들어 신버전에서 trackingCode를 다른 변수명으로 변경하면, 구 템플릿을 유지하고 있는 사용자는 새 코드로부터 필요한 값을 받을 수 없게 됩니다. 릴리스 전에 다음 계약을 확인합니다.
  • 네임스페이스와 deliveries.show 같은 뷰 이름을 부주의하게 변경하지 않는다.
  • 전달하는 변수, 타입, 필수 여부를 기록한다.
  • @include나 @extends의 참조 대상, Blade 컴포넌트의 props도 변경 사항에 포함한다.
  • 수정한 뷰와, 배포된 구버전에 적용해야 할 변경을 릴리스 노트에 기재한다.
사용자를 위해 구버전의 패키지 뷰와 신버전을 비교하고, 커스터마이즈한 파일에 필요한 변경을 수동으로 반영하는 절차를 마련합니다. 덮어쓰기가 더 이상 필요 없는 파일은 백업이나 버전 관리로 변경 내용을 보전한 뒤 제거하면 패키지 쪽 뷰로 되돌릴 수 있습니다.

Blade 캐시는 덮어쓰기 파일을 업데이트하지 않는다

view:cache는 Blade 템플릿을 PHP로 미리 컴파일합니다. ViewCacheCommand는 먼저 view:clear를 실행하고, 일반 뷰 경로와 네임스페이스에 등록된 경로를 수집하여 컴파일 대상을 찾습니다.
이 처리는 배포된 Blade 파일을 다시 쓰지 않으며, 뷰의 검색 우선순위도 바꾸지 않습니다. 오래된 덮어쓰기 파일이 존재하면 캐시를 재구축해도 그 파일이 계속 선택됩니다. 배포(deploy) 시에는 코드와 덮어쓰기 파일의 업데이트를 마친 뒤 컴파일합니다. 개발 중에 컴파일된 파일을 지우고 다시 렌더링하고 싶다면 다음 명령어를 사용합니다.
일반적인 타임스탬프 확인이 활성화되어 있으면 Blade 컴파일러는 원본 파일과 컴파일된 파일의 수정 시각을 비교합니다. 다만 타임스탬프 확인을 비활성화하는 구성도 있으므로, 배포(deploy) 시의 재구축을 자동 판정에만 맡기지 마세요.

검색 결과 캐시와 구분하기

FileViewFinder::find()는 찾은 경로를 해당 Finder 인스턴스의 $views 배열에 저장합니다. 또한 덮어쓰기 디렉터리의 존재 확인은 loadViewsFrom()의 콜백에서 이루어집니다. 기동 후에 새 디렉터리를 추가해도 이미 등록된 검색 경로에 자동으로 추가되지는 않습니다. view:clear는 다른 실행 중인 프로세스가 보유한 Finder의 상태를 일괄로 지우는 명령어가 아닙니다. Octane 같은 장시간 실행되는 프로세스에서는 일반적인 배포(deploy) 절차에 따라 다시 로드하세요. Finder의 flush()는 검색 결과를 지우지만, 새 덮어쓰기 디렉터리의 등록까지 수행하는 처리는 아닙니다.

릴리스 전에 확인할 사항

패키지 테스트에 더해, 사용하는 애플리케이션에서 다음 조합을 확인합니다. 최신 템플릿만 렌더링하는 테스트로는 구버전을 배포한 사용자에 대한 호환성을 검증할 수 없습니다.
  • 배포하지 않은 상태에서 패키지의 뷰가 렌더링된다.
  • 하나만 덮어쓰면 그 파일만 우선되고, 나머지는 패키지로 폴백된다.
  • 구버전의 배포된 템플릿을 남긴 상태에서도 신버전이 전달하는 데이터로 렌더링할 수 있다.
  • 일반 재배포에서 커스터마이즈가 유지되고, 배포되지 않은 파일의 추가도 의도한 대로 이루어진다.
  • 덮어쓰기 파일 변경 후 view:cache가 성공하고, 새로 기동하면 변경 후의 표시가 된다.

관련 페이지

뷰

뷰 생성, 데이터 전달, 사전 컴파일의 기본을 확인합니다.

Blade 템플릿

레이아웃, include, 컴포넌트의 사용법을 확인합니다.

버전 호환성 관리

템플릿의 계약 변경을 릴리스 방침과 연결합니다.

Octane

장시간 실행되는 애플리케이션의 라이프사이클과 다시 로드를 확인합니다.

참조한 1차 자료

마지막 수정일 2026년 10월 4일