데이터 요청 속도 차이로 인한 불규칙한 로딩 UI

liinyeye·2024년 7월 15일
0

Project

목록 보기
29/44

🔎 문제 상황

  • 한 페이지에서 여러 API를 호출하면서 스켈레톤 UI가 비동기적으로 표시되는 문제 발생
  • 데이터 요청 속도 차이로 인한 불규칙한 로딩 UI 표시

🤔 해결 방안

1. useQueries와 Promise.all을 활용한 비동기 요청 병렬 처리

  • 여러 API 요청을 순차적으로 처리할 경우 전체 로딩 시간이 각 요청 시간의 합이 됨
  • React Query의 useQueries로 여러 쿼리를 병렬로 실행하여 전체 대기 시간을 줄일 수 있음

2. prefetchQuery를 통한 서버 사이드 데이터 프리페칭

  • 클라이언트에서만 데이터를 요청하면 초기 페이지 로드 시 빈 화면이 표시됨
  • 서버에서 미리 데이터를 가져와 초기 렌더링에 포함시키면 첫 로드 성능 개선

3. HydrationBoundary를 활용한 클라이언트 상태 관리

  • 클라이언트에서 추가 요청 없이 서버의 초기 상태를 그대로 사용 가능
  • React Query의 캐시 시스템을 활용하여 불필요한 리페치 방지

📝 해결 과정

1. 비동기 요청 최적화

여러 API 요청을 한 번에 병렬로 처리하기 위해 useQueries를 사용

const results = useQueries({
  queries: [
    { queryKey: ["posts", postId], queryFn: () => fetchPosts(postId) },
    { queryKey: [queryKey, postId], queryFn: () => fetchComments({ postId, tableName }) }
  ]
});

2. 서버 사이드 프리페칭

모든 렌더링의 시간을 맞추기 위해 Promise.all을 사용하여 비동기 작업을 동시에 끝낼 수 있도록 함.

await Promise.all([
  queryClient.prefetchQuery({
    queryKey: ["posts", params.id],
    queryFn: () => fetchPosts(params.id)
  }),
  queryClient.prefetchQuery({
    queryKey: ["comments", params.id],
    queryFn: () => fetchComments({ postId: params.id, tableName: "comments" })
  })
]);

3. 클라이언트 상태 관리 최적화

서버 컴포넌트에서 데이터를 prefetching해서 프리렌더링해주어 SEO와 UX도 모두 향상될 수 있도록 함.

<HydrationBoundary state={dehydrate(queryClient)}>
  <Post params={params} />
  <Comment params={params} />
</HydrationBoundary>

트러블 슈팅

문제 상황

  • 서버 컴포넌트에서 내부 API 라우트를 직접 호출하여 데이터 로드 실패
  • Next.js의 서버 컴포넌트는 API 라우트 대신 직접 데이터베이스에 접근하거나 외부 API를 호출해야함

해결 방안

1. 외부 API 엔드포인트 사용

  • 서버 컴포넌트에서는 배포된 API URL을 통해 데이터 접근
  • 내부 라우트 핸들러 대신 실제 API 엔드포인트 활용

2. 데이터 갱신 전략

  • cache: 'no-store' 설정으로 매 요청마다 최신 데이터 확보
  • 서버 컴포넌트에서 prefetch한 초기 데이터를 클라이언트에서 활용
export const fetchComments = async ({ postId, tableName }: FetchCommentProps): Promise<CommonCommentType[]> => {
  try {
    const response = await fetch(`https://music-community-pearl.vercel.app/api/${tableName}/${postId}`, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      },
      cache: 'no-store'
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    return data as CommonCommentType[];
  } catch (error) {
    console.error("댓글 불러오기 실패", error);
    return [];
  }
};

학습 내용

SSG 서버 컴포넌트에서 route handler 호출 방법
SSG 컴포넌트는 build 시 실행되지만, build 상황에서 route handler는 활성화되어 있지 않기 때문에 api url을 변경해줘야 한다.

Bad Practice 🤔

export default async function Home() {
  const data = await fetch("http://localhost:3000/api/json").then(
    (res) => res.json(),
  );

  return <>{data.title}</>;
}

Good Practice 🤩

export default async function Home() {
  const data = await fetch("https://jsonplaceholder.typicode.com/todos/1").then(
    (res) => res.json(),
  );

  return <>{data.title}</>;
}

결과

일관성 있는 로딩으로 로딩 상태 관리 개선!

profile
웹 프론트엔드 UXUI

0개의 댓글