레이아웃 컴포넌트
MainLayout과 중첩 레이아웃을 구성해 공통 UI 구조를 재사용하는 방법을 배웁니다.
📌 학습 목표
- 레이아웃 컴포넌트가 무엇이며 왜 필요한지 설명할 수 있습니다.
LayoutComponentBase를 상속하고@Body를 올바르게 배치할 수 있습니다.MainLayout.razor를 직접 작성하고 프로젝트에 적용할 수 있습니다.- 개별 페이지에 다른 레이아웃을 지정하는 세 가지 방법과 우선순위를 이해합니다.
- 레이아웃 안에 레이아웃을 중첩하여 계층적 UI 구조를 구성할 수 있습니다.
RouteView + DefaultLayout 지정
공통 UI (헤더·사이드바·푸터)
현재 라우팅된 페이지 컴포넌트
📝 개념 설명
1. 레이아웃 컴포넌트란?
웹 애플리케이션의 모든 페이지에는 헤더, 내비게이션 메뉴, 사이드바, 푸터 같은 공통 UI 요소가 반복됩니다. 이를 각 페이지마다 복사·붙여넣기로 관리한다면 수정 한 번에 수십 개의 파일을 변경해야 하는 문제가 생깁니다.
Blazor는 이 문제를 레이아웃 컴포넌트(Layout Component)로 해결합니다. 레이아웃 컴포넌트는 공통 UI 구조를 한 곳에 정의해 두고, 실제 페이지 내용이 표시될 위치만 @Body로 지정하는 특수 컴포넌트입니다.
레이아웃 컴포넌트는 액자 틀이고, 각 페이지 컴포넌트는 액자 안에 끼워지는 그림입니다. 틀(레이아웃)은 공통이고, 그림(페이지 내용)만 페이지마다 바뀝니다.
2. LayoutComponentBase와 @Body
레이아웃 컴포넌트가 일반 컴포넌트와 다른 핵심 차이점은 LayoutComponentBase를 상속한다는 것입니다. 이 클래스는 Microsoft.AspNetCore.Components 네임스페이스에 포함되어 있으며, @Body 속성(RenderFragment 타입)을 제공합니다.
@* Shared/MainLayout.razor — 가장 기본적인 구조 *@
@inherits LayoutComponentBase
<div class="layout-wrapper">
<header>
<h1>내 Blazor 앱</h1>
</header>
<main>
@Body
</main>
<footer>
<p>© 2024 My App</p>
</footer>
</div>
@inherits LayoutComponentBase: 이 컴포넌트가 레이아웃임을 Blazor에 알립니다. 반드시 파일 최상단에 선언해야 합니다.@Body: 현재 라우팅된 페이지의 내용이 이 위치에 삽입됩니다. 레이아웃 파일 안에 정확히 한 번만 배치합니다.
3. 레이아웃 적용 방법 세 가지
방법 ① App.razor — DefaultLayout 지정 (전역 기본값)
가장 광범위한 방법으로, 별도 레이아웃을 지정하지 않은 모든 페이지에 기본 적용됩니다.
@* App.razor *@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>페이지를 찾을 수 없습니다.</p>
</LayoutView>
</NotFound>
</Router>
방법 ② _Imports.razor — 폴더 단위 레이아웃 지정
_Imports.razor에 @layout을 추가하면 해당 폴더(및 하위 폴더)의 모든 페이지에 레이아웃이 적용됩니다.
@* _Imports.razor *@
@using MyApp.Shared
@layout MainLayout
방법 ③ 개별 페이지 — @layout 지시어 직접 선언 (최우선)
특정 페이지만 다른 레이아웃을 사용할 때, 해당 페이지 최상단에 @layout을 직접 선언합니다. 세 가지 방법 중 가장 높은 우선순위를 가집니다.
@* Pages/Login.razor — 로그인 페이지는 사이드바 없는 심플 레이아웃 사용 *@
@page "/login"
@layout LoginLayout
<h2>로그인</h2>
<input type="text" placeholder="아이디" />
| 방법 | 선언 위치 | 적용 범위 | 우선순위 |
|---|---|---|---|
개별 페이지 @layout | 각 .razor 파일 | 해당 페이지만 | 🥇 가장 높음 |
_Imports.razor @layout | 폴더 내 _Imports.razor | 해당 폴더 전체 | 🥈 중간 |
App.razor DefaultLayout | App.razor RouteView | 앱 전체 기본값 | 🥉 가장 낮음 |
4. 중첩 레이아웃 (Nested Layouts)
레이아웃 안에 또 다른 레이아웃을 중첩할 수 있습니다. 예를 들어, 관리자 섹션은 기본 MainLayout의 공통 헤더·푸터를 유지하면서 추가로 관리자 전용 사이드 메뉴를 가진 AdminLayout을 중첩해 사용할 수 있습니다.
자식 레이아웃 파일에 @layout 부모레이아웃을 선언하는 것만으로 중첩이 완성됩니다.
@* Shared/AdminLayout.razor — MainLayout을 부모로 사용하는 중첩 레이아웃 *@
@inherits LayoutComponentBase
@layout MainLayout
<div class="admin-container">
<nav class="admin-sidebar">
<ul>
<li><a href="/admin/users">사용자 관리</a></li>
<li><a href="/admin/posts">게시글 관리</a></li>
<li><a href="/admin/settings">설정</a></li>
</ul>
</nav>
<div class="admin-content">
@Body
</div>
</div>
관리자 페이지에서 AdminLayout을 지정하면, Blazor가 자동으로 MainLayout → AdminLayout → 페이지 순서로 중첩 렌더링합니다.
@* Pages/Admin/Users.razor *@
@page "/admin/users"
@layout AdminLayout
<h2>사용자 관리</h2>
<p>전체 사용자 목록입니다.</p>
💡 예제 & 실습 — 실전 레이아웃 구조 완성하기
아래 예제는 실제 프로젝트에 적용할 수 있는 완전한 레이아웃 구조입니다. 단계별로 따라 해보세요.
단계 1: Shared/MainLayout.razor 작성
@* Shared/MainLayout.razor *@
@inherits LayoutComponentBase
<div class="page">
<header class="site-header">
<nav>
<a href="/">🏠 홈</a>
<a href="/about">소개</a>
<a href="/contact">문의</a>
</nav>
</header>
<div class="content-wrapper">
<aside class="sidebar">
<h4>카테고리</h4>
<ul>
<li><a href="/posts/csharp">C#</a></li>
<li><a href="/posts/blazor">Blazor</a></li>
</ul>
</aside>
<main class="main-content">
@Body
</main>
</div>
<footer class="site-footer">
<p>© 2024 Blazor 학습 블로그</p>
</footer>
</div>
@code {
// @code 블록으로 의존성 주입(DI)·상태·이벤트 핸들러를 레이아웃 수준에서 정의할 수 있습니다
}
단계 2: App.razor에서 기본 레이아웃 지정
@* App.razor *@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">페이지를 찾을 수 없습니다.</p>
</LayoutView>
</NotFound>
</Router>
단계 3: 일반 페이지 — 레이아웃 자동 적용 확인
@* Pages/Index.razor — 별도 @layout 없이 DefaultLayout(MainLayout) 자동 적용 *@
@page "/"
<h1>안녕하세요!</h1>
<p>이 내용은 MainLayout의 @Body 위치에 렌더링됩니다.</p>
단계 4: EmptyLayout — 레이아웃 없는 전체 화면 페이지
레이아웃을 완전히 제거하고 싶다면, 내용만 통과시키는 최소 레이아웃(EmptyLayout)을 만들어 사용합니다. Blazor에서 레이아웃을 null로 지정하는 것은 불가능하므로 이 방법이 표준적인 패턴입니다.
@* Shared/EmptyLayout.razor *@
@inherits LayoutComponentBase
@Body
@* Pages/FullscreenMap.razor *@
@page "/map"
@layout EmptyLayout
<div style="width:100vw; height:100vh;">
<!-- 전체 화면 지도 또는 전용 UI -->
</div>
⚠️ 자주 틀리는 것 / 주의사항
실수 1: @inherits LayoutComponentBase 누락
원인:
@inherits LayoutComponentBase가 없으면 Blazor는 이 파일을 일반 컴포넌트로 취급합니다.해결: 레이아웃 .razor 파일 최상단에 반드시
@inherits LayoutComponentBase를 선언하세요.
실수 2: @Body 누락 또는 잘못된 위치 배치
원인:
@Body가 없으면 현재 페이지 컴포넌트가 삽입될 위치가 없습니다.해결: 콘텐츠 메인 영역 안에 반드시
@Body를 배치하세요. <main> 또는 내용 컨테이너 안이 적절합니다.
실수 3: 레이아웃 파일에 @page 지시어 사용
원인: 레이아웃 컴포넌트는 URL 라우팅 대상이 아닙니다.
@page를 선언하면 라우터가 이를 독립 페이지로 인식합니다.해결:
MainLayout.razor 등 레이아웃 파일에서 @page를 제거하세요.
실수 4: 하나의 레이아웃에 @Body를 두 번 이상 배치
원인: 하나의 레이아웃 파일 안에
@Body는 정확히 한 번만 허용됩니다.해결:
@Body를 단 하나만 배치하고, 나머지는 모두 제거하세요.
실수 5: 네임스페이스 누락으로 레이아웃 참조 실패
App.razor에서 typeof(MainLayout) 참조 시 “이름을 찾을 수 없습니다” 오류 발생.원인:
MainLayout이 위치한 네임스페이스(일반적으로 프로젝트명.Shared)가 _Imports.razor에 열려있지 않습니다.해결:
_Imports.razor에 @using 프로젝트명.Shared를 추가하세요.
🎯 마무리
레이아웃 컴포넌트는 Blazor 애플리케이션의 UI 구조를 단 한 곳에서 통합 관리하게 해주는 핵심 기능입니다. @inherits LayoutComponentBase와 @Body 두 가지만 이해하면 기본 레이아웃은 바로 구현할 수 있으며, 적용 우선순위 규칙을 숙지하면 섹션마다 다른 레이아웃을 유연하게 설계할 수 있습니다.
중첩 레이아웃은 관리자 화면, 결제 플로우, 로그인 페이지처럼 서로 다른 구조가 필요한 영역을 깔끔하게 분리하는 강력한 도구입니다. 자식 레이아웃에 @layout 부모레이아웃 한 줄만 추가하면 계층적 UI 구조가 완성됩니다. 이 패턴을 잘 활용하면 전체 앱의 유지보수성이 크게 향상됩니다.
- 레이아웃 컴포넌트는
@inherits LayoutComponentBase를 선언하고, 페이지 내용 삽입 위치에@Body를 정확히 한 번 배치합니다. - 레이아웃 적용 우선순위: 개별 페이지 @layout > _Imports.razor @layout > App.razor DefaultLayout
- 중첩 레이아웃은 자식 레이아웃 파일에
@layout 부모레이아웃을 선언하는 것으로 구성됩니다. - 레이아웃 파일에는
@page지시어를 절대 사용하지 않습니다. - 레이아웃에도
@code블록을 사용해 의존성 주입, 상태 관리, 이벤트 처리를 구현할 수 있습니다. - 레이아웃을 완전히 제거하려면
EmptyLayout(@Body만 있는 최소 레이아웃)을 만들어 사용합니다.