본문 바로가기

Blade 템플릿

업데이트됨

번역일: 2026년 9월 30일

이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.

원문 수정
2026년 9월 30일
번역 갱신
2026년 9월 30일

Blade 템플릿

Blade 템플릿

소개

Blade는 라라벨에 기본으로 포함된 템플릿 엔진으로, 간단하면서도 강력한 기능을 제공합니다. 다른 PHP 템플릿 엔진과 달리, Blade는 템플릿 안에서 순수 PHP 코드를 사용하는 것을 막지 않습니다. 실제로 모든 Blade 템플릿은 순수 PHP 코드로 컴파일된 뒤 캐싱되며, 이 컴파일 결과는 템플릿 파일이 변경되기 전까지 그대로 재사용됩니다. 즉, Blade는 애플리케이션에 사실상 아무런 오버헤드를 추가하지 않습니다. Blade 템플릿 파일은 .blade.php 확장자를 사용하며, 보통 resources/views 디렉터리에 저장합니다.

Blade 뷰는 전역 view 헬퍼를 사용해 라우트나 컨트롤러에서 반환할 수 있습니다. 뷰 문서에서 설명한 것처럼, view 헬퍼의 두 번째 인자를 통해 뷰에 데이터를 전달할 수도 있습니다.

Route::get('/', function () { return view('greeting', ['name' => 'Finn']); });

Livewire로 Blade를 한층 강력하게

Blade 템플릿을 한 단계 더 발전시켜 동적인 화면을 손쉽게 만들고 싶다면 Laravel Livewire를 살펴보세요. Livewire를 사용하면 React, Svelte, Vue 같은 프론트엔드 프레임워크에서나 가능했던 동적 기능을 Blade 컴포넌트에 그대로 담아낼 수 있습니다. 덕분에 복잡한 클라이언트 사이드 렌더링이나 별도의 빌드 과정 없이도, 최신 트렌드에 맞는 반응형 프론트엔드를 구축할 수 있는 훌륭한 대안이 됩니다.

NOTE

많은 국내 라라벨 프로젝트에서 Vue.js나 React 같은 별도 SPA 프레임워크 대신 Blade + Livewire 조합을 채택하는 경우가 늘고 있습니다. 백엔드 개발자가 별도의 프론트엔드 빌드 체인(Webpack, Vite 설정 등)을 깊이 알지 못해도 동적인 UI를 구현할 수 있다는 점이 큰 장점입니다.

데이터 표시하기

Blade 뷰에 전달된 데이터는 변수를 중괄호로 감싸서 화면에 표시할 수 있습니다. 예를 들어 다음과 같은 라우트가 있다고 가정해 보겠습니다.

Route::get('/', function () { return view('welcome', ['name' => 'Samantha']); });

이때 name 변수의 값은 이렇게 출력할 수 있습니다.

Hello, {{ $name }}.

NOTE

Blade의 {{ }} 출력 구문은 자동으로 PHP의 htmlspecialchars 함수를 거치기 때문에 XSS 공격을 방지할 수 있습니다.

뷰에 전달된 변수만 출력할 수 있는 것은 아닙니다. PHP 함수의 실행 결과도 얼마든지 출력할 수 있습니다. 사실 Blade의 출력 구문 안에는 원하는 어떤 PHP 코드든 넣을 수 있습니다.

The current UNIX timestamp is {{ time() }}.

HTML 엔터티 인코딩

Blade(그리고 Laravel의 e 함수)는 기본적으로 HTML 엔터티를 이중으로 인코딩합니다. 이중 인코딩을 비활성화하고 싶다면 AppServiceProvider의 boot 메서드에서 Blade::withoutDoubleEncoding 메서드를 호출하면 됩니다.

<?php namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. */ public function boot(): void { Blade::withoutDoubleEncoding(); } }

이스케이프 없이 데이터 표시하기

앞서 설명했듯 Blade의 {{ }} 구문은 기본적으로 htmlspecialchars 함수를 거쳐 XSS 공격을 방지합니다. 만약 데이터를 이스케이프 처리 없이 그대로 출력하고 싶다면 다음과 같은 문법을 사용하면 됩니다.

Hello, {!! $name !!}.

WARNING

사용자가 입력한 콘텐츠를 출력할 때는 각별히 주의해야 합니다. 사용자가 입력한 데이터를 표시할 때는 XSS 공격을 막기 위해 대부분의 경우 이스케이프 처리가 되는 이중 중괄호 문법을 사용하는 것이 좋습니다.

Blade와 JavaScript 프레임워크

Vue나 React 같은 여러 JavaScript 프레임워크 역시 브라우저에 표현식을 출력할 때 중괄호를 사용합니다. 이런 경우 @ 기호를 사용하면 Blade 렌더링 엔진에게 "이 표현식은 건드리지 말고 그대로 두라"고 알려줄 수 있습니다. 예를 들면 다음과 같습니다.

<h1>Laravel</h1> Hello, @{{ name }}.

이 예제에서 @ 기호는 Blade에 의해 제거되지만, {{ name }} 표현식 자체는 Blade 엔진의 손을 타지 않고 그대로 남아 있게 됩니다. 덕분에 이 부분은 JavaScript 프레임워크가 렌더링할 수 있습니다.

@ 기호는 Blade 디렉티브 자체를 이스케이프할 때도 사용할 수 있습니다.

{{-- Blade 템플릿 --}} @@if() <!-- HTML 출력 결과 --> @if()

JSON 렌더링하기

JavaScript 변수를 초기화하기 위해 배열을 뷰에 전달하고, 이를 JSON으로 렌더링해야 하는 경우가 있습니다. 예를 들면 다음과 같습니다.

<script> var app = <?php echo json_encode($array); ?>; </script>

하지만 이렇게 json_encode를 직접 호출하는 대신, Illuminate\Support\Js::from 메서드를 사용할 수 있습니다. from 메서드는 PHP의 json_encode 함수와 동일한 인자를 받으면서도, 결과로 생성되는 JSON이 HTML 큰따옴표 안에 안전하게 포함될 수 있도록 적절히 이스케이프 처리를 해줍니다. from 메서드는 전달받은 객체나 배열을 유효한 JavaScript 객체로 변환하는 JSON.parse 구문 문자열을 반환합니다.

<script> var app = {{ Illuminate\Support\Js::from($array) }}; </script>

최신 버전의 Laravel 애플리케이션 스켈레톤에는 Js 파사드가 포함되어 있어서, Blade 템플릿 안에서 이 기능을 더 편리하게 사용할 수 있습니다.

<script> var app = {{ Js::from($array) }}; </script>

WARNING

Js::from 메서드는 이미 존재하는 변수를 JSON으로 렌더링하는 용도로만 사용해야 합니다. Blade 템플릿 엔진은 정규 표현식을 기반으로 동작하기 때문에, 복잡한 표현식을 디렉티브에 전달하려고 하면 예상치 못한 오류가 발생할 수 있습니다.

`@verbatim` 디렉티브

템플릿의 넓은 영역에 걸쳐 JavaScript 변수를 표시해야 한다면, 매번 Blade 출력 구문 앞에 @ 기호를 붙이는 대신 해당 HTML 영역 전체를 @verbatim 디렉티브로 감싸는 방법을 사용할 수 있습니다.

@verbatim <div class="container"> Hello, {{ name }}. </div> @endverbatim

Blade 지시어

템플릿 상속과 데이터 출력 외에도, Blade는 조건문이나 반복문처럼 자주 사용하는 PHP 제어 구조를 위한 간편한 단축 문법을 제공합니다. 이 지시어들은 PHP 원본 문법과 매우 비슷해서 익숙하게 사용할 수 있으면서도, 훨씬 간결하고 깔끔하게 코드를 작성할 수 있게 해줍니다.

If 문

@if, @elseif, @else, @endif 지시어로 if 문을 작성할 수 있습니다. 동작 방식은 PHP의 if 문과 완전히 동일합니다.

@if (count($records) === 1) 레코드가 1개 있습니다! @elseif (count($records) > 1) 레코드가 여러 개 있습니다! @else 레코드가 없습니다! @endif

편의를 위해 Blade는 @unless 지시어도 제공합니다.

@unless (Auth::check()) 로그인되어 있지 않습니다. @endunless

이미 살펴본 조건 지시어 외에도, @isset과 @empty는 각각 PHP의 isset(), empty() 함수를 대신하는 단축 문법으로 사용할 수 있습니다.

@isset($records) // $records가 정의되어 있고 null이 아닙니다... @endisset @empty($records) // $records가 "비어" 있습니다... @endempty

인증 관련 지시어

@auth와 @guest 지시어를 사용하면 현재 사용자가 인증되어 있는지, 혹은 게스트(비로그인 사용자)인지 손쉽게 확인할 수 있습니다.

@auth // 사용자가 인증되어 있습니다... @endauth @guest // 사용자가 인증되어 있지 않습니다... @endguest

필요하다면 @auth와 @guest 지시어에 확인할 인증 가드(guard)를 지정할 수도 있습니다.

@auth('admin') // 사용자가 인증되어 있습니다... @endauth @guest('admin') // 사용자가 인증되어 있지 않습니다... @endguest

환경(Environment) 관련 지시어

@production 지시어를 사용하면 애플리케이션이 프로덕션 환경에서 실행 중인지 확인할 수 있습니다.

@production // 프로덕션 환경 전용 콘텐츠... @endproduction

또는 @env 지시어로 특정 환경에서 실행 중인지 확인할 수도 있습니다.

