Post

Nextjs에서 loading과 error 다루기

팀 프로젝트를 진행하던 도중 Next.js에서는 React와는 다르게 페이지 단위 로딩을 쉽게 구현할 수 있는 사실을 공식 문서에서 확인할 수 있었습니다. 또한 에러 처리를 할 수 있는 코드도 React 와는 다르게 쉽게 작성할 수 있기 때문에 오늘은 에러처리와 로딩을 어떻게 사용하는지 간단한 사용법과 프로젝트를 만들어 보겠습니다.

이 글은 2024-06-23 에 업데이트 되었습니다.

이 글은 HTML, CSS , JavaScript, TypeScript, React, Next.js에 대한 기본적인 지식을 알고 있어야 합니다.

로딩

Next.js에서 로딩을 구현하는 방법은 특수 파일인 loading.js를 만들어 사용이 가능합니다. 아래의 이미지 처럼 원하는 페이지의 폴더 안에 loading.js를 만들어 주면 됩니다.

로딩 이미지

1
2
3
4
export default function Loading() {
  // 원하는 로딩 컴포넌트를 사용하기
  return <LoadingSkeleton />;
}

그리고 같은 폴더에서 Loading을 사용할 경우 같은 layout.js 안에 중첩됩니다. page.js 파일과 그 아래의 모든 하위 파일을 <Suspense> 경계로 자동으로 래핑합니다. 아래의 이미지처럼 동작하게 됩니다.

로딩 이미지

또한 loading.js 외에도 자체 UI 컴포넌트에 대한 Suspense 바운더리를 수동으로 생성할 수도 있습니다. 앱 라우터는 Node.js 및 Edge 런타임 모두에 대해 Suspense를 사용한 스트리밍을 지원합니다.

아래의 코드처럼 Suspense를 이용해 각각의 컴포넌트에 로딩을 적용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Suspense } from "react";
import { PostFeed, Weather } from "./Components";

export default function Posts() {
  return (
    <section>
      <Suspense fallback={<p>Loading feed...</p>}>
        <PostFeed />
      </Suspense>
      <Suspense fallback={<p>Loading weather...</p>}>
        <Weather />
      </Suspense>
    </section>
  );
}

에러

Next.js에서는 예기치 않은 에러를 처리하기 위해 error.js라는 특수 파일을 사용할 수 있습니다. 이 파일은 서버 컴포넌트와 클라이언트 컴포넌트에서 발생하는 예기치 않은 오류를 포착하고 UI를 표시할 수 있습니다.

아래의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"use client"; // 에러 컴포넌트는 클라이언트에서만 사용합니다.

import { useEffect } from "react";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // useEffect로 에러의 로그를 확인할 수 있습니다.
    console.error(error);
  }, [error]);

  return (
    <div>
      <h2>Something went wrong!</h2>
      <button
        onClick={
          // 리셋 버튼으로 리 랜더링을 시도할 수 있습니다.
          () => reset()
        }
      >
        Try again
      </button>
    </div>
  );
}

루트 레이아웃에서 오류를 처리하려면 루트 경로에 있는 app/global-error.js 파일을 사용하세요. 이 파일은 모든 오류를 처리하고 오류 정보를 전달합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"use client";

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  );
}

간단한 프로젝트

이제 위에서 배운 것들을 활용할 간단한 프로젝트를 만들어 보겠습니다. 우선 페이지를 home, about, error-boundary 라는 3개의 페이지를 만들고 각각 페이지에는 공통 컴포넌트로 페이지를 이동할 수 있는 네비게이션 바와 페이지 이동 시에는 잠깐의 로딩을 주고 error-boundary에서는 고의적으로 error를 던져서 에러 처리를 해 보겠습니다.

루트 레이아웃

루트 레이아웃에는 전체 UI 레이아웃과 Navbar를 위치시켜주었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Navbar from "./components/navbar";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body
        className={
          inter.className +
          "flex min-h-screen flex-col items-center justify-between p-24"
        }
      >
        <Navbar />
        {children}
      </body>
    </html>
  );
}

홈 페이지

홈 페이지에서는 아무런 로딩이 발생하지 않게 하기 위해 loading.js를 생성하지 않았습니다. 대신 Next.js 공식 문서에서 예시로 나온 코드에서처럼 PostFeedWeather 컴포넌트를 만들어서 사용하였습니다. 각각의 컴포넌트를 1초간 지연시켜 주었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
export default function Home() {
  return <main className="border p-10 mt-5">홈 페이지</main>;
}

async function PostFeed() {
  await delay(2000);
  return <div>PostFeed</div>;
}
async function Weather() {
  await delay(2500);
  return <div>Weather</div>;
}
1
2
3
4
// util
export async function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

About 페이지

About 페이지에서는 로딩을 발생시키기 위해 홈 페이지에서 사용했었던 delay 함수를 사용하였습니다. 그리고 이 페이지에서만 사용할 loading.tsxabout 폴더에 만들어 주었습니다.

1
2
3
4
5
6
import { delay } from "@/util/util";

export default async function About() {
  await delay(1000);
  return <main className="border p-10 mt-5">About 페이지</main>;
}
1
2
3
4
// loading.tsx
export default function Loading() {
  return <div className="border p-10 mt-5">로딩중...</div>;
}

check-error 페이지

check-error 페이지에서는 로딩을 발생시키기 위해 delay 함수를 그대로 사용해 주었고, 로딩이 끝나면 일부러 에러를 발생시켜 주었습니다.

그리고 같은 폴더에 error.tsx를 만들어 에러가 발생했을 때 처리를 해 주었습니다.

1
2
3
4
5
6
7
import { delay } from "@/util/util";

export default async function CheckError() {
  await delay(1000);
  throw new Error("일부러 발생한 에러!");
  return <main className="border p-10 mt-5">에러 페이지</main>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"use client"; // Error components must be Client Components

import { useEffect } from "react";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Log the error to an error reporting service
    console.error(error);
  }, [error]);

  return (
    <div className="border border-red-500 text-center space-y-4 p-10 mt-5">
      <h2>에러가 발생했습니다! 아래는 에러의 내용입니다.</h2>
      <p>{error.message}</p>
      <button
        className="bg-red-500 p-2 rounded-md"
        onClick={
          // Attempt to recover by trying to re-render the segment
          () => reset()
        }
      >
        Try again
      </button>
    </div>
  );
}

codesandbox에서 확인하기

모든 코드는 codesandbox에서 확인하실 수 있습니다. 직접 navbar를 이용하여 페이지를 이동하시고 로딩과 에러가 어떤 식으로 에러가 처리되는지 확인해 보실 수 있습니다.

결론

Next.js에서는 같은 폴더에 loading.tsxerror.tsx를 만들어 쉽게 로딩 및 에러 처리를 할 수 있었습니다. 또한 <Suspense> 경계로 래핑해 주면 컴포넌트 관련 로딩도 자동으로 처리해 주기 때문에 코드를 더욱 간단하게 작성할 수 있었습니다.

This post is licensed under CC BY 4.0 by the author.