[TIL] 241110_Next.js: Error Handling - Day15

지코·2024년 11월 11일
0

Today I Learned

목록 보기
50/66
post-thumbnail
export default function Page() {
	return (
		<h1>에러 핸들링에 대하여 🤓</h1>
    );
}

⚡️ 에러 핸들링

Next는 에러 핸들링에서 일반적인 try-catch문을 사용하는 것보다 더 효율적인 방법을 제공하고 있는데, 바로 📁 error.tsx 파일을 사용하는 것이다.

'use client';  // 반드시 client component!

import { useEffect } from "react";

export default function Error({ error, reset }: { error: Error, reset: ()=>void }) {
  useEffect(() => {
    console.error(error);
  }, [error]);
  
  return (
    <div>
      <h3>오류가 발생했습니다</h3>
      <button onClick={() => reset()}>다시 시도</button>
    </div>
  );
}

이렇게 📁 error.tsx 파일을 생성하면 이 파일과 같은 위치 혹은 하위 파일까지 모두 에러 핸들링이 적용된다. 마치 대체 UI를 설정하는 것과 같다.
만약 하위 폴더에서 다른 에러 핸들링을 적용하고 싶다면, 해당 경로에서 새로운 📁 error.tsx 파일을 생성해주면 된다.

그리고 이 파일은 ✳️ 반드시 client component ✳️여야 하는데, 이는 서버 측에서 발생하는 에러는 물론이고, 사용자 입력의 유효성 검사나 잘못된 API 요청 등 클라이언트에서 발생하는 에러 상황까지 처리하기 위해서이다.


에러 컴포넌트는 error 말고도 reset 이라는 하나의 props를 더 제공하는데, 이 props는 컴포넌트들을 다시 렌더링하는 기능을 하는 함수이다. 따라서 리렌더링을 위해 버튼을 하나 만들고, 클릭 시 reset 함수가 실행될 수 있도록 구현할 수 있다.

하지만 이 reset 함수는 서버로부터 전달 받은 데이터를 이용해서 화면을 리렌더링하기만 하고, 서버 측에서 실행되는 서버 컴포넌트를 다시 실행하지는 않는다. 따라서 이 방법으로는 클라이언트 내부에서 발생한 오류만 해결할 수 있다.

이걸 해결할 수 있는 방법은

  1. window.location.reload()를 통해 브라우저를 강제로 새로고침한다.

➡️ 하지만 이 방법은 리렌더링이 필요하지 않은 레이아웃 컴포넌트 등까지 모두 리렌더링하기 때문에 비효율적일 수 있다.

  1. useRouter() 의 refresh 메서드를 사용한다.
    (⚠️ next/navigation에서 호출해야 함!)

refresh 메서드를 호출하면 Next.js 서버에게 서버 컴포넌트만 리렌더링해달라고 요청하게 되며, 후에 reset 함수를 호출해서, refresh 메서드를 통해 서버로부터 전달 받은 데이터를 화면에 새롭게 렌더링한다.

만약 refresh 메서드만 사용하게 된다면 에러 상태가 초기화되지는 않는다. 따라서 reset 메서드를 refresh 메서드 호출 후에 호출해야, 에러 상태를 초기화하고 컴포넌트들을 리렌더링할 수 있다.

그래서 다음과 같이 코드를 작성할 수 있다.

'use client';  // 반드시 client component!

import { useRouter } from "next/navigation";
import { useEffect } from "react";

export default function Error({ error, reset }: { error: Error, reset: ()=>void }) {
  const router = useRouter();
  
  useEffect(() => {
    console.error(error);
  }, [error]);
  
  return (
    <div>
      <h3>오류가 발생했습니다</h3>
      <button onClick={() => {
        // 현재 페이지에 필요한 서버 컴포넌트들을 다시 불러올 것을 서버에 요청.
        router.refresh()
        // 에러 상태를 초기화, 컴포넌트들을 리렌더링.
        reset()
      }}>다시 시도</button>
    </div>
  );
}

이 때 refresh 메서드는 비동기적으로 실행되는 함수인데, 위와 같이 작성하게 되면 refresh 메서드 실행이 끝나기도 전에 reset 메서드가 실행되어, 에러 상태가 복구되지 않게 된다.
이 상태는 async-await 키워드를 통해서도 해결할 수 없는데, 이는 refresh 메서드의 반환값이 promise 타입이 아니라 void 타입이기 때문이다❗️

따라서 React 18v부터 추가된 startTransition()이라는 함수를 통해 해결할 수 있다. 이 함수는 하나의 콜백 함수를 인수로 전달 받아, 이 콜백 함수 안에 들어있는 UI를 변경시키는 작업을 일괄적으로 처리해준다.

'use client';  // 반드시 client component!

import { useRouter } from "next/navigation";
import { startTransition, useEffect } from "react";

export default function Error({ error, reset }: { error: Error, reset: ()=>void }) {
  const router = useRouter();
  
  useEffect(() => {
    console.error(error);
  }, [error]);
  
  return (
    <div>
      <h3>오류가 발생했습니다</h3>
      <button onClick={() => {
        startTransition(() => {
          // 현재 페이지에 필요한 서버 컴포넌트들을 다시 불러올 것을 서버에 요청.
          router.refresh()
          // 에러 상태를 초기화, 컴포넌트들을 리렌더링.
          reset()
        })
      }}>다시 시도</button>
    </div>
  );
}

따라서 위와 같이 코드를 수정할 수 있다. 위 코드처럼 작성하면 에러 상태를 일관성 있게 복구할 수 있게 된다.


Reference

👩🏻‍🏫 한입 크기로 잘라 먹는 Next.js(15+) https://www.inflearn.com/course/한입-크기-nextjs/

error.tsx 파일은 왜 client component여야 하는가 https://www.heropy.dev/p/n7JHmI

profile
꾸준하고 성실하게, FE 개발자 역량을 키워보자 !

0개의 댓글