@env('staging') // 애플리케이션이 "staging" 환경에서 실행 중입니다... @endenv @env(['staging', 'production']) // 애플리케이션이 "staging" 또는 "production" 환경에서 실행 중입니다... @endenv

섹션(Section) 관련 지시어

@hasSection 지시어를 사용하면 템플릿 상속의 특정 섹션에 내용이 존재하는지 확인할 수 있습니다.

@hasSection('navigation') <div class="pull-right"> @yield('navigation') </div> <div class="clearfix"></div> @endif

반대로 sectionMissing 지시어를 사용하면 특정 섹션에 내용이 없는지 확인할 수 있습니다.

@sectionMissing('navigation') <div class="pull-right"> @include('default-navigation') </div> @endif

세션(Session) 관련 지시어

@session 지시어를 사용하면 세션 값이 존재하는지 확인할 수 있습니다. 세션 값이 존재하면 @session과 @endsession 사이의 내용이 렌더링됩니다. 이 블록 안에서는 $value 변수를 사용해 세션 값을 바로 출력할 수 있습니다.

@session('status') <div class="p-4 bg-green-100"> {{ $value }} </div> @endsession

컨텍스트(Context) 관련 지시어

@context 지시어를 사용하면 컨텍스트 값이 존재하는지 확인할 수 있습니다. 컨텍스트 값이 존재하면 @context와 @endcontext 사이의 내용이 렌더링됩니다. 이 블록 안에서는 $value 변수로 해당 컨텍스트 값을 출력할 수 있습니다.

@context('canonical') <link href="{{ $value }}" rel="canonical"> @endcontext

Switch 문

@switch, @case, @break, @default, @endswitch 지시어를 사용해 switch 문을 작성할 수 있습니다.

@switch($i) @case(1) 첫 번째 케이스... @break @case(2) 두 번째 케이스... @break @default 기본 케이스... @endswitch

반복문(Loops)

조건문뿐 아니라 Blade는 PHP의 반복 구조를 위한 지시어도 제공합니다. 이 역시 각 지시어는 대응하는 PHP 문법과 동일하게 동작합니다.

@for ($i = 0; $i < 10; $i++) 현재 값은 {{ $i }} 입니다 @endfor @foreach ($users as $user) <p>이 사용자는 {{ $user->id }} 입니다</p> @endforeach @forelse ($users as $user) <li>{{ $user->name }}</li> @empty <p>사용자가 없습니다</p> @endforelse @while (true) <p>무한히 반복합니다.</p> @endwhile

NOTE

foreach 반복문을 순회하는 동안에는 $loop 변수를 사용해 현재 반복이 첫 번째인지 마지막인지 등 유용한 정보를 알아낼 수 있습니다.

반복문을 사용할 때 @continue와 @break 지시어로 현재 반복을 건너뛰거나 반복문 자체를 종료할 수 있습니다.

@foreach ($users as $user) @if ($user->type == 1) @continue @endif <li>{{ $user->name }}</li> @if ($user->number == 5) @break @endif @endforeach

조건을 지시어 선언부 안에 직접 포함시킬 수도 있습니다.

@foreach ($users as $user) @continue($user->type == 1) <li>{{ $user->name }}</li> @break($user->number == 5) @endforeach

`$loop` 변수

foreach 반복문 안에서는 $loop 변수를 사용할 수 있습니다. 이 변수를 통해 현재 반복의 인덱스, 첫 번째/마지막 반복 여부 등 유용한 정보에 접근할 수 있습니다.

@foreach ($users as $user) @if ($loop->first) 첫 번째 반복입니다. @endif @if ($loop->last) 마지막 반복입니다. @endif <p>이 사용자는 {{ $user->id }} 입니다</p> @endforeach

중첩된 반복문 안에서는 parent 프로퍼티를 통해 부모 반복문의 $loop 변수에 접근할 수 있습니다.

@foreach ($users as $user) @foreach ($user->posts as $post) @if ($loop->parent->first) 부모 반복문의 첫 번째 반복입니다. @endif @endforeach @endforeach

$loop 변수는 이 외에도 다양한 유용한 프로퍼티를 제공합니다.

프로퍼티설명
$loop->index현재 반복의 인덱스입니다(0부터 시작).
$loop->iteration현재 반복 횟수입니다(1부터 시작).
$loop->remaining남은 반복 횟수입니다.
$loop->count순회 중인 배열(또는 컬렉션)의 전체 항목 수입니다.
$loop->first현재 반복이 첫 번째인지 여부입니다.
$loop->last현재 반복이 마지막인지 여부입니다.
$loop->even현재 반복이 짝수 번째인지 여부입니다.
$loop->odd현재 반복이 홀수 번째인지 여부입니다.
$loop->depth현재 반복문의 중첩 깊이입니다.
$loop->parent중첩된 반복문일 경우, 부모 반복문의 $loop 변수입니다.

조건부 클래스 & 스타일

@class 지시어는 조건에 따라 CSS 클래스 문자열을 조합해 컴파일합니다. 이 지시어는 배열을 인자로 받으며, 배열의 키는 추가하고자 하는 클래스(하나 이상), 값은 그 클래스를 적용할지 여부를 나타내는 불리언 표현식입니다. 배열 요소의 키가 숫자인 경우에는 조건과 무관하게 항상 클래스 목록에 포함됩니다.

@php $isActive = false; $hasError = true; @endphp <span @class([ 'p-4', 'font-bold' => $isActive, 'text-gray-500' => ! $isActive, 'bg-red' => $hasError, ])></span> <span class="p-4 text-gray-500 bg-red"></span>

마찬가지로 @style 지시어를 사용하면 HTML 요소에 조건부로 인라인 CSS 스타일을 추가할 수 있습니다.

@php $isActive = true; @endphp <span @style([ 'background-color: red', 'font-weight: bold' => $isActive, ])></span> <span style="background-color: red; font-weight: bold;"></span>

그 외 편의 속성 지시어

@checked 지시어를 사용하면 특정 HTML 체크박스 input이 "checked" 상태여야 하는지 간편하게 표시할 수 있습니다. 조건식이 true로 평가되면 checked를 출력합니다.

<input type="checkbox" name="active" value="active" @checked(old('active', $user->active)) />

마찬가지로 @selected 지시어는 특정 select 옵션이 "selected" 상태여야 하는지 표시할 때 사용합니다.

<select name="version"> @foreach ($product->versions as $version) <option value="{{ $version }}" @selected(old('version') == $version)> {{ $version }} </option> @endforeach </select>

또한 @disabled 지시어는 특정 요소를 "disabled" 상태로 표시할 때 사용합니다.

<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>

@readonly 지시어는 특정 요소를 "readonly" 상태로 표시할 때 사용합니다.

<input type="email" name="email" value="email@laravel.com" @readonly($user->isNotAdmin()) />

@required 지시어는 특정 요소를 "required" 상태로 표시할 때 사용합니다.

<input type="text" name="title" value="title" @required($user->isAdmin()) />

서브뷰 포함하기

NOTE

@include 지시어를 자유롭게 사용해도 무방하지만, Blade 컴포넌트는 데이터/속성 바인딩 등 @include보다 더 많은 이점을 제공하는 유사한 기능을 지원합니다.

Blade의 @include 지시어를 사용하면 다른 뷰 안에 특정 Blade 뷰를 포함시킬 수 있습니다. 부모 뷰에서 사용 가능한 모든 변수는 포함된(included) 뷰에서도 그대로 사용할 수 있습니다.

<div> @include('shared.errors') <form> <!-- 폼 내용 --> </form> </div>

포함된 뷰는 부모 뷰의 데이터를 그대로 상속받지만, 추가로 전달하고 싶은 데이터가 있다면 배열 형태로 넘길 수도 있습니다.

@include('view.name', ['status' => 'complete'])

존재하지 않는 뷰를 @include로 포함시키려 하면 Laravel은 에러를 발생시킵니다. 존재할 수도, 존재하지 않을 수도 있는 뷰를 포함시키고 싶다면 @includeIf 지시어를 사용하세요.

@includeIf('view.name', ['status' => 'complete'])

주어진 불리언 표현식이 true 또는 false일 때만 뷰를 @include하고 싶다면 @includeWhen과 @includeUnless 지시어를 사용할 수 있습니다.

@includeWhen($boolean, 'view.name', ['status' => 'complete']) @includeUnless($boolean, 'view.name', ['status' => 'complete'])

주어진 뷰 배열 중 실제로 존재하는 첫 번째 뷰를 포함시키고 싶다면 @includeFirst 지시어를 사용하세요.

@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])

부모 뷰의 변수를 전혀 상속받지 않고 뷰를 포함시키고 싶다면 @includeIsolated 지시어를 사용할 수 있습니다. 이 경우 포함된 뷰는 명시적으로 전달한 변수에만 접근할 수 있습니다.

@includeIsolated('view.name', ['user' => $user])

WARNING

Blade 뷰 안에서는 __DIR__, __FILE__ 상수를 사용하지 않는 것이 좋습니다. 이 상수들은 실제 뷰 파일이 아니라 캐시된 컴파일 뷰 파일의 위치를 가리키기 때문입니다.

컬렉션(Collection)을 위한 뷰 렌더링

Blade의 @each 지시어를 사용하면 반복문과 뷰 포함을 한 줄로 결합할 수 있습니다.

@each('view.name', $jobs, 'job')

