학습 자료

약속된 이름들 — layout, loading, error


Article

2강에서 page.jslayout.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.jshtml · body · 헤더 · 푸터
    • app/dashboard/layout.js사이드바
      • app/dashboard/page.js/dashboard
      • app/dashboard/settings/page.js/dashboard/settings
    • app/about/page.js/about — 사이드바 없음

/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로 이동하면 즉시 "불러오는 중"이 뜨고, 데이터가 도착하면 진짜 화면으로 바뀐다.

loading.js 가 있을 때와 없을 때
없을 때
이전 페이지 그대로갑자기 새 페이지
있을 때
로딩 화면새 페이지
클릭데이터 도착
사용자는 즉시 반응을 본다. 클릭이 먹었는지 의심하지 않는다

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>
  );
}

두 가지를 눈여겨본다.

error.js 의 규칙
  1. 01

    반드시 클라이언트 컴포넌트다

    "use client" 가 맨 위에 있다. 다시 시도 버튼에 onClick 이 필요하기 때문이다.

    "use client";
  2. 02

    error 와 reset 을 props 로 받는다

    error 는 터진 예외, reset 은 다시 렌더링을 시도하는 함수다.

  3. 03

    자기 자신은 못 잡는다

    레이아웃에서 터진 에러는 같은 폴더의 error.js 가 못 잡는다. 한 단계 위의 error.js 가 잡는다.

세 번째가 함정이다.

에러가 위로 올라간다
  • app/error.jsdashboard/layout.js 의 에러를 여기서 잡는다
    • app/dashboard/layout.js여기서 터진 에러는
      • app/dashboard/error.js이걸로는 못 잡는다
      • app/dashboard/page.jspage 의 에러는 잡는다

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과 같은 흐름이다.

파일들이 겹쳐지는 순서

다섯 개가 다 있으면 이런 모양으로 감싸진다.

바깥에서 안으로
  1. layout껍데기
  2. error예외를 잡는다
  3. loading기다리는 동안
  4. page진짜 화면

코드로 쓰면 이렇다.

<Layout>
  <ErrorBoundary fallback={<Error />}>
    <Suspense fallback={<Loading />}>
      <Page />
    </Suspense>
  </ErrorBoundary>
</Layout>

우리는 파일만 만들었을 뿐인데 Next.js가 이 구조를 만들어준다. 1강에서 말한 "Next.js가 내 코드를 부른다"가 이것이다.

template.js — 상태를 유지하지 않는 레이아웃

드물게 쓰이지만 알아두면 좋다. layout.js는 이동해도 다시 렌더링되지 않는다고 했다. 반대가 필요할 때가 있다.

layout 과 template

layout.js

유지된다

  • 이동해도 다시 안 그린다
  • 상태와 스크롤이 남는다
  • 사이드바 · 헤더
  • 대부분 이걸 쓴다

template.js

매번 새로 만든다

  • 이동할 때마다 새 인스턴스
  • 상태가 초기화된다
  • 페이지 진입 애니메이션
  • 가끔 필요하다

페이지가 바뀔 때마다 페이드인 애니메이션을 넣고 싶다면 template.js를 쓴다. layout.js에 넣으면 처음 한 번만 재생된다.

정리하면

  • Next.js는 정해진 파일 이름을 알아본다. import도 등록도 필요 없다.
  • layout.js중첩된다. 폴더마다 두면 겹겹이 쌓인다. 이동해도 다시 렌더링되지 않는다.
  • loading.js를 만들면 로딩 화면이 생긴다. 내부적으로 **<Suspense>**로 감싸는 것이다.
  • error.js반드시 클라이언트 컴포넌트이고, errorreset을 받는다.
  • 같은 폴더의 layout.js 에러는 못 잡는다. 한 단계 위가 잡는다.
  • notFound()를 부르면 렌더링이 멈추고 not-found.js가 뜬다.
  • template.js는 이동할 때마다 새로 만들어진다. 진입 애니메이션에 쓴다.

다음 강의에서는 /posts/1, /posts/2처럼 주소가 변하는 페이지를 만든다. Next 16에서 문법이 바뀐 부분이다.

약속된 이름들 — layout, loading, error — 디코드랩(DCODELAB)