약속된 이름들 — layout, loading, error
Article
2강에서 page.js와 layout.js를 봤다. Next.js가 알아보는 이름은 더 있다.
정해진 이름을 정해진 폴더에 두면 Next.js가 알아서 쓴다. import도, 등록도 필요 없다.
다섯 개의 파일
- app/dashboard/
- page.js
- layout.js
- loading.js
- error.js
- not-found.js
각각이 무엇을 하는지, 그리고 왜 이런 구조인지 본다.
layout 은 중첩된다
app/layout.js가 모든 페이지를 감싼다는 건 봤다. 그런데 폴더마다 layout.js를 둘 수 있다.
- app/layout.js
- app/dashboard/layout.js
- app/dashboard/page.js
- app/dashboard/settings/page.js
- app/about/page.js
- app/dashboard/layout.js
/dashboard/settings로 가면 루트 레이아웃 안에 대시보드 레이아웃이 들어가고, 그 안에 페이지가 들어간다.
<RootLayout>
<DashboardLayout>
<SettingsPage />
</DashboardLayout>
</RootLayout>
/about은 대시보드 레이아웃을 거치지 않는다. 폴더 밖에 있기 때문이다.
loading.js — 공짜로 얻는 로딩 화면
3강에서 서버 컴포넌트는 await로 데이터를 기다린다고 했다. 그동안 사용자는 무엇을 볼까.
export default async function Posts() {
const posts = await getPosts();
return <ul>…</ul>;
}
데이터가 3초 걸리면 3초 동안 아무것도 안 보인다. 이전 페이지에 머물러 있다가 갑자기 바뀐다.
app/posts/loading.js를 만든다.
export default function Loading() {
return <p>불러오는 중…</p>;
}
끝이다. 이제 /posts로 이동하면 즉시 "불러오는 중"이 뜨고, 데이터가 도착하면 진짜 화면으로 바뀐다.
error.js — 무너지지 않게 막는다
컴포넌트에서 예외가 터지면 어떻게 될까. error.js가 없으면 화면 전체가 하얘진다.
"use client";
export default function Error({ error, reset }) {
return (
<div>
<h2>문제가 생겼다</h2>
<p>{error.message}</p>
<button onClick={reset}>다시 시도</button>
</div>
);
}
두 가지를 눈여겨본다.
- 01
반드시 클라이언트 컴포넌트다
"use client"가 맨 위에 있다. 다시 시도 버튼에onClick이 필요하기 때문이다."use client"; - 02
error 와 reset 을 props 로 받는다
error는 터진 예외,reset은 다시 렌더링을 시도하는 함수다. - 03
자기 자신은 못 잡는다
레이아웃에서 터진 에러는 같은 폴더의 error.js 가 못 잡는다. 한 단계 위의 error.js 가 잡는다.
세 번째가 함정이다.
- app/error.js
- app/dashboard/layout.js
- app/dashboard/error.js
- app/dashboard/page.js
- app/dashboard/layout.js
error.js는 같은 폴더의 layout.js 안에 들어가기 때문이다. 레이아웃이 이미 터졌다면 그 안의 에러 화면도 못 그린다.
not-found.js — 없는 것을 찾을 때
없는 주소로 가면 Next.js가 기본 404 화면을 보여준다. 바꾸려면 app/not-found.js를 만든다.
import Link from "next/link";
export default function NotFound() {
return (
<div>
<h2>페이지를 찾을 수 없다</h2>
<Link href="/">홈으로</Link>
</div>
);
}
주소는 맞는데 데이터가 없을 때도 쓴다. 예를 들어 /posts/9999인데 그런 글이 없다면.
import { notFound } from "next/navigation";
export default async function Post({ params }) {
const { id } = await params;
const post = await getPost(id);
if (!post) notFound();
return <article>{post.title}</article>;
}
notFound()를 부르면 즉시 렌더링을 멈추고 not-found.js를 보여준다. React 편 7강의 early return과 같은 흐름이다.
파일들이 겹쳐지는 순서
다섯 개가 다 있으면 이런 모양으로 감싸진다.
- layout껍데기
- error예외를 잡는다
- loading기다리는 동안
- page진짜 화면
코드로 쓰면 이렇다.
<Layout>
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Loading />}>
<Page />
</Suspense>
</ErrorBoundary>
</Layout>
우리는 파일만 만들었을 뿐인데 Next.js가 이 구조를 만들어준다. 1강에서 말한 "Next.js가 내 코드를 부른다"가 이것이다.
template.js — 상태를 유지하지 않는 레이아웃
드물게 쓰이지만 알아두면 좋다. layout.js는 이동해도 다시 렌더링되지 않는다고 했다. 반대가 필요할 때가 있다.
layout.js
유지된다
- 이동해도 다시 안 그린다
- 상태와 스크롤이 남는다
- 사이드바 · 헤더
- 대부분 이걸 쓴다
template.js
매번 새로 만든다
- 이동할 때마다 새 인스턴스
- 상태가 초기화된다
- 페이지 진입 애니메이션
- 가끔 필요하다
페이지가 바뀔 때마다 페이드인 애니메이션을 넣고 싶다면 template.js를 쓴다. layout.js에 넣으면 처음 한 번만 재생된다.
정리하면
- Next.js는 정해진 파일 이름을 알아본다. import도 등록도 필요 없다.
layout.js는 중첩된다. 폴더마다 두면 겹겹이 쌓인다. 이동해도 다시 렌더링되지 않는다.loading.js를 만들면 로딩 화면이 생긴다. 내부적으로 **<Suspense>**로 감싸는 것이다.error.js는 반드시 클라이언트 컴포넌트이고,error와reset을 받는다.- 같은 폴더의
layout.js에러는 못 잡는다. 한 단계 위가 잡는다. notFound()를 부르면 렌더링이 멈추고not-found.js가 뜬다.template.js는 이동할 때마다 새로 만들어진다. 진입 애니메이션에 쓴다.
다음 강의에서는 /posts/1, /posts/2처럼 주소가 변하는 페이지를 만든다. Next 16에서 문법이 바뀐 부분이다.