@each 지시어의 첫 번째 인자는 배열(또는 컬렉션)의 각 요소마다 렌더링할 뷰입니다. 두 번째 인자는 순회 대상이 되는 배열이나 컬렉션이며, 세 번째 인자는 현재 반복 요소에 할당할 변수명입니다. 예를 들어 jobs라는 배열을 순회한다면, 보통 뷰 안에서 각 항목을 job이라는 변수명으로 접근하고 싶을 것입니다. 또한 현재 반복의 배열 키는 뷰 안에서 key 변수로 사용할 수 있습니다.

@each 지시어에는 네 번째 인자를 전달할 수도 있습니다. 이 인자는 주어진 배열이 비어 있을 때 렌더링할 뷰를 지정합니다.

@each('view.name', $jobs, 'job', 'view.empty')

WARNING

@each로 렌더링된 뷰는 부모 뷰의 변수를 상속받지 않습니다. 자식 뷰에서 부모 뷰의 변수가 필요하다면 @foreach와 @include 지시어 조합을 사용해야 합니다.

`@once` 지시어

@once 지시어를 사용하면 템플릿의 특정 영역이 렌더링 사이클당 단 한 번만 평가되도록 만들 수 있습니다. 예를 들어 스택(stack)을 이용해 페이지 헤더에 특정 자바스크립트를 추가하고 싶을 때 유용합니다. 반복문 안에서 특정 컴포넌트를 렌더링하는 경우, 컴포넌트가 처음 렌더링될 때만 자바스크립트를 헤더에 추가하고 싶은 상황을 생각해 보세요.

@once @push('scripts') <script> // 커스텀 자바스크립트... </script> @endpush @endonce

@once 지시어는 @push나 @prepend와 함께 사용되는 경우가 많기 때문에, 편의를 위해 @pushOnce와 @prependOnce 지시어도 제공됩니다.

@pushOnce('scripts') <script> // 커스텀 자바스크립트... </script> @endPushOnce

서로 다른 두 개의 Blade 템플릿에서 동일한 콘텐츠를 push하는 경우, @pushOnce의 두 번째 인자로 고유 식별자를 지정하면 해당 콘텐츠가 단 한 번만 렌더링되도록 보장할 수 있습니다.

<!-- pie-chart.blade.php --> @pushOnce('scripts', 'chart.js') <script src="/chart.js"></script> @endPushOnce <!-- line-chart.blade.php --> @pushOnce('scripts', 'chart.js') <script src="/chart.js"></script> @endPushOnce

순수 PHP 코드 사용하기

경우에 따라 뷰 안에 PHP 코드를 직접 삽입하는 것이 편리할 때가 있습니다. Blade의 @php 지시어를 사용하면 템플릿 안에서 순수 PHP 코드 블록을 실행할 수 있습니다.

@php $counter = 1; @endphp

단순히 클래스를 임포트하기 위한 목적이라면 @use 지시어를 사용할 수도 있습니다.

@use('App\Models\Flight')

@use 지시어에 두 번째 인자를 전달하면 임포트한 클래스에 별칭(alias)을 지정할 수 있습니다.

@use('App\Models\Flight', 'FlightModel')

같은 네임스페이스 안에 여러 클래스가 있다면, 임포트를 그룹으로 묶을 수도 있습니다.

@use('App\Models\{Flight, Airport}')

@use 지시어는 임포트 경로 앞에 function이나 const 수식어를 붙여서 PHP 함수나 상수를 임포트하는 것도 지원합니다.

@use(function App\Helpers\format_currency) @use(const App\Constants\MAX_ATTEMPTS)

클래스 임포트와 마찬가지로, 함수와 상수 임포트에도 별칭을 지정할 수 있습니다.

@use(function App\Helpers\format_currency, 'formatMoney') @use(const App\Constants\MAX_ATTEMPTS, 'MAX_TRIES')

function, const 수식어에도 그룹 임포트를 사용할 수 있어서, 같은 네임스페이스에 속한 여러 심볼을 하나의 지시어로 임포트할 수 있습니다.

@use(function App\Helpers\{format_currency, format_date}) @use(const App\Constants\{MAX_ATTEMPTS, DEFAULT_TIMEOUT})

폰트(Fonts)

Laravel Vite 폰트 최적화 기능을 사용하는 경우, @fonts 지시어를 이용해 설정된 폰트의 preload 링크와 인라인 폰트 CSS를 애플리케이션 레이아웃에 렌더링할 수 있습니다.

<!doctype html> <head> {{-- ... --}} @fonts @vite('resources/js/app.js') </head>

@fonts 지시어는 vite.config.js 파일에 설정된 모든 폰트 패밀리를 렌더링합니다. 이 지시어는 일반적으로 해당 폰트를 사용하는 콘텐츠보다 앞선 위치, 즉 애플리케이션 루트 레이아웃의 <head> 태그 안에 배치해야 합니다.

특정 페이지에서 설정된 폰트 중 일부만 필요하다면, 지시어에 폰트 별칭을 하나 이상 전달할 수 있습니다.

{{-- 단일 폰트 별칭 로드... --}} @fonts('sans') {{-- 여러 폰트 별칭 로드... --}} @fonts(['sans', 'mono'])

폰트 별칭은 Vite 설정 파일에서 폰트를 정의할 때 alias 옵션으로 지정합니다. @fonts 지시어는 내부적으로 Vite 파사드가 제공하는 fonts 메서드를 호출하며, 이 메서드는 다음과 같이 직접 호출할 수도 있습니다.

{{ Vite::fonts(['sans', 'mono']) }}

주석(Comments)

Blade에서도 뷰 안에 주석을 작성할 수 있습니다. 단, HTML 주석과 달리 Blade 주석은 애플리케이션이 반환하는 최종 HTML에 포함되지 않습니다.

{{-- 이 주석은 렌더링된 HTML에 포함되지 않습니다 --}}

Components (컴포넌트)

컴포넌트와 슬롯은 섹션, 레이아웃, include와 비슷한 역할을 하지만, 사람에 따라서는 컴포넌트와 슬롯이라는 개념 모델이 더 이해하기 쉬울 수 있습니다. 컴포넌트를 작성하는 방식에는 클래스 기반 컴포넌트와 익명 컴포넌트, 두 가지가 있습니다.

클래스 기반 컴포넌트를 만들려면 make:component Artisan 명령어를 사용하면 됩니다. 사용법을 익히기 위해 간단한 Alert 컴포넌트를 만들어보겠습니다. make:component 명령어는 컴포넌트 클래스를 app/View/Components 디렉터리에 생성합니다:

php artisan make:component Alert

이 명령어는 컴포넌트의 뷰 템플릿도 함께 생성합니다. 뷰 파일은 resources/views/components 디렉터리에 위치합니다. 자신의 애플리케이션을 위한 컴포넌트를 작성할 때는 app/View/Components 디렉터리와 resources/views/components 디렉터리 안의 컴포넌트가 자동으로 인식되므로, 별도의 등록 작업이 대부분 필요하지 않습니다.

하위 디렉터리 안에 컴포넌트를 만들 수도 있습니다:

php artisan make:component Forms/Input

위 명령어를 실행하면 app/View/Components/Forms 디렉터리에 Input 컴포넌트가 생성되고, 뷰는 resources/views/components/forms 디렉터리에 생성됩니다.

패키지 컴포넌트 수동 등록하기

자신의 애플리케이션용 컴포넌트를 작성할 때는 app/View/Components 디렉터리와 resources/views/components 디렉터리 안의 컴포넌트가 자동으로 인식됩니다.

하지만 Blade 컴포넌트를 사용하는 패키지를 만드는 경우에는 컴포넌트 클래스와 HTML 태그 별칭을 직접 등록해야 합니다. 보통 패키지의 서비스 프로바이더 boot 메서드에서 컴포넌트를 등록합니다:

use Illuminate\Support\Facades\Blade; /** * 패키지의 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::component('package-alert', Alert::class); }

컴포넌트를 등록하고 나면 태그 별칭으로 렌더링할 수 있습니다:

<x-package-alert/>

또는 componentNamespace 메서드를 사용해 컨벤션에 따라 컴포넌트 클래스를 자동으로 로드할 수도 있습니다. 예를 들어 Nightshade라는 패키지가 Package\Views\Components 네임스페이스 안에 Calendar, ColorPicker 컴포넌트를 가지고 있다고 가정해보겠습니다:

use Illuminate\Support\Facades\Blade; /** * 패키지의 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); }

이렇게 하면 package-name:: 문법을 사용해 벤더 네임스페이스별로 패키지 컴포넌트를 사용할 수 있습니다:

<x-nightshade::calendar /> <x-nightshade::color-picker />

Blade는 컴포넌트 이름을 파스칼 케이스로 변환하여 연결된 클래스를 자동으로 찾아냅니다. 하위 디렉터리는 "점(dot)" 표기법으로 지원됩니다.

컴포넌트 렌더링하기

컴포넌트를 화면에 표시하려면 Blade 템플릿 안에서 Blade 컴포넌트 태그를 사용하면 됩니다. Blade 컴포넌트 태그는 x-로 시작하고, 그 뒤에 컴포넌트 클래스 이름을 케밥 케이스로 표기합니다:

<x-alert/> <x-user-profile/>

컴포넌트 클래스가 app/View/Components 디렉터리 안에서 더 깊은 위치에 있다면 . 문자를 사용해 디렉터리 중첩을 표현할 수 있습니다. 예를 들어 컴포넌트가 app/View/Components/Inputs/Button.php에 있다면 다음과 같이 렌더링합니다:

<x-inputs.button/>

컴포넌트를 조건에 따라 렌더링하고 싶다면 컴포넌트 클래스에 shouldRender 메서드를 정의하면 됩니다. shouldRender 메서드가 false를 반환하면 해당 컴포넌트는 렌더링되지 않습니다:

use Illuminate\Support\Str; /** * 컴포넌트를 렌더링해야 하는지 여부를 판단합니다. */ public function shouldRender(): bool { return Str::length($this->message) > 0; }

