Suspense ↔ (lazy loading, dynamic import, HTML Streaming)Frontend/개발 관련 지식2025. 9. 25. 23:30
Table of Contents
Suspese는 많이 쓰이기도 하지만, SSR, SSG 기반의 Page router의 next와 React Server Component(HTML Streaming) 기반의 App router에서의 용도가 다르다
시간이 지나며 렌더링 전략이 고도화 되면서 suspense의 역할이 더욱더 중요해졌다고 볼 수 있을 것 같다
기본적으로 선언적으로 대기 상태를 보여준 점에서 개발자에게 아주 친절한 날먹 기술 중 하나지만.. 날먹을 어떻게 했는지 파헤쳐보자!
그리고 우선 이 글을 읽기 전에 SSR과 RSC의 차이점에 대해 반드시 알아보고 오도록하자!
https://duckgugong.tistory.com/399
Suspense?
- Suspense는 React에서 비동기 데이터 로딩 상태를 관리하고, 데이터가 준비되지 않은 동안 로딩 UI(fallback)를 표시하는 기능이다
- React 18부터는 서버 측에서도 Suspense를 사용할 수 있게 되었고, React Server Componet(Streaming Rendering)과 함께 동작해 점진적 렌더링(Progressive Rendering)을 지원한다.
- 주요 역할:
- 비동기 컴포넌트 로딩(React.lazy) 관리.
- 서버 컴포넌트(RSC)와 함께 데이터 로딩 상태를 처리.
- 로딩 중인 상태를 사용자에게 보여주는 로딩 UI 제공.
핵심
- React는 컴포넌트가 렌더링 중에 Promise를 throw하면, 해당 Promise가 해결될 때까지 기다림.
- Suspense는 이 "기다리는 동안" 사용자에게 로딩 UI(fallback)를 표시함.
- 비동기 작업이 완료되면, React는 로딩 UI를 제거하고 최종 콘텐츠를 렌더링!!
- ++ 서버에서의 sups
Suspense의 과거...
- HTML은 서버에서 한 번에 생성되거나, 클라이언트에서만 렌더링(CSR)되었다.
- 동적 로딩은 주로 클라이언트 측에서 Lazy Loading을 통해 구현되었고, 서버는 HTML을 생성한 후 클라이언트에 전달하는 역할만 했음.
- React.lazy or Next Dynamic Import
Suspense의 현재..
- HTML Streaming은 서버에서 HTML을 조각 단위로 스트리밍하여 클라이언트에 전달하는 방식이다.
- 서버는 준비된 컴포넌트부터 HTML을 생성해 클라이언트에 전달하고, 데이터가 준비되지 않은 컴포넌트는 Suspense의 fallback으로 대체.
- 점진적 렌더링(Progressive Rendering)
- 준비된 컴포넌트부터 순차적으로 렌더링하므로, 사용자는 빠르게 콘텐츠를 볼 수 있다!
- 점진적 렌더링(Progressive Rendering)
작동 방식
- 컴포넌트가 Throw한 Promise가 있다면 Fallback UI 표시
Lazy loading ?
- Lazy Loading은 컴포넌트를 애플리케이션의 초기 로딩 시점에 모두 로드하지 않고, 필요한 시점에 동적으로 로드하는 방식이다
- React에서는 React.lazy를 사용해 컴포넌트를 동적으로 로드할 수 있음
- Lazy Loading은 코드 스플리팅(Code Splitting)의 한 형태로, 애플리케이션의 성능 최적화에 중요한 역할을 한다.
- Lazy Loading은 기본적으로 클라이언트 측에서만 동작하는 개념이다
- React.lazy를 사용한 Lazy Loading은 서버 사이드 렌더링(SSR)에서는 작동하지 않음!!
작동 방식
- 초기 로딩 시점
- Lazy Loading된 컴포넌트는 초기 로딩 시점에 로드되지 않음.
- 대신, React는 해당 컴포넌트를 로드하기 위한 동적 import 함수를 준비함.
- 컴포넌트가 필요할 때:
- Lazy Loading된 컴포넌트가 렌더링될 시점이 되면, React는 동적 import를 실행해 해당 컴포넌트를 로드.
- Suspense와 연동:
- Lazy Loading된 컴포넌트가 로드되는 동안 React는 Suspense를 사용해 로딩 상태를 관리하고, fallback으로 지정된 로딩 UI를 표시.
import React, { Suspense, useState } from "react";
const LazyComponent = React.lazy(() =>
import("./LazyComponent") // 동적 import
);
export default function App() {
const [show, setShow] = useState(false);
return (
<div>
<h1>Conditional Lazy Loading</h1>
<button onClick={() => setShow((prev) => !prev)}>
{show ? "Hide" : "Show"} Lazy Component
</button>
{show && (
<Suspense fallback={<p>Loading...</p>}>
<LazyComponent />
</Suspense>
)}
</div>
);
}
- 초기 로딩
- LazyComponent는 초기 로딩 시점에 로드되지 않음.
- 버튼 클릭 시
- 사용자가 버튼을 클릭해 show 상태가 true로 변경되면, LazyComponent가 로드됨.
- 로딩 상태 표시:
- LazyComponent가 로드되는 동안, Suspense의 fallback으로 지정된 <p>Loading...</p>가 렌더링됨.
- 컴포넌트 로드 완료:
- LazyComponent가 로드되면, 최종적으로 해당 컴포넌트가 렌더링됨.
Dynamic Import
- Next의 page router이나 client component에서 사용하는 동적 로딩(중요!)
- React의 React.lazy와 비슷하지만, SSR과 CSR을 모두 지원하며, 더 많은 옵션을 제공한다.
- dynamic 함수는 Lazy Loading을 구현할 수 있을 뿐만 아니라, 서버 렌더링 여부를 제어할 수 있는 ssr 옵션을 제공한다.
Next.js Dynamic Import의 작동 방식
- 초기 로딩 시점
- Dynamic Import된 컴포넌트는 초기 로딩 시점에 로드되지 않는다.
- 대신, Next.js는 해당 컴포넌트를 로드하기 위한 동적 import 함수를 준비한다.
- 컴포넌트가 필요할 때
- Dynamic Import된 컴포넌트가 렌더링될 시점이 되면, Next.js는 동적 import를 실행해 해당 컴포넌트를 로드한다.
- 로딩 상태 관리
- 컴포넌트가 로드되는 동안, loading 옵션으로 지정된 로딩 UI를 표시한다.
- React의 Suspense와 함께 사용할 수도 있다.
Next.js Dynamic Import의 ssr 옵션
- ssr 옵션은 Dynamic Import된 컴포넌트가 서버에서 렌더링될지 여부를 제어하는 옵션이다.
- 기본값은 true로 설정되어 있으며, 서버에서 컴포넌트를 렌더링한다.
- 클라이언트는 서버에서 전달받은 HTML을 Hydration하여 동적인 기능을 활성화한다.
- ssr: false로 설정하면, 해당 컴포넌트는 서버에서 렌더링되지 않고, 클라이언트에서만 렌더링된다(CSR).
import dynamic from "next/dynamic";
// Dynamic Import된 컴포넌트 (SSR 활성화)
const DynamicComponent = dynamic(() => import("./DynamicComponent"), {
loading: () => <p>Loading...</p>, // 로딩 중 표시할 컴포넌트
ssr: true, // 서버 렌더링 활성화 (기본값)
});
export default function Page() {
return (
<div>
<h1>SSR Enabled</h1>
<DynamicComponent />
</div>
);
}
ssr: true
- 서버에서 DynamicComponent를 렌더링하고, 클라이언트로 HTML을 전달.
- 클라이언트는 서버에서 전달받은 HTML을 Hydration하여 동적인 기능을 활성화.
import dynamic from "next/dynamic";
// Dynamic Import된 컴포넌트 (SSR 비활성화)
const DynamicComponent = dynamic(() => import("./DynamicComponent"), {
loading: () => <p>Loading...</p>, // 로딩 중 표시할 컴포넌트
ssr: false, // 서버 렌더링 비활성화
});
export default function Page() {
return (
<div>
<h1>SSR Disabled</h1>
<DynamicComponent />
</div>
);
}
ssr: false
- 서버는 DynamicComponent를 렌더링하지 않고, 클라이언트에서만 렌더링.
- 클라이언트는 JavaScript 번들을 다운로드한 후 DynamicComponent를 렌더링.
결론은 초기 HTML을 미리 만들어 주냐 안주냐의 차이이다..!
HTML Streaming
- HTML Streaming은 서버가 HTML을 한 번에 생성하지 않고, 준비된 부분부터 조각 단위로 클라이언트에 전달하는 방식!
- 클라이언트는 HTML 조각을 받는 즉시 렌더링을 시작할 수 있어, 초기 로딩 속도가 크게 개선된다.
- React의 Suspense와 함께 사용하면, 데이터가 준비되지 않은 동안 로딩 UI를 표시하고, 준비된 데이터가 있으면 해당 HTML을 추가로 스트리밍할 수 있다.
- 이 친구는와 Suspense
HTML Streaming의 동작 방식
- 초기 HTML 스트리밍
- 서버는 준비된 React 컴포넌트의 HTML을 먼저 생성하고, 이를 클라이언트에 전달.
- 클라이언트는 이 HTML을 즉시 렌더링할 수 있다!
- Suspense와 함께 동작
- 데이터가 준비되지 않은 컴포넌트는 Suspense의 fallback으로 대체.
- 데이터가 준비되면 해당 컴포넌트의 HTML을 추가로 스트리밍.
- 점진적 렌더링
- 준비된 컴포넌트부터 순차적으로 렌더링하므로, 사용자는 빠르게 콘텐츠를 볼 수 있음
Suspense + React Server Component(Streaming SSR)
React Server Components와 SSR의 기본 원리
서버에서의 렌더링 흐름
- React는 서버에서 컴포넌트를 실행하여 HTML을 생성하고, 이를 클라이언트에 전달함.
- 서버는 모든 컴포넌트가 렌더링될 때까지 기다린 후 최종 HTML을 생성하는데, 이는 React가 비동기 작업(Promise)을 처리하는 방식 때문이다!
서버에서의 비동기 렌더링의 기본 원리
- React는 컴포넌트가 Promise를 반환하면, 해당 Promise가 해결될 때까지 기다린 후 렌더링을 진행한다.
- 서버는 모든 비동기 작업이 완료된 후 최종 HTML을 생성하여 클라이언트에 전달한다
- 컴포넌트가 Promise를 반환하면, 해당 작업이 완료될 때까지 기다린 후 최종 HTML을 생성한다!
- Suspense를 사용하지 않으면, React는 모든 비동기 작업이 완료될 때까지 기다린 후 한 번에 HTML을 생성하여 클라이언트에 전달한다!
- Suspense가 없으면, React는 중간 상태를 클라이언트에 전달하지 않고, 모든 작업이 완료될 때까지 기다린다! (핵심)
- ++ 클라이언트에서의 비동기 렌더링은 React concurrent rendering과 관련된 내용입니다~~~
React Server Component에 Suspense를 사용하지 않았을 때 문제점..!
- 나는 개인적으로 이 부분이 굉장히 헷갈렸다
- 서버에서의 비동기 렌더링 + supense 의 원리를 잘 모른 상태에서 아래와 같은 코드를 마주했을 때 왜 Server Components without Suspense 가 먼저 보이지 않고 모든 promise가 완료되었을 때, 한번에 페이지가 보이는지 이해가 가지 않았었다
// ThreeSecondComponent.tsx
export default async function ThreeSecondComponent() {
await new Promise((resolve) => setTimeout(resolve, 3000)); // 3초 지연
return <p>This is the 3-second server component!</p>;
}
// FiveSecondComponent.tsx
export default async function FiveSecondComponent() {
await new Promise((resolve) => setTimeout(resolve, 5000)); // 5초 지연
return <p>This is the 5-second server component!</p>;
}
// Page.tsx
import FiveSecondComponent from "@/components/FiveSecondComponent";
import ThreeSecondComponent from "@/components/ThreeSecondComponent";
export default function Page() {
return (
<div>
<h1>Server Components without Suspense</h1>
<ThreeSecondComponent />
<FiveSecondComponent />
</div>
);
}
- 위와 같은 코드를 작성하면 모든 비동기 컴포넌트가 완료되기 이전까지 흰 화면만 보이게 된다 ㅠㅠ
RSC with Suspense
- 서버에서의 Suspense는 병렬로 작동한다!
- React의 서버 렌더링(SSR)에서 Suspense를 사용하면, 서버는 비동기 작업을 병렬로 처리하고, 준비된 HTML 조각부터 클라이언트에 스트리밍으로 전달할 수 있다.
- 이는 React 18에서 도입된 Streaming Rendering 덕분에 가능해진 기능입니다!!!
서버에서의 Suspense와 병렬 처리
- 병렬 처리란, 여러 비동기 작업(예: 데이터 로딩, API 호출)을 동시에 실행하여, 각 작업이 독립적으로 완료될 수 있도록 하는 방식이다.
- 서버에서의 Suspense는 각 컴포넌트의 비동기 작업을 병렬로 실행하고, 준비된 컴포넌트부터 순차적으로 HTML을 스트리밍으로 클라이언트에 전달한다.
비동기 작업 병렬 실행
- 서버는 React 컴포넌트 트리를 탐색하며, 각 컴포넌트의 비동기 작업을 병렬로 실행.
- 예를 들어, Promise를 반환하는 두 개의 컴포넌트가 있다면, 두 작업은 동시에 실행된다.
- 데이터가 준비되지 않은 컴포넌트는 Suspense의 fallback으로 대체.
- 준비된 컴포넌트부터 HTML을 생성하여 클라이언트에 전달.
코드 예제
// ThreeSecondComponent.tsx
export default async function ThreeSecondComponent() {
await new Promise((resolve) => setTimeout(resolve, 3000)); // 3초 지연
return <p>This is the 3-second server component!</p>;
}
// FiveSecondComponent.tsx
export default async function FiveSecondComponent() {
await new Promise((resolve) => setTimeout(resolve, 5000)); // 5초 지연
return <p>This is the 5-second server component!</p>;
}
export default function Page() {
return (
<div>
<h1>Server Components without Suspense</h1>
<Suspense fallback={<p>Loading 3-second component...</p>}>
<ThreeSecondComponent />
</Suspense>
<Suspense fallback={<p>Loading 5-second component...</p>}>
<FiveSecondComponent />
</Suspense>
</div>
);
}
- 초기 HTML 스트리밍
- 서버는 <h1>과 <Suspense fallback>을 포함한 HTML을 먼저 클라이언트에 전달
- ThreeSecondComponent 준비 완료
- 3초 후, ThreeSecondComponent가데이터를 준비하면 해당 HTML이 추가로 클라이언트에 스트리밍
- FiveSecondComponent 준비 완료
- 5초 후, FiveSecondComponent가 데이터를 준비하면 해당 HTML이 추가로 클라이언트에 스트리밍
핵심
병렬 처리의 장점
- 여러 비동기 작업(예: 데이터 로딩)을 동시에 실행하여, 하나의 작업이 지연되더라도 다른 작업에는 영향을 주지 않음.
- 독립적으로 실행된 작업이 준비되면 즉시 렌더링 가능.
점진적 렌더링
- 준비된 HTML 조각부터 클라이언트에 순차적으로 전달(Streaming Rendering).
- 로딩 상태(fallback)를 표시하며, 데이터가 준비되면 최종 콘텐츠를 추가로 렌더링.
- 사용자는 준비된 콘텐츠를 빠르게 볼 수 있으며, 나머지 콘텐츠는 데이터가 준비되는 대로 순차적으로 렌더링됨.
'Frontend > 개발 관련 지식' 카테고리의 다른 글
| React 이벤트 처리 방식 (0) | 2025.10.14 |
|---|---|
| Redux, Zustand 돌아보기 =) + 구조분해할당에 대한 오해.. (1) | 2025.09.28 |
| 서버 컴포넌트(RSC) .. ? SSR .. ? (0) | 2025.09.24 |
| Routing (react / next) (0) | 2025.09.22 |
| CSR에서 SSG, SSR, ISR 그리고 Next ?? (0) | 2025.09.22 |
@덕구공 :: Duck9s'
주니어 개발자에욤
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!