본문 바로가기
4주차

🛠️ React Suspense로 비동기 처리와 UX 개선하기

by 지숭숭숭 2025. 5. 13.

안녕하세요 웹 YB 임지수입니다!

다들 데이터를 불러올 때 로딩 처리에 대해 한 번쯤 고민해 보신 적 있지 않으신가요?

저도 프로젝트를 진행하면서 언제 로딩 UI를 띄울지, 어떻게 띄울지에 대해 고민했던 적이 많았습니다.

 

그 과정에서 React의 Suspense라는 기능을 알게 되었습니다.

Suspense를 활용하면 효율적인 로딩 처리뿐만 아니라 UX 개선까지 자연스럽게 이어질 수 있었습니다!!

 

이번 글에서는 Suspense를 중심으로 기존의 비동기 처리 방식부터 Suspense를 어떻게 사용하고 주의할 점은 어떤 게 있는지까지 알아보려고 합니다.

 

 

 

 

 

 

📍 React에서 비동기 처리 방식

Fetch-on-render

컴포넌트 렌더링 후 데이터 가져오기 → 데이터 가져오는 동안 빈 상태나 로딩 화면 보임

 

Fetch-then-render

데이터 먼저 가져오고 컴포넌트 렌더링 → 데이터 페칭 완료까지 렌더링 지연

 

Render-as-you-fetch

데이터 가져오는 동시에 컴포넌트 렌더링 → Suspense 사용할 때 이 방식 활용

 

 

 

 

리액트에서 비동기 데이터를 불러와야 할 때 보통은 useEffect와 useState를 조합해 아래처럼 작성합니다.

import { useEffect, useState } from 'react';

function Albums() {
  const [albums, setAlbums] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/albums')
      .then(res => res.json())
      .then(data => {
        setAlbums(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>로딩 중...</p>;

  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>{album.title}</li>
      ))}
    </ul>
  );
}

이 방식은 많은 분들이 흔히 사용하는 패턴입니다.

하지만 이렇게 작성하다 보면 몇 가지 문제에 부딪히게 되는데요,,

 

 

‼️Side Effect 문제가 발생합니다

→ fetch()는 렌더링 외부에서 일어나는 부수 효과입니다.

→ useEffect는 렌더링 이후에 실행되기 때문에 데이터가 오기 전까지 화면에 빈 컴포넌트나 깜빡임이 보일 수 있습니다.

→ 컴포넌트가 언마운트되기 전에 fetch 응답이 오면 메모리 누수 경고 발생할 수 있습니다.

 

 

 

🤔 기존 방식의 한계

Suspense 없이 로딩을 처리하려면 다음과 같은 문제점이 생깁니다

  • 매 컴포넌트마다 loading, setLoading 상태를 따로 선언해야 함
  • useEffect로 fetch 후 상태 변경을 일일이 관리해야 함
  • 여러 컴포넌트가 동시에 로딩될 경우, 컴포넌트 단위로 분리된 로딩 처리가 어려움
  • 로딩 UI가 중복되고 유지 보수가 어려워짐

 

 

 

💉 Suspense가 해결 가능

React 18부터는 Suspense를 활용한 비동기 컴포넌트 처리가 가능합니다.

import { Suspense } from 'react';
import Albums from './Albums';

function App() {
  return (
    <Suspense fallback={<p>로딩 중...</p>}>
      <Albums />
    </Suspense>
  );
}

이렇게 Suspense는 비동기적으로 로딩되는 컴포넌트를 감싸서 로딩 중에는 fallback UI를 보여주고 데이터가 준비되면 본 UI를 렌더링 합니다.

컴포넌트 자체에 loading 상태를 따로 관리하지 않아도 되고 로딩 처리를 선언적으로 분리할 수 있어 코드도 깔끔해집니다.

 

import { Suspense } from 'react';
import UserProfile from './UserProfile';
import UserPosts from './UserPosts';

function App() {
  return (
    <>
      <Suspense fallback={<p>프로필 로딩 중...</p>}>
        <UserProfile />
      </Suspense>

      <Suspense fallback={<p>게시글 로딩 중...</p>}>
        <UserPosts />
      </Suspense>
    </>
  );
}

이렇게 컴포넌트 단위로 개별적인 로딩 UI를 구성할 수 있어 각 영역마다 자연스러운 사용자 경험을 제공할 수 있습니다.

 

 

 

👍🏻 Suspense의 장점

① 로딩 UI를 선언적으로 관리 → fallback으로 간단히 로딩 화면 처리 가능

 

② 컴포넌트 단위 제어 → 하위 컴포넌트 개별 로딩 처리 가능

 

③ 깜빡임 최소화 → 렌더 전에 fetch 전략으로 깜빡임 없는 UI

 

④ 라이브러리와 시너지 → React Query, Next.js 등과 함께 사용하면 진가 발휘

 

 

 

🌟Suspense 사용 시 주의할 점

- 기본 fetch()만으론 Suspense를 사용할 수 없습니다.
→ Suspense는 컴포넌트가 필요한 데이터를 아직 불러오지 못한 상태를 감지하고, 그동안 fallback UI를 보여주는 방식입니다.

     React Query, Relay와 같이 Suspense를 지원하는 데이터 패칭 라이브러리를 사용하는 것이 필요합니다.

import { useQuery } from '@tanstack/react-query';

const fetchAlbums = async () => {
  const response = await fetch('/api/albums');
  if (!response.ok) {
    throw new Error('Failed to fetch albums');
  }
  return response.json();
};

function Albums() {
  const { data } = useQuery(['albums'], fetchAlbums, {
    suspense: true,
  });

  return (
    <ul>
      {data.map((album) => (
        <li key={album.id}>{album.title}</li>
      ))}
    </ul>
  );
}

위의 코드에서 useQuery는 Suspense와 함께 작동하도록 되어 있습니다.

suspense: true를 추가하면 React Query는 데이터 로딩 중 Suspense의 fallback UI를 자동으로 표시하고 데이터가 준비되면 컴포넌트를 렌더링합니다.

 

- Next.js 같이 서버에서 렌더링 하는 프레임워크에서는 완전히 자유롭지 않다.

 서버 사이드 렌더링 환경에서는 Suspense 지원이 아직 제한적이거나 실험적인 경우가 많아 사용 전 공식 문서를 확인하거나 테스트해 보는 것을 추천드립니다.

 

- fallback에는 복잡한 로직을 넣지 않는다.

 Suspense의 fallback은 단순 대체 UI를 보여주기 용도이기 때문에 그 안에서 상태를 변경하거나 API 호출을 하는 등 복잡한 로직은 피하는 게 좋습니다.

<Suspense fallback={setLoading(true)}> // 상태 변경 X

 

 

 

 

Suspense가 아직 모든 상황에서 완벽한 해결책은 아니지만 꽤 유용한 도구라고 생각합니다.

특히 React Query 같은 라이브러리와 함께 사용할 때 유용하게 활용할 수 있으니 관심 있으시면 같이 적용해 보는 걸 추천드려요!

이 방식 말고도 다른 여러 방식이 있겠지만, 하나의 선택지로 활용해 보시면 좋을 거 같습니다 ~ ~😊

 

 

 

🔗 참고

<Suspense> – React

 

<Suspense> – React

The library for web and native user interfaces

react.dev