인덱스 컴포넌트

컴포넌트가 하나의 그룹에 속해 있어서 관련 컴포넌트들을 한 디렉터리에 모아두고 싶은 경우가 있습니다. 예를 들어 다음과 같은 클래스 구조를 가진 "card" 컴포넌트를 생각해봅시다:

App\View\Components\Card\Card
App\View\Components\Card\Header
App\View\Components\Card\Body

루트 Card 컴포넌트가 Card라는 디렉터리 안에 중첩되어 있으니 <x-card.card>처럼 렌더링해야 할 것 같지만, 컴포넌트 파일 이름이 그 컴포넌트가 속한 디렉터리 이름과 같으면 Laravel은 해당 컴포넌트를 "루트" 컴포넌트로 자동 인식합니다. 따라서 디렉터리 이름을 반복하지 않고 렌더링할 수 있습니다:

<x-card> <x-card.header>...</x-card.header> <x-card.body>...</x-card.body> </x-card>

컴포넌트에 데이터 전달하기

Blade 컴포넌트에는 HTML 속성(attribute)을 통해 데이터를 전달할 수 있습니다. 하드코딩된 단순 값은 일반 HTML 속성 문자열로 전달하면 되고, PHP 표현식이나 변수는 속성 앞에 : 문자를 붙여 전달해야 합니다:

<x-alert type="error" :message="$message"/>

컴포넌트가 받을 데이터 속성은 모두 클래스 생성자에 정의해야 합니다. 컴포넌트의 public 속성은 자동으로 컴포넌트 뷰에서 사용할 수 있게 됩니다. 즉, render 메서드에서 뷰로 데이터를 따로 전달할 필요가 없습니다:

<?php namespace App\View\Components; use Illuminate\View\Component; use Illuminate\View\View; class Alert extends Component { /** * 컴포넌트 인스턴스를 생성합니다. */ public function __construct( public string $type, public string $message, ) {} /** * 컴포넌트를 나타내는 뷰 / 콘텐츠를 가져옵니다. */ public function render(): View { return view('components.alert'); } }

컴포넌트가 렌더링될 때, public 변수의 이름을 출력하여 그 값을 화면에 표시할 수 있습니다:

<div class="alert alert-{{ $type }}"> {{ $message }} </div>

대소문자 표기 규칙 (Casing)

컴포넌트 생성자의 인자 이름은 camelCase로 작성하지만, HTML 속성에서 해당 인자를 참조할 때는 kebab-case를 사용해야 합니다. 예를 들어 다음과 같은 생성자가 있다고 가정해봅시다:

/** * 컴포넌트 인스턴스를 생성합니다. */ public function __construct( public string $alertType, ) {}

$alertType 인자는 다음과 같이 전달할 수 있습니다:

<x-alert alert-type="danger" />

짧은 속성 문법 (Short Attribute Syntax)

컴포넌트에 속성을 전달할 때 "짧은 속성" 문법을 사용할 수도 있습니다. 속성 이름이 대응하는 변수 이름과 같은 경우가 많기 때문에 이 문법이 편리할 때가 많습니다:

{{-- 짧은 속성 문법... --}} <x-profile :$userId :$name /> {{-- 다음과 동일합니다... --}} <x-profile :user-id="$userId" :name="$name" />

속성 렌더링 이스케이프하기

Alpine.js 같은 일부 자바스크립트 프레임워크도 콜론(:)으로 시작하는 속성을 사용하기 때문에, 이런 속성이 PHP 표현식이 아니라는 것을 Blade에게 알려주려면 더블 콜론(::) 접두사를 사용하면 됩니다. 다음 컴포넌트를 예로 들어보겠습니다:

<x-button ::class="{ danger: isDeleting }"> Submit </x-button>

Blade는 다음과 같은 HTML을 렌더링합니다:

<button :class="{ danger: isDeleting }"> Submit </button>

컴포넌트 메서드

컴포넌트 템플릿에서는 public 변수뿐만 아니라 컴포넌트의 public 메서드도 호출할 수 있습니다. 예를 들어 isSelected라는 메서드를 가진 컴포넌트가 있다고 가정해봅시다:

/** * 주어진 옵션이 현재 선택된 옵션인지 판단합니다. */ public function isSelected(string $option): bool { return $option === $this->selected; }

메서드 이름과 일치하는 변수를 호출하는 방식으로 컴포넌트 템플릿에서 이 메서드를 실행할 수 있습니다:

<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}"> {{ $label }} </option>

컴포넌트 클래스 안에서 속성 및 슬롯 접근하기

Blade 컴포넌트에서는 클래스의 render 메서드 안에서도 컴포넌트 이름, 속성, 슬롯에 접근할 수 있습니다. 다만 이런 데이터에 접근하려면 render 메서드가 클로저를 반환해야 합니다:

use Closure; /** * 컴포넌트를 나타내는 뷰 / 콘텐츠를 가져옵니다. */ public function render(): Closure { return function () { return '<div {{ $attributes }}>Components content</div>'; }; }

render 메서드가 반환하는 클로저는 유일한 인자로 $data 배열을 받을 수도 있습니다. 이 배열에는 컴포넌트에 관한 정보를 담은 몇 가지 요소가 들어 있습니다:

return function (array $data) { // $data['componentName']; // $data['attributes']; // $data['slot']; return '<div {{ $attributes }}>Components content</div>'; };

WARNING

$data 배열의 요소를 render 메서드가 반환하는 Blade 문자열에 직접 삽입해서는 안 됩니다. 악의적인 속성 내용이 포함될 경우 원격 코드 실행(RCE)으로 이어질 수 있습니다.

componentName은 HTML 태그에서 x- 접두사 뒤에 사용된 이름과 동일합니다. 즉 <x-alert />의 componentName은 alert가 됩니다. attributes 요소에는 HTML 태그에 존재했던 모든 속성이 담깁니다. slot 요소는 컴포넌트 슬롯의 내용을 담은 Illuminate\Support\HtmlString 인스턴스입니다.

이 클로저는 문자열을 반환해야 합니다. 반환된 문자열이 기존 뷰의 이름과 일치하면 해당 뷰가 렌더링되고, 그렇지 않으면 반환된 문자열이 인라인 Blade 뷰로 평가됩니다.

추가 의존성 주입하기

컴포넌트가 Laravel 서비스 컨테이너에 등록된 의존성이 필요한 경우, 컴포넌트의 데이터 속성보다 앞쪽에 나열하면 컨테이너가 자동으로 주입해 줍니다:

use App\Services\AlertCreator; /** * 컴포넌트 인스턴스를 생성합니다. */ public function __construct( public AlertCreator $creator, public string $type, public string $message, ) {}

속성 / 메서드 숨기기

일부 public 메서드나 속성을 컴포넌트 템플릿에 변수로 노출시키고 싶지 않다면, 컴포넌트의 $except 배열 속성에 추가하면 됩니다:

<?php namespace App\View\Components; use Illuminate\View\Component; class Alert extends Component { /** * 컴포넌트 템플릿에 노출하지 않을 속성 / 메서드 목록입니다. * * @var array */ protected $except = ['type']; /** * 컴포넌트 인스턴스를 생성합니다. */ public function __construct( public string $type, ) {} }

컴포넌트 속성 (Component Attributes)

지금까지는 컴포넌트에 데이터 속성을 전달하는 방법을 살펴봤습니다. 하지만 때로는 컴포넌트가 동작하는 데 필요한 데이터는 아니지만 class처럼 추가적인 HTML 속성을 지정해야 할 때가 있습니다. 보통 이런 속성들은 컴포넌트 템플릿의 최상위(root) 엘리먼트로 전달하고 싶을 것입니다. 예를 들어 다음과 같이 alert 컴포넌트를 렌더링한다고 가정해봅시다:

<x-alert type="error" :message="$message" class="mt-4"/>

컴포넌트의 생성자에 정의되지 않은 모든 속성은 자동으로 컴포넌트의 "속성 가방(attribute bag)"에 담깁니다. 이 속성 가방은 $attributes 변수를 통해 컴포넌트 안에서 자동으로 사용할 수 있습니다. 이 변수를 출력하면 모든 속성을 컴포넌트 안에서 렌더링할 수 있습니다:

<div {{ $attributes }}> <!-- 컴포넌트 콘텐츠 --> </div>

WARNING

현재 컴포넌트 태그 안에서 @env와 같은 디렉티브를 사용하는 것은 지원되지 않습니다. 예를 들어 <x-alert :live="@env('production')"/>는 컴파일되지 않습니다.

기본값 지정 및 속성 병합하기

속성의 기본값을 지정하거나, 컴포넌트의 일부 속성에 추가 값을 병합해야 할 때가 있습니다. 이때는 속성 가방의 merge 메서드를 사용하면 됩니다. 이 메서드는 컴포넌트에 항상 적용되어야 하는 기본 CSS 클래스 집합을 정의할 때 특히 유용합니다:

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}> {{ $message }} </div>

