[Next.js 챌린지] DAY15 컴포넌트 스트리밍, 스켈레톤UI, 에러핸들링

정재은·2024년 9월 29일

Next.js 챌린지

목록 보기
14/16
post-thumbnail

#section6

3. 스트리밍2) 컴포넌트 스트리밍 적용하기

컴포넌트 스트리밍은 페이지 스트리밍보다 더 세밀하게 스트리밍을 설정할 수 있다


실습하기

  1. 기존 loading.tsx 파일 삭제

  2. 페이지 컴포넌트 내부에서 비동기 작업중인 코드를 분리

원본


수정본 (비동기 작업 분리)


  1. 리액트 컴포넌트인 Suspense로 감싸주기

적용 결과

loading.tsx파일을 이용할 때랑 동일하게 쿼리스트링만 변경하였을 때
로딩상태가 표시되지 않는 문제가 발생한다


Suspense key

하지만 Suspense에서는 key 값을 추가하여 이를 해결할 수 있다
key에 해당하는 값이 변경될 때 마다 로딩 표시가 나타난다

<Suspense key={searchParams.q || ''} fallback={<div>Loading ...</div>}>

적용 결과




4. 스켈레톤 UI 적용하기

  • 페이지의 일부 컨텐츠가 로딩이 되는 동안 실제 컨텐츠 대신 보여지게 될 UI
  • 훨씬 나은 사용자 경험을 제공

ex) 유튜브의 스켈레톤 UI


실습하기

  1. skeleton 폴더 생성
    book-item-skeleton.tsx 파일 생성
    book-item-skeleton.module.css 파일 생성

  1. 뼈대 만들기
// 📄 src/components/skeleton/book-item-skeleton.tsx
import style from './book-item-skeleton.module.css';

export default function BookItemSkeleton() {
  return (
    <div className={style.container}>
      <div className={style.cover_img}></div>
      <div className={style.info_container}>
        <div className={style.title}></div>
        <div className={style.subTitle}></div>
        <br />
        <div className={style.author}></div>
      </div>
    </div>
  );
}

  1. 스타일링
📄 /* src/components/skeleton/book-item-skeleton.module.css */

.container {
  display: flex;
  gap: 15px;
  padding: 20px 10px;
  border-bottom: 1px solid rgb(220, 220, 220);
}

.cover_img {
  width: 80px;
  height: 105px;
  background-color: rgb(230, 230, 230);
}

.info_container {
  flex: 1;
}

.title,
.subTitle,
.author {
  width: 100%;
  height: 20px;
  background-color: rgb(230, 230, 230);
}

  1. Suspense 컴포넌트의 fallback으로 적용
<Suspense fallback={
      <>
        <BookItemSkeleton />
        <BookItemSkeleton />
        <BookItemSkeleton />
      </>
    }
>
...

새로고침 후 결과 확인



* 참고
react-loading-skeleton
자동으로 스켈레톤 UI를 만들어주는 라이브러리




5. 에러 핸들링

백엔드 서버를 끄고 실습 진행

에러 핸들링 하려는 페이지와 같은 레벨에 error.tsx 파일 생성

// 📄 src/app/(with-searchbar)/error.tsx
'use client';

export default function Error() {
  return (
    <div>
      <h3>오류가 발생했습니다</h3>
    </div>
  );
}

use client를 통해 클라이언트 컴포넌트로 설정하는 이유는
오류는 서버측에서도, 클라이언트측에서도 발생할 수 있기 때문에
두가지 상황에 대응하기 위함이다



props

1. error

error 를 통해 오류의 정보를 받아올 수 있다

// 📄 src/app/(with-searchbar)/error.tsx

'use client';

import { useEffect } from 'react';

export default function Error({ error }: { error: Error }) {
  useEffect(() => {
    console.error(error.message);
  }, [error]);

  return (
    <div>
      <h3>오류가 발생했습니다</h3>
    </div>
  );
}

적용 결과


2. reset

에러가 발생한 페이지를 복구하기 위해 다시 한 번 컴포넌트를 렌더링 시키는 함수

'use client';

import { useEffect } from 'react';

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  useEffect(() => {
    console.error(error.message);
  }, [error]);

  return (
    <div>
      <h3>오류가 발생했습니다</h3>
      <button onClick={() => reset()}>다시 시도</button>
    </div>
  );
}

적용 결과


백엔드 서버 실행 후 다시 시도 버튼 클릭

그런데 계속해서 오류가 발생한다

reset이라는 메서드는 클라이언트 측에서만 화면을 다시 렌더링한다
즉, 서버를 다시 실행하지 않기 때문에 데이터가 없으므로 계속해서 오류가 발생한다



오류가 발생한 부분만 새롭게 렌더링하기

페이지를 강제로 새로고침 하여 해결할 수 있다

  1. startTransition과 useRouter를 import 하고
  2. button 코드 수정
import { useRouter } from 'next/navigation';
import { startTransition} from 'react';

const router = useRouter();

 <button
  onClick={() => {
    startTransition(() => {
      router.refresh();
      reset();
    });
  }}
>

startTransition( )
하나의 콜백 함수를 인수로 전달 받아서 콜백 함수 안에 들어있는 UI를 변경 시키는 작업을 일괄적으로 처리

refresh( )
현재 페이지에 필요한 서버 컴포넌트들을 다시 불러옴

reset( )
에러 상태를 초기화하고 컴포넌트들을 다시 렌더링


적용 결과

profile
프론트엔드

0개의 댓글