헤더와 푸터를 페이지마다 복사해 넣지 않으려면 레이아웃이 필요하다. Nuxt3는 이걸 layouts 디렉토리와 이름 규칙으로 처리한다. 페이지 vue 파일에 따로 설정하지 않으면 그 페이지는 default 레이아웃으로 렌더링된다.
이 글에서는 기본 레이아웃부터 잡고, 페이지마다 다른 레이아웃을 지정하는 법과 실행 중에 레이아웃을 바꾸는 법으로 넘어간다.
default.vue가 모든 페이지를 감싼다
~/layouts/ 경로에 default.vue 파일을 추가하면 기본 레이아웃이 된다. 가장 단순한 형태는 이렇다.
<template>
<div>
<slot />
</div>
</template>
공식 문서 기준으로 레이아웃이 적용되려면 app.vue에 <NuxtLayout>이 있어야 한다.
실무에서 쓰고 있는 레이아웃은 헤더와 푸터 컴포넌트 사이에 <slot />을 둔 모양이다.
<template>
<div id="page" class="page font--jakarta">
<HeaderLayout1 />
<!-- 페이지 컴포넌트가 이 자리에 렌더링된다 -->
<slot />
<FooterLayout1 />
</div>
</template>
<slot />은 페이지를 렌더링하는 특수한 태그다. 레이아웃에 <slot />이 없으면 페이지가 들어갈 자리가 없다. 이상하게 동작할 수 있으니 레이아웃에는 반드시 넣는다.
페이지 영역을 숨길 때는 slot을 감싼다
레이아웃은 그대로 두고 페이지 영역만 숨겨야 할 때가 있다. 이때도 <slot />을 지우지 말고 v-show 같은 디렉티브로 제어한다. 단, v-show는 <slot />이 아니라 그 상위 Element에 붙여야 한다.
<template>
<div>
<Header />
<!-- ✓ v-show는 slot을 감싼 div에 건다 -->
<div v-show="false">
<slot />
</div>
<Footer />
</div>
</template>
<slot /> 자체는 DOM Element로 남지 않고 내용으로 치환된다. 그래서 디렉티브를 걸 대상은 실제로 렌더링되는 상위 Element여야 한다.
(레이아웃에는 <slot />이 꼭 있어야 한다. 숨길 때는 slot을 감싼 상위 Element에 v-show를 건다.)
definePageMeta로 페이지마다 레이아웃을 고른다
기본값은 default이고, 각 페이지 컴포넌트에서 definePageMeta로 쓸 레이아웃을 바꾼다. layout에 적는 이름은 layouts 디렉토리 안의 파일 이름이다. 아래 예시라면 layouts/custom.vue를 쓴다.
<script lang="ts" setup>
definePageMeta({
layout: "custom",
});
</script>
setPageLayout으로 실행 중에 바꾼다
어떤 조건에 따라 레이아웃을 동적으로 바꾸고 싶다면 setPageLayout 메서드를 쓴다.
<script lang="ts" setup>
// ...
setPageLayout('custom2')
</script>
definePageMeta가 페이지에 붙는 고정값이라면 setPageLayout은 실행 중에 부르는 함수다. 공식 문서는 서버 쪽에서 레이아웃을 바꿀 때 Vue가 레이아웃을 렌더링하기 전, 즉 plugin이나 route middleware 안에서 호출하라고 안내한다. 그렇지 않으면 hydration mismatch가 날 수 있다. layouts 문서의 컴포넌트 예시도 setup에서 바로 부르지 않고 버튼 클릭 핸들러 안에서 호출한다. 위 코드처럼 setup에서 부를 때는 이 점을 한 번 확인해 두는 게 좋다.