이 컴포넌트를 다음과 같이 사용한다고 가정해봅시다:

<x-alert type="error" :message="$message" class="mb-4"/>

최종적으로 렌더링되는 HTML은 다음과 같습니다:

<div class="alert alert-error mb-4"> <!-- $message 변수의 내용 --> </div>

조건부로 클래스 병합하기

특정 조건이 true일 때만 클래스를 병합하고 싶은 경우도 있습니다. 이럴 때는 class 메서드를 사용할 수 있는데, 이 메서드는 배열을 인자로 받습니다. 배열의 키는 추가하고 싶은 클래스(들)를 나타내고, 값은 불리언 표현식입니다. 배열 요소의 키가 숫자라면 항상 렌더링되는 클래스 목록에 포함됩니다:

<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}> {{ $message }} </div>

컴포넌트에 다른 속성도 함께 병합해야 한다면 class 메서드 뒤에 merge 메서드를 체이닝하면 됩니다:

<button {{ $attributes->class(['p-4'])->merge(['type' => 'button']) }}> {{ $slot }} </button>

NOTE

병합된 속성을 받지 않는 다른 HTML 엘리먼트에서 조건부로 클래스를 조합해야 한다면 @class 디렉티브를 사용하면 됩니다.

class가 아닌 속성 병합하기

class가 아닌 속성을 병합할 때는 merge 메서드에 전달한 값이 해당 속성의 "기본값" 역할을 합니다. 하지만 class 속성과 달리 이런 속성들은 주입된 속성 값과 병합되지 않고, 대신 덮어써집니다. 예를 들어 button 컴포넌트는 다음과 같이 구현될 수 있습니다:

<button {{ $attributes->merge(['type' => 'button']) }}> {{ $slot }} </button>

버튼 컴포넌트를 사용할 때 커스텀 type을 지정하려면 컴포넌트를 사용하는 쪽에서 지정하면 됩니다. type을 지정하지 않으면 button 타입이 사용됩니다:

<x-button type="submit"> Submit </x-button>

위 예시에서 button 컴포넌트가 렌더링하는 HTML은 다음과 같습니다:

<button type="submit"> Submit </button>

class가 아닌 속성에도 기본값과 주입된 값을 함께 합치고 싶다면 prepends 메서드를 사용할 수 있습니다. 아래 예시에서는 data-controller 속성이 항상 profile-controller로 시작하고, 이후에 추가로 주입되는 data-controller 값이 그 뒤에 붙게 됩니다:

<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}> {{ $slot }} </div>

속성 조회 및 필터링하기

filter 메서드를 사용하면 속성을 필터링할 수 있습니다. 이 메서드는 클로저를 인자로 받으며, 속성 가방에 남겨두고 싶은 속성에 대해서는 true를 반환해야 합니다:

{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}

특정 문자열로 시작하는 모든 속성을 가져오고 싶다면 whereStartsWith 메서드를 사용하면 편리합니다:

{{ $attributes->whereStartsWith('wire:model') }}

반대로, 특정 문자열로 시작하는 속성을 모두 제외하고 싶다면 whereDoesntStartWith 메서드를 사용하면 됩니다:

{{ $attributes->whereDoesntStartWith('wire:model') }}

first 메서드를 사용하면 주어진 속성 가방에서 첫 번째 속성을 렌더링할 수 있습니다:

{{ $attributes->whereStartsWith('wire:model')->first() }}

컴포넌트에 특정 속성이 존재하는지 확인하고 싶다면 has 메서드를 사용하면 됩니다. 이 메서드는 속성 이름을 유일한 인자로 받고, 해당 속성이 있는지 여부를 불리언으로 반환합니다:

@if ($attributes->has('class')) <div>class 속성이 존재합니다</div> @endif

has 메서드에 배열을 전달하면 주어진 속성이 모두 존재하는지 확인합니다:

@if ($attributes->has(['name', 'class'])) <div>모든 속성이 존재합니다</div> @endif

hasAny 메서드는 주어진 속성 중 하나라도 존재하는지 확인할 때 사용할 수 있습니다:

@if ($attributes->hasAny(['href', ':href', 'v-bind:href'])) <div>속성 중 하나가 존재합니다</div> @endif

특정 속성의 값을 가져오려면 get 메서드를 사용하면 됩니다:

{{ $attributes->get('class') }}

주어진 키에 해당하는 속성만 가져오려면 only 메서드를 사용하면 됩니다:

{{ $attributes->only(['class']) }}

주어진 키를 제외한 나머지 속성을 모두 가져오려면 except 메서드를 사용하면 됩니다:

{{ $attributes->except(['class']) }}

예약어 (Reserved Keywords)

기본적으로 일부 키워드는 Blade가 컴포넌트를 렌더링하기 위해 내부적으로 사용하도록 예약되어 있습니다. 따라서 다음 키워드는 컴포넌트의 public 속성이나 메서드 이름으로 사용할 수 없습니다:

  • data
  • render
  • resolve
  • resolveView
  • shouldRender
  • view
  • withAttributes
  • withName

슬롯 (Slots)

컴포넌트에 추가적인 콘텐츠를 전달해야 할 때는 "슬롯"을 자주 활용하게 됩니다. 컴포넌트 슬롯은 $slot 변수를 출력하여 렌더링합니다. 다음과 같은 마크업을 가진 alert 컴포넌트를 예로 들어보겠습니다:

<!-- /resources/views/components/alert.blade.php --> <div class="alert alert-danger"> {{ $slot }} </div>

컴포넌트 안에 콘텐츠를 주입함으로써 slot에 콘텐츠를 전달할 수 있습니다:

<x-alert> <strong>이런!</strong> 문제가 발생했습니다! </x-alert>

경우에 따라서는 컴포넌트 안 여러 위치에 서로 다른 슬롯을 렌더링해야 할 때도 있습니다. alert 컴포넌트를 수정해서 "title" 슬롯을 주입할 수 있도록 해보겠습니다:

<!-- /resources/views/components/alert.blade.php --> <span class="alert-title">{{ $title }}</span> <div class="alert alert-danger"> {{ $slot }} </div>

named 슬롯의 내용은 x-slot 태그를 사용해 정의할 수 있습니다. 명시적인 x-slot 태그에 속하지 않은 나머지 콘텐츠는 모두 컴포넌트의 $slot 변수로 전달됩니다:

<x-alert> <x-slot:title> 서버 오류 </x-slot> <strong>이런!</strong> 문제가 발생했습니다! </x-alert>

슬롯에 콘텐츠가 있는지 확인하려면 슬롯의 isEmpty 메서드를 호출하면 됩니다:

<span class="alert-title">{{ $title }}</span> <div class="alert alert-danger"> @if ($slot->isEmpty()) 슬롯이 비어 있을 때 표시할 기본 콘텐츠입니다. @else {{ $slot }} @endif </div>

또한 hasActualContent 메서드를 사용하면 슬롯에 HTML 주석이 아닌 "실제" 콘텐츠가 있는지 확인할 수 있습니다:

@if ($slot->hasActualContent()) 이 슬롯에는 주석이 아닌 콘텐츠가 있습니다. @endif

스코프 슬롯 (Scoped Slots)

Vue와 같은 자바스크립트 프레임워크를 사용해본 적이 있다면 슬롯 안에서 컴포넌트의 데이터나 메서드에 접근할 수 있게 해주는 "스코프 슬롯"이라는 개념에 익숙할 것입니다. Laravel에서도 컴포넌트에 public 메서드나 속성을 정의하고, 슬롯 안에서 $component 변수를 통해 컴포넌트에 접근함으로써 비슷한 동작을 구현할 수 있습니다. 이 예제에서는 x-alert 컴포넌트 클래스에 formatAlert라는 public 메서드가 정의되어 있다고 가정하겠습니다:

<x-alert> <x-slot:title> {{ $component->formatAlert('서버 오류') }} </x-slot> <strong>이런!</strong> 문제가 발생했습니다! </x-alert>

슬롯 속성 (Slot Attributes)

Blade 컴포넌트와 마찬가지로, 슬롯에도 CSS 클래스 이름 같은 추가 속성을 지정할 수 있습니다:

<x-card class="shadow-sm"> <x-slot:heading class="font-bold"> Heading </x-slot> Content <x-slot:footer class="text-sm"> Footer </x-slot> </x-card>

슬롯의 속성을 다루려면 슬롯 변수의 attributes 프로퍼티에 접근하면 됩니다. 속성을 다루는 방법에 대한 더 자세한 내용은 컴포넌트 속성 문서를 참고하세요:

@props([ 'heading', 'footer', ]) <div {{ $attributes->class(['border']) }}> <h1 {{ $heading->attributes->class(['text-lg']) }}> {{ $heading }} </h1> {{ $slot }} <footer {{ $footer->attributes->class(['text-gray-700']) }}> {{ $footer }} </footer> </div>

인라인 컴포넌트 뷰

아주 작은 컴포넌트라면 컴포넌트 클래스와 뷰 템플릿을 각각 따로 관리하는 것이 번거로울 수 있습니다. 이럴 때는 render 메서드에서 컴포넌트의 마크업을 직접 반환할 수 있습니다:

/** * 컴포넌트를 나타내는 뷰 / 콘텐츠를 가져옵니다. */ public function render(): string { return <<<'blade' <div class="alert alert-danger"> {{ $slot }} </div> blade; }

인라인 뷰 컴포넌트 생성하기

