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
공식 문서에서 예시로 나온 코드에서처럼 PostFeed
와 Weather
컴포넌트를 만들어서 사용하였습니다. 각각의 컴포넌트를 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.tsx
를 about
폴더에 만들어 주었습니다.
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.tsx
와 error.tsx
를 만들어 쉽게 로딩 및 에러 처리를 할 수 있었습니다. 또한 <Suspense>
경계로 래핑해 주면 컴포넌트 관련 로딩도 자동으로 처리해 주기 때문에 코드를 더욱 간단하게 작성할 수 있었습니다.