본문 바로가기
개발/Next.js

[Next] Susponse, Error Boundary 적용 후기(feat.SWR)

by 핸디(Handy) 2021. 12. 20.

도입부

 

[리액트] React-query 도입과 Suspense, Error Boundary를 적용

React-query는 왜 도입했는가? 실제로 처음 접한 라이브러리는 react-query가 아닌 swr입니다. 진행하고 있는 사이드 프로젝트가 Next를 사용하고 있고 그래서 자연스레 swr를 도입했습니다. 사용하다 보

all-dev-kang.tistory.com

먼저 위의 글을 읽고 오시면 React-query 또는 SWR을 이용해 Susponse와 ErrorBoundary를 처리하는 구조을 왜 적용하려는 지에 대한 의문이 약간이나마 해소되실 겁니다.

다시 한번 상기해보면 SWR의 useSWR은

다음과 같은 형태를 가지고 있고 data와 error 주고 해당 값을 통해 상황에 맞는 랜더링을 하도록 예시를 제공하고 있습니다.

error -> ErrorBoundary가 처리하고,
loading -> Suspense가 처리하고,
저는 data가 잘 오는 경우에만 랜더링에 신경쓰고 싶다는 생각이 들었습니다.

그리고 하고있던 사이트프로젝트에 바로  ErrorBoundary와 Suspense를 적용해보도록 하겠습니다.


적용 모습

Suspese 적용 화면과 ErrorBoundary 적용 화면

일단 적용화면을 보기 전에 기존 컴포넌트를 확인하겠습니다. ( 오른쪽은 Error 창은 개발모드라 뜹니다 )

Next의 맨 처음 index.tsx 파일입니다. 해당 컴포넌트에서 SEO와 HomePage를 랜더링 합니다.

이번에는 여기 HomePage에 ErrorBoundary와 Suspense를 도입시켜보겠습니다.


1. Next에서 Suspense 쓰기

일단 Next에서 Suspense를 사용하기 위해서 dynamic import를 해줍시다.

옵션에 있는 SSR은 나중에 다시 살펴볼게요

 

Advanced Features: Dynamic Import | Next.js

Dynamically import JavaScript modules and React Components and split your code into manageable chunks.

nextjs.org

 

2. SWR에서 Suspense 쓴다고 알려주기

전 과 후

export function useTopicTeacherList() {
  const { data } = useSWR("topicTeacherList", getTopicTeacherList, { dedupingInterval: 600000, suspense: true });

  return {
    topicTeacherList: data,
  };
}

코드를 보면 isLoading과 isError에 대한 값이 사라집니다. 이건 이제 누군가 알아해 해줄거니까요.

3. 랜더링 컴포넌트에서는 제대로 온 Data에만 신경 쓰기

이전 코드는 위에 사진처럼 error와 data가 없을 때에 대한 로딩, 에러 컴포넌트를 return 해줬습니다. 근데 이걸 부모로 제어권한은 넘기게 되면

다음과 같이 topicTeacherList(실제 Data)에만 신경 쓸 수 있는 코드가 됩니다.

4. 부모 컴포넌트에서 제어하기

이렇게 HomePage 컴포넌트에서는 제대로 데이터가 오는 경우만 신경을 쓰고 에러와 로딩 중은 부모 컴포넌트로 제어를 넘기게 되었습니다. 이를 LOC(제어의 역전)이라고 합니다.

실제로는 자식 컴포넌트에서 로딩 또는 에러가 일어났는데 부모 컴포넌트가 처리하는 구조가 되었습니다.

HomePage를 Susponse와 ErrorBoundary가 깜싸고 있다.

이렇게 하면 자식 컴포넌트가 딱 자신에게 필요한 자료만 랜더링을 하게 되고 에러, 로딩중은 별도로 신경 쓸 필요가 없게 되는 깔끔한 구조가 됩니다.

다음은 ErrorBoundary.tsx 코드입니다.

예시 코드인데 Typescript에서 제가 사용한 ErrorBoundary 컴포넌트입니다. 일반적으로 에러가 일어났을 때에 log 관련된 기능을 넣기도 합니다

// ErrorBoundary.tsx
import React, { Component, ErrorInfo, ReactNode } from "react";
interface Props {
  children: ReactNode;
  fallback: ReactNode;
}

interface State {
  hasError: boolean;
}

class ErrorBoundary extends Component<Props, State> {
  public state: State = {
    hasError: false,
  };

  public static getDerivedStateFromError(_: Error): State {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    // console.error("Uncaught error:", error, errorInfo);
  }

  public render() {
    if (this.state.hasError) {
      return this.props.fallback;
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

 

5. Next.js 에서 사용하기

일단 Suspense가 react18의 기능이니 react18로 업데이트를 하고 Next에서 바로 쓰려면 문제가 생깁니다.

그래서 next.config.js에 아래 코드를 밀어 넣어줘야 합니다.

module.exports = {
  experimental: {
    reactMode: "concurrent",
  },
};

첫 번째 과정에서 dynamic import 하는데 ssr : false로 줬던 것을 기억하십니까?
해당 옵션을 빼고 한번 테스트해보겠습니다.

???

혼종이 생겼습니다. 그리고 이분은 이지우 프로님이신데 조진형 프로라고 잘못 매칭이 됩니다.

console 창에도 에러가 떠있습니다.

이는 바로 Next.js SSR의 특징 중 하나인 Hydrate 때문입니다.

Hydrate는 Server Side 단에서 렌더링 된 정적 페이지와 번들링 된 JS파일을 클라이언트에게 보낸 뒤, 클라이언트 단에서 HTML 코드와 React인 JS코드를 서로 매칭 시키는 과정을 말한다.

즉 ssr : false 옵션을 주지 않으면 Pre-rendering 된 HTML 코드가 날아오고 이후 데이터가 불려 온 뒤로 React와 JS코드가 매칭 되는 과정이 일어나는데 그 사이에 스타일이 없는 HTML 코드가 드러나게 되어 저런 상태가 되는 겁니다.

또한 현재 홈페이지에서는 프로선수님들의 순서를 랜덤 하게 표시해주는 기능이 들어가 있고 이는 매번 화면에 접근할 때마다 바뀌는 상황입니다.

근데 Hydrate의 경우 그렇게 똑똑하지 못해 HTML Tag를 보고 React, JS를 매칭 하기 때문에 같은 컴포넌트가 여러 개 있을 경우 저렇게 섞여버리는 상황이 발생합니다.

따라서 이 페이지에는 SSR이 필요 없고 매번 데이터 가져와서 랜더링 치라는 의미로 ssr: false가 필요합니다.


마무리

일단 Suspense기능이 react 18에 들어온 기능입니다. 그래서 실험적인 기능이라고 하는데 제가 볼 땐 살아남을듯합니다. 그래서 과감히 업데이트를 하고 사용하고 있습니다.
근데 빌드할 때마다 실험적인 기능이라고 경고를 주긴 하지만 괜찮습니다. ㅋㅋ

이렇게 원하는 대로 React의 Suspense, ErrorBoundary를 사용하여 로딩과 에러 처리를 하는 아름다운 구조를 만들어봤습니다.

이게 정답인지는 모르겠습니다. 근데 내 기준으로 코딩이 편해졌으니 일단 좋습니다 

혹시 더 좋은 방법이나 이게 안티 패턴이라면 댓글로 욕해주시길 바랍니다.

댓글