인라인 뷰를 렌더링하는 컴포넌트를 만들려면 make:component 명령어 실행 시 inline 옵션을 사용하면 됩니다:

php artisan make:component Alert --inline

동적 컴포넌트 (Dynamic Components)

런타임이 되어야 어떤 컴포넌트를 렌더링할지 알 수 있는 경우가 있습니다. 이런 상황에서는 Laravel에 내장된 dynamic-component 컴포넌트를 사용해 런타임 값이나 변수를 기반으로 컴포넌트를 렌더링할 수 있습니다:

// $componentName = "secondary-button"; <x-dynamic-component :component="$componentName" class="mt-4" />

컴포넌트 수동 등록하기

WARNING

아래 내용은 뷰 컴포넌트를 포함하는 Laravel 패키지를 작성하는 개발자에게 주로 해당됩니다. 패키지를 작성하는 것이 아니라면 이 부분은 크게 신경 쓰지 않아도 됩니다.

자신의 애플리케이션을 위한 컴포넌트를 작성할 때는 app/View/Components 디렉터리와 resources/views/components 디렉터리 안의 컴포넌트가 자동으로 인식됩니다.

하지만 Blade 컴포넌트를 사용하는 패키지를 만들거나, 컴포넌트를 관례에서 벗어난 디렉터리에 배치하는 경우에는 Laravel이 컴포넌트의 위치를 알 수 있도록 컴포넌트 클래스와 HTML 태그 별칭을 직접 등록해야 합니다. 보통 패키지의 서비스 프로바이더 boot 메서드에서 컴포넌트를 등록합니다:

use Illuminate\Support\Facades\Blade; use VendorPackage\View\Components\AlertComponent; /** * 패키지의 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::component('package-alert', AlertComponent::class); }

컴포넌트를 등록하고 나면 태그 별칭으로 렌더링할 수 있습니다:

<x-package-alert/>

패키지 컴포넌트 자동 로딩하기

또는 componentNamespace 메서드를 사용해 컨벤션에 따라 컴포넌트 클래스를 자동으로 로드할 수도 있습니다. 예를 들어 Nightshade라는 패키지가 Package\Views\Components 네임스페이스 안에 Calendar, ColorPicker 컴포넌트를 가지고 있다고 가정해보겠습니다:

use Illuminate\Support\Facades\Blade; /** * 패키지의 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); }

이렇게 하면 package-name:: 문법을 사용해 벤더 네임스페이스별로 패키지 컴포넌트를 사용할 수 있습니다:

<x-nightshade::calendar /> <x-nightshade::color-picker />

Blade는 컴포넌트 이름을 파스칼 케이스로 변환하여 연결된 클래스를 자동으로 찾아냅니다. 하위 디렉터리는 "점(dot)" 표기법으로 지원됩니다.

Blade 템플릿

익명 컴포넌트 (Anonymous Components)

인라인 컴포넌트와 마찬가지로, 익명 컴포넌트도 컴포넌트를 하나의 파일로 관리할 수 있게 해주는 방법입니다. 다만 익명 컴포넌트는 단일 뷰 파일만 사용하며, 별도의 클래스가 존재하지 않습니다. 익명 컴포넌트를 정의하려면 resources/views/components 디렉터리에 Blade 템플릿 파일을 하나 만들기만 하면 됩니다. 예를 들어, resources/views/components/alert.blade.php 파일에 컴포넌트를 정의했다면 다음과 같이 간단히 렌더링할 수 있습니다.

<x-alert/>

. 문자를 사용하면 components 디렉터리 안에 더 깊이 중첩된 컴포넌트도 표현할 수 있습니다. 예를 들어 resources/views/components/inputs/button.blade.php에 컴포넌트가 정의되어 있다면 다음과 같이 렌더링합니다.

<x-inputs.button/>

Artisan 명령어로 익명 컴포넌트를 생성하려면 make:component 명령어 실행 시 --view 플래그를 사용하면 됩니다.

php artisan make:component forms.input --view

위 명령어를 실행하면 resources/views/components/forms/input.blade.php 경로에 Blade 파일이 생성되며, <x-forms.input />로 렌더링할 수 있습니다.

익명 인덱스 컴포넌트 (Anonymous Index Components)

컴포넌트 하나가 여러 개의 Blade 템플릿으로 구성되는 경우, 해당 컴포넌트와 관련된 템플릿들을 하나의 디렉터리로 묶어서 관리하고 싶을 때가 있습니다. 예를 들어 다음과 같은 디렉터리 구조를 가진 "아코디언(accordion)" 컴포넌트를 생각해봅시다.

/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php

이 디렉터리 구조에서는 아코디언 컴포넌트와 그 하위 아이템을 다음과 같이 렌더링할 수 있습니다.

<x-accordion> <x-accordion.item> ... </x-accordion.item> </x-accordion>

하지만 x-accordion으로 아코디언 컴포넌트를 렌더링하려면, 다른 아코디언 관련 템플릿들과 함께 accordion 디렉터리 안에 두지 않고 "루트(index)" 역할을 하는 아코디언 템플릿을 resources/views/components 디렉터리에 직접 두어야만 했습니다.

다행히 Blade는 컴포넌트 디렉터리 이름과 동일한 이름의 파일을 그 디렉터리 안에 함께 둘 수 있도록 지원합니다. 이런 파일이 존재하면, 비록 하위 디렉터리 안에 중첩되어 있더라도 해당 컴포넌트의 "루트" 엘리먼트로 렌더링될 수 있습니다. 즉, 위 예제와 동일한 Blade 문법을 그대로 유지하면서 디렉터리 구조만 다음과 같이 조정하면 됩니다.

/resources/views/components/accordion/accordion.blade.php
/resources/views/components/accordion/item.blade.php

데이터 프로퍼티 / 속성 (Data Properties / Attributes)

익명 컴포넌트는 연결된 클래스가 없기 때문에, 어떤 데이터를 변수로 컴포넌트에 전달해야 하고 어떤 속성이 컴포넌트의 속성 백(attribute bag)에 담겨야 하는지 구분하는 방법이 궁금할 수 있습니다.

컴포넌트의 Blade 템플릿 최상단에 @props 디렉티브를 사용하면, 어떤 속성을 데이터 변수로 취급할지 지정할 수 있습니다. 그 외의 나머지 속성들은 모두 컴포넌트의 속성 백을 통해 사용할 수 있습니다. 데이터 변수에 기본값을 지정하고 싶다면, 배열의 키에 변수 이름을, 값에 기본값을 넣어주면 됩니다.

<!-- /resources/views/components/alert.blade.php --> @props(['type' => 'info', 'message']) <div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}> {{ $message }} </div>

위와 같이 컴포넌트를 정의했다면, 다음처럼 렌더링할 수 있습니다.

<x-alert type="error" :message="$message" class="mb-4"/>

부모 데이터 접근하기 (Accessing Parent Data)

경우에 따라 자식 컴포넌트 안에서 부모 컴포넌트의 데이터에 접근하고 싶을 수 있습니다. 이럴 때는 @aware 디렉티브를 사용합니다. 예를 들어, 부모 <x-menu>와 자식 <x-menu.item>으로 구성된 복잡한 메뉴 컴포넌트를 만든다고 가정해봅시다.

<x-menu color="purple"> <x-menu.item>...</x-menu.item> <x-menu.item>...</x-menu.item> </x-menu>

<x-menu> 컴포넌트는 다음과 같이 구현할 수 있습니다.

<!-- /resources/views/components/menu/index.blade.php --> @props(['color' => 'gray']) <ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}> {{ $slot }} </ul>

color prop은 부모 컴포넌트(<x-menu>)에만 전달되었기 때문에, 기본적으로는 <x-menu.item> 안에서 사용할 수 없습니다. 하지만 @aware 디렉티브를 사용하면 <x-menu.item> 안에서도 이 값을 사용할 수 있습니다.

<!-- /resources/views/components/menu/item.blade.php --> @aware(['color' => 'gray']) <li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}> {{ $slot }} </li>

WARNING

@aware 디렉티브는 부모 컴포넌트에 HTML 속성 형태로 명시적으로 전달된 데이터만 가져올 수 있습니다. 부모 컴포넌트에 명시적으로 전달되지 않은 @props의 기본값은 @aware 디렉티브로 접근할 수 없습니다.

NOTE

@aware는 부모-자식 관계에 있는 컴포넌트끼리 값을 "상속"받는 것처럼 보이지만, 실제로는 부모에게 명시적으로 전달된 속성 값만 하위로 전파해주는 기능입니다. 부모 컴포넌트 내부에서만 계산되거나 기본값으로 채워진 값은 대상이 아니라는 점을 기억해두면 혼동을 줄일 수 있습니다.

익명 컴포넌트 경로 (Anonymous Component Paths)

앞서 살펴본 것처럼 익명 컴포넌트는 보통 resources/views/components 디렉터리에 Blade 템플릿을 두어 정의합니다. 하지만 기본 경로 외에 다른 경로도 익명 컴포넌트 위치로 Laravel에 등록하고 싶을 때가 있습니다.

anonymousComponentPath 메서드는 첫 번째 인자로 익명 컴포넌트가 위치한 "경로"를 받고, 두 번째 인자로는 해당 컴포넌트들을 묶을 "네임스페이스"를 선택적으로 받습니다. 이 메서드는 보통 애플리케이션의 서비스 프로바이더 중 하나의 boot 메서드에서 호출합니다.

/** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::anonymousComponentPath(__DIR__.'/../components'); }

위 예제처럼 접두사(prefix) 없이 컴포넌트 경로를 등록하면, 해당 경로의 Blade 컴포넌트도 접두사 없이 그대로 렌더링할 수 있습니다. 예를 들어 위에서 등록한 경로에 panel.blade.php 컴포넌트가 있다면 다음과 같이 렌더링할 수 있습니다.

<x-panel />

anonymousComponentPath 메서드의 두 번째 인자로 접두사 "네임스페이스"를 지정할 수도 있습니다.

Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');

접두사를 지정하면, 컴포넌트를 렌더링할 때 해당 네임스페이스를 컴포넌트 이름 앞에 붙여서 사용할 수 있습니다.

<x-dashboard::panel />

레이아웃 구성하기

컴포넌트를 이용한 레이아웃

대부분의 웹 애플리케이션은 여러 페이지에서 동일한 전반적인 레이아웃을 유지합니다. 매번 뷰를 만들 때마다 레이아웃 전체 HTML을 반복해서 작성해야 한다면 애플리케이션을 유지보수하기가 대단히 번거로울 것입니다. 다행히 이러한 레이아웃은 하나의 Blade 컴포넌트로 정의한 뒤 애플리케이션 전체에서 재사용할 수 있습니다.

레이아웃 컴포넌트 정의하기

예를 들어 할 일 관리(Todo) 애플리케이션을 만든다고 가정해 봅시다. 다음과 같이 layout 컴포넌트를 정의할 수 있습니다.

<!-- resources/views/components/layout.blade.php --> <html> <head> <title>{{ $title ?? 'Todo Manager' }}</title> </head> <body> <h1>Todos</h1> <hr/> {{ $slot }} </body> </html>

레이아웃 컴포넌트 적용하기

layout 컴포넌트를 정의했다면, 이 컴포넌트를 활용하는 Blade 뷰를 만들 수 있습니다. 아래 예시에서는 할 일 목록을 보여주는 간단한 뷰를 정의합니다.

<!-- resources/views/tasks.blade.php --> <x-layout> @foreach ($tasks as $task) <div>{{ $task }}</div> @endforeach </x-layout>

컴포넌트에 주입된 콘텐츠는 layout 컴포넌트의 기본 $slot 변수로 전달된다는 점을 기억하세요. 눈치채셨겠지만, 우리의 layout 컴포넌트는 $title 슬롯이 전달되면 이를 사용하고, 그렇지 않으면 기본 제목을 표시합니다. 컴포넌트 문서에서 설명한 일반적인 슬롯 문법을 사용해 할 일 목록 뷰에서 커스텀 제목을 전달할 수 있습니다.

<!-- resources/views/tasks.blade.php --> <x-layout> <x-slot:title> 커스텀 제목 </x-slot> @foreach ($tasks as $task) <div>{{ $task }}</div> @endforeach </x-layout>

이제 레이아웃과 할 일 목록 뷰를 모두 정의했으니, 라우트에서 tasks 뷰를 반환하기만 하면 됩니다.

use App\Models\Task; Route::get('/tasks', function () { return view('tasks', ['tasks' => Task::all()]); });

템플릿 상속을 이용한 레이아웃

레이아웃 정의하기

레이아웃은 "템플릿 상속" 방식으로도 만들 수 있습니다. 이는 컴포넌트가 도입되기 이전에 애플리케이션을 구성하던 주된 방식이었습니다.

간단한 예제를 통해 살펴보겠습니다. 먼저 페이지 레이아웃을 살펴봅시다. 대부분의 웹 애플리케이션은 여러 페이지에서 동일한 전반적인 레이아웃을 유지하므로, 이러한 레이아웃을 하나의 Blade 뷰로 정의해두면 편리합니다.

<!-- resources/views/layouts/app.blade.php --> <html> <head> <title>App Name - @yield('title')</title> </head> <body> @section('sidebar') 이곳은 기본 사이드바입니다. @show <div class="container"> @yield('content') </div> </body> </html>

일반적인 HTML 마크업으로 이루어져 있지만, @section 디렉티브와 @yield 디렉티브를 눈여겨보세요. 이름에서 알 수 있듯이 @section 디렉티브는 콘텐츠의 한 영역(섹션)을 정의하며, @yield 디렉티브는 지정한 섹션의 내용을 출력하는 데 사용합니다.

이제 애플리케이션의 레이아웃을 정의했으니, 이 레이아웃을 상속하는 자식 페이지를 정의해 보겠습니다.

레이아웃 확장하기

자식 뷰를 정의할 때는 @extends Blade 디렉티브를 사용해 어떤 레이아웃을 "상속"할지 지정합니다. Blade 레이아웃을 확장하는 뷰는 @section 디렉티브를 사용해 레이아웃의 섹션에 콘텐츠를 주입할 수 있습니다. 앞선 예시에서 보았듯, 이 섹션들의 내용은 레이아웃의 @yield 위치에 출력됩니다.

<!-- resources/views/child.blade.php --> @extends('layouts.app') @section('title', '페이지 제목') @section('sidebar') @@parent <p>기본 사이드바에 추가되는 내용입니다.</p> @endsection @section('content') <p>본문 내용입니다.</p> @endsection

이 예시에서 sidebar 섹션은 @@parent 디렉티브를 사용해 레이아웃의 사이드바 내용을 덮어쓰지 않고 그 뒤에 콘텐츠를 추가하고 있습니다. @@parent 디렉티브는 뷰가 렌더링될 때 레이아웃에 정의된 원래 콘텐츠로 치환됩니다.

NOTE

앞선 예시와 달리, 이번 sidebar 섹션은 @show가 아닌 @endsection으로 끝난다는 점에 주목하세요. @endsection은 섹션을 정의만 할 뿐이지만, @show는 섹션을 정의함과 동시에 즉시 출력합니다.

@yield 디렉티브는 두 번째 인자로 기본값을 받을 수도 있습니다. 이 값은 출력하려는 섹션이 정의되어 있지 않을 때 대신 렌더링됩니다.

@yield('content', '기본 콘텐츠')

폼 (Forms)

CSRF 필드

애플리케이션에서 HTML 폼을 정의할 때는 반드시 숨겨진 CSRF 토큰 필드를 포함시켜야 합니다. 그래야 CSRF 보호 미들웨어가 해당 요청을 정상적으로 검증할 수 있습니다. @csrf Blade 지시어를 사용하면 이 토큰 필드를 간편하게 생성할 수 있습니다.

<form method="POST" action="/profile"> @csrf ... </form>

Method 필드

HTML 폼은 기본적으로 PUT, PATCH, DELETE 요청을 보낼 수 없기 때문에, 이런 HTTP 메서드를 흉내 내려면 숨겨진 _method 필드를 추가해야 합니다. @method Blade 지시어를 사용하면 이 필드를 손쉽게 생성할 수 있습니다.

<form action="/foo/bar" method="POST"> @method('PUT') ... </form>

검증 에러 (Validation Errors)

@error 지시어를 사용하면 특정 속성(attribute)에 대한 유효성 검증 에러 메시지가 존재하는지 빠르게 확인할 수 있습니다. @error 지시어 블록 안에서는 $message 변수를 출력해 실제 에러 메시지를 보여줄 수 있습니다.

<!-- /resources/views/post/create.blade.php --> <label for="title">게시글 제목</label> <input id="title" type="text" class="@error('title') is-invalid @enderror" /> @error('title') <div class="alert alert-danger">{{ $message }}</div> @enderror

@error 지시어는 내부적으로 "if" 조건문으로 컴파일되기 때문에, 해당 속성에 에러가 없을 때 다른 내용을 보여주고 싶다면 @else 지시어를 함께 사용할 수 있습니다.

<!-- /resources/views/auth.blade.php --> <label for="email">이메일 주소</label> <input id="email" type="email" class="@error('email') is-invalid @else is-valid @enderror" />

한 페이지에 여러 개의 폼이 있어서 이름이 지정된 에러 백(error bag)을 사용해야 하는 경우, @error 지시어의 두 번째 인자로 에러 백 이름을 전달하면 해당 폼에 맞는 검증 에러 메시지만 가져올 수 있습니다.

<!-- /resources/views/auth.blade.php --> <label for="email">이메일 주소</label> <input id="email" type="email" class="@error('email', 'login') is-invalid @enderror" /> @error('email', 'login') <div class="alert alert-danger">{{ $message }}</div> @enderror

NOTE

한 페이지에 로그인 폼과 회원가입 폼처럼 여러 폼이 동시에 존재하는 경우, 에러 백을 지정하지 않으면 두 폼의 에러 메시지가 뒤섞여 표시될 수 있습니다. 이런 상황에서는 반드시 에러 백 이름을 명시적으로 지정해 주세요.

Blade 템플릿

Stacks (스택)

Blade의 스택 기능을 사용하면 특정 이름의 스택에 콘텐츠를 쌓아두었다가, 다른 뷰나 레이아웃의 원하는 위치에서 한꺼번에 렌더링할 수 있습니다. 자식 뷰마다 필요한 JavaScript 라이브러리가 다를 때, 이를 한곳에 모아 레이아웃 상단이나 하단에 출력하는 용도로 특히 유용합니다:

@push('scripts') <script src="/example.js"></script> @endpush

주어진 불리언 표현식이 true로 평가될 때만 @push를 실행하고 싶다면 @pushIf 디렉티브를 사용하면 됩니다:

@pushIf($shouldPush, 'scripts') <script src="/example.js"></script> @endPushIf

하나의 스택에는 필요한 만큼 여러 번 @push할 수 있습니다. 스택에 쌓인 전체 콘텐츠를 렌더링하려면, @stack 디렉티브에 스택 이름을 전달하면 됩니다:

<head> <!-- 헤드(head) 영역 콘텐츠 --> @stack('scripts') </head>

스택의 맨 앞에 콘텐츠를 추가하고 싶을 때는 @prepend 디렉티브를 사용합니다:

@push('scripts') 이 내용은 두 번째로 출력됩니다... @endpush // 이후... @prepend('scripts') 이 내용은 첫 번째로 출력됩니다... @endprepend

NOTE

@push는 코드를 작성한 순서대로 스택에 추가되지만, @prepend를 사용하면 순서를 뒤집어 먼저 표시되도록 할 수 있습니다. 레이아웃 파일을 수정하지 않고도 자식 뷰에서 출력 순서를 제어하고 싶을 때 유용합니다.

@hasstack 디렉티브를 사용하면 스택이 비어 있는지 여부를 확인할 수 있습니다:

@hasstack('list') <ul> @stack('list') </ul> @endif

서비스 주입

@inject 디렉티브를 사용하면 Laravel 서비스 컨테이너에서 서비스를 가져와 사용할 수 있습니다. @inject에 전달하는 첫 번째 인자는 서비스가 담길 변수의 이름이고, 두 번째 인자는 컨테이너에서 해석(resolve)하고자 하는 서비스의 클래스명 또는 인터페이스명입니다:

@inject('metrics', 'App\Services\MetricsService') <div> 이번 달 매출: {{ $metrics->monthlyRevenue() }}. </div>

NOTE

@inject는 뷰 안에서 서비스 컨테이너에 등록된 클래스를 간편하게 사용할 수 있게 해주는 문법입니다. 컨트롤러에서 데이터를 미리 계산해 뷰로 넘기는 방식이 더 일반적이지만, 뷰 자체에서 컨테이너의 기능을 직접 활용해야 하는 경우에 유용합니다.

인라인 Blade 템플릿 렌더링

경우에 따라 Blade 템플릿 문자열을 순수하게 그대로 받아서 HTML로 변환해야 할 때가 있습니다. 이럴 때는 Blade 파사드가 제공하는 render 메서드를 사용하면 됩니다. render 메서드는 Blade 템플릿 문자열과, 템플릿에 전달할 데이터 배열(선택 사항)을 인자로 받습니다:

use Illuminate\Support\Facades\Blade; return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);

Laravel은 인라인 Blade 템플릿을 렌더링할 때 storage/framework/views 디렉터리에 파일을 임시로 작성하는 방식을 사용합니다. 템플릿 렌더링이 끝난 후 이 임시 파일을 삭제하고 싶다면 deleteCachedView 인자를 전달하면 됩니다:

return Blade::render( 'Hello, {{ $name }}', ['name' => 'Julian Bashir'], deleteCachedView: true );

Blade 템플릿

Blade 조각(Fragment) 렌더링

Turbo나 htmx 같은 프론트엔드 프레임워크를 사용하다 보면, HTTP 응답으로 Blade 템플릿 전체가 아니라 그 일부만 돌려줘야 할 때가 있습니다. Blade의 "조각(Fragment)" 기능은 바로 이럴 때 사용합니다. 사용법은 간단한데, Blade 템플릿에서 반환하고 싶은 부분을 @fragment와 @endfragment 디렉티브로 감싸주기만 하면 됩니다:

@fragment('user-list') <ul> @foreach ($users as $user) <li>{{ $user->name }}</li> @endforeach </ul> @endfragment

그리고 이 템플릿을 사용하는 뷰를 렌더링할 때 fragment 메서드를 호출하면, HTTP 응답에 지정한 조각만 포함시킬 수 있습니다:

return view('dashboard', ['users' => $users])->fragment('user-list');

fragmentIf 메서드를 사용하면 특정 조건에 따라 조각만 반환할지, 아니면 뷰 전체를 반환할지 결정할 수 있습니다. 조건이 거짓이면 뷰 전체가 반환됩니다:

return view('dashboard', ['users' => $users]) ->fragmentIf($request->hasHeader('HX-Request'), 'user-list');

NOTE

예를 들어 htmx를 사용하는 경우, 요청에 HX-Request 헤더가 포함되어 있는지로 "이 요청이 htmx가 보낸 부분 갱신 요청인지"를 판단할 수 있습니다. 이런 패턴을 활용하면 같은 컨트롤러 액션이 일반 페이지 요청과 부분 갱신 요청을 모두 처리하도록 만들 수 있습니다.

fragments와 fragmentsIf 메서드를 사용하면 여러 개의 뷰 조각을 한 번에 응답으로 반환할 수 있습니다. 지정한 조각들은 순서대로 이어붙여져 반환됩니다:

view('dashboard', ['users' => $users]) ->fragments(['user-list', 'comment-list']); view('dashboard', ['users' => $users]) ->fragmentsIf( $request->hasHeader('HX-Request'), ['user-list', 'comment-list'] );

Blade 확장하기

Blade는 directive 메서드를 통해 여러분만의 커스텀 디렉티브를 정의할 수 있도록 지원합니다. Blade 컴파일러가 커스텀 디렉티브를 발견하면, 해당 디렉티브가 담고 있는 표현식(expression)을 인자로 전달하며 여러분이 등록한 콜백을 호출합니다.

아래 예제는 DateTime 인스턴스인 $var를 원하는 형식으로 출력해주는 @datetime($var) 디렉티브를 만드는 방법을 보여줍니다:

<?php namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * 애플리케이션 서비스를 등록합니다. */ public function register(): void { // ... } /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::directive('datetime', function (string $expression) { return "<?php echo ($expression)->format('m/d/Y H:i'); ?>"; }); } }

보시다시피, 디렉티브에 전달된 표현식 뒤에 format 메서드를 체이닝하고 있습니다. 즉, 위 예제에서 최종적으로 생성되는 PHP 코드는 다음과 같습니다:

<?php echo ($var)->format('m/d/Y H:i'); ?>

WARNING

Blade 디렉티브의 로직을 수정한 뒤에는 반드시 캐시된 Blade 뷰 파일을 모두 삭제해야 합니다. 캐시된 Blade 뷰는 view:clear Artisan 명령어로 삭제할 수 있습니다.

커스텀 echo 핸들러

Blade에서 객체를 "echo(출력)"하려고 하면, 해당 객체의 __toString 메서드가 호출됩니다. __toString은 PHP에 내장된 "매직 메서드" 중 하나입니다. 하지만 서드파티 라이브러리의 클래스를 다루는 경우처럼, 해당 클래스의 __toString 메서드를 직접 제어할 수 없는 상황도 있습니다.

이런 경우, Blade에서는 특정 타입의 객체에 대해 커스텀 echo 핸들러를 등록할 수 있습니다. 이를 위해서는 Blade의 stringable 메서드를 호출하면 됩니다. stringable 메서드는 클로저를 인자로 받으며, 이 클로저는 자신이 렌더링을 담당할 객체의 타입을 타입힌트로 지정해야 합니다. 일반적으로 stringable 메서드는 애플리케이션의 AppServiceProvider 클래스에 있는 boot 메서드 안에서 호출합니다:

use Illuminate\Support\Facades\Blade; use Money\Money; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::stringable(function (Money $money) { return $money->formatTo('en_GB'); }); }

커스텀 echo 핸들러를 정의하고 나면, Blade 템플릿에서 해당 객체를 그냥 출력하기만 하면 됩니다:

Cost: {{ $money }}

커스텀 If 문

간단한 조건문 형태의 커스텀 디렉티브를 만들 때는 매번 디렉티브를 직접 프로그래밍하는 것이 번거로울 수 있습니다. 이런 경우를 위해 Blade는 Blade::if 메서드를 제공하는데, 이를 사용하면 클로저만으로 손쉽게 커스텀 조건부 디렉티브를 정의할 수 있습니다. 예를 들어, 애플리케이션에 설정된 기본 "디스크"를 확인하는 커스텀 조건문을 만들어보겠습니다. 이 코드는 AppServiceProvider의 boot 메서드에 작성합니다:

use Illuminate\Support\Facades\Blade; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Blade::if('disk', function (string $value) { return config('filesystems.default') === $value; }); }

커스텀 조건문을 정의하고 나면, 템플릿 안에서 다음과 같이 사용할 수 있습니다:

@disk('local') <!-- 애플리케이션이 local 디스크를 사용 중입니다... --> @elsedisk('s3') <!-- 애플리케이션이 s3 디스크를 사용 중입니다... --> @else <!-- 애플리케이션이 다른 디스크를 사용 중입니다... --> @enddisk @unlessdisk('local') <!-- 애플리케이션이 local 디스크를 사용하고 있지 않습니다... --> @enddisk

NOTE

커스텀 디렉티브나 커스텀 If 문은 프로젝트 전반에서 반복적으로 사용되는 조건 로직(예: 특정 권한 확인, 특정 환경 분기 등)을 뷰 레이어에서 간결하게 표현하고 싶을 때 특히 유용합니다. 다만 비즈니스 로직이 복잡해진다면, 뷰보다는 컨트롤러나 서비스 클래스로 옮기는 것이 유지보수 측면에서 더 바람직합니다.

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 9월 30일