스트리밍 (Streaming)

Dam·2026년 2월 25일

[Next.js]

목록 보기
15/28
post-thumbnail

Streaming

Next.js를 공부하면서 인상 깊었던 부분은 서버 컴포넌트 기반의 데이터 페칭과 Streaming 구조였다.
기존 SSR의 한계를 어떻게 보완하는지, 그리고 React 18의 Suspense와 어떻게 연결되는지 정리해본다.


1. SSR의 한계

SSR(Server Side Rendering)은 서버에서 완성된 HTML을 생성한 뒤 클라이언트에 전달하는 방식이다.

SSR의 동작 과정은 다음과 같다.

  1. 페이지에 필요한 모든 데이터를 서버에서 가져온다.
  2. 서버에서 해당 페이지의 HTML을 렌더링한다.
  3. 완성된 HTML, CSS, JavaScript를 클라이언트로 전송한다.
  4. 브라우저가 HTML과 CSS를 먼저 화면에 그린다.
  5. React가 Hydration을 수행하여 상호작용이 가능해진다.

문제는 모든 데이터 fetching이 끝나야 HTML을 전송할 수 있다는 점이다.
API 응답이 느리면 사용자는 아무 화면도 보지 못한 채 기다려야 한다.

즉, SSR은 SEO와 초기 렌더링에는 유리하지만, 데이터 의존성이 큰 페이지에서는 체감 속도가 느려질 수 있다.

이 문제를 보완하는 방식이 바로 Streaming이다.


2. Streaming이란?

Streaming은 서버가 HTML을 한 번에 완성해서 보내는 것이 아니라,
준비된 부분부터 작은 청크(chunk) 단위로 나누어 점진적으로 전송하는 방식이다.

기존 SSR과 비교하면 다음과 같다.

  • 기존 SSR: 모든 작업 완료 → HTML 한 번에 전송
  • Streaming SSR: 준비된 부분부터 순차적으로 전송

HTML은 원래 네트워크를 통해 청크 단위로 전송될 수 있다.
React 18은 이 특성을 활용해 부분적으로 HTML을 생성하고, 준비된 영역부터 먼저 전송할 수 있도록 개선되었다.

이로 인해 다음과 같은 지표 개선을 기대할 수 있다.

  • TTFB (Time To First Byte)
  • FCP (First Contentful Paint)
  • TTI (Time To Interactive)

특히 여러 API 호출이 동시에 일어나는 Dynamic Page에서 효과적이다.


3. CSR에서는 왜 Streaming을 사용할 수 없을까?

기본 React(CSR)는 서버에서 거의 비어있는 HTML 파일 하나만 전달한다.
실제 렌더링은 브라우저에서 JavaScript를 실행한 뒤 시작된다.

즉,

  • 서버가 HTML을 부분적으로 생성하지 않는다.
  • HTML을 나누어 전송하는 구조가 아니다.
  • JS 번들이 로드되기 전까지 의미 있는 UI가 없다.

Streaming은 서버에서 HTML을 생성하는 SSR 환경이 전제 조건이다.
따라서 CSR 기반 React에서는 기본적으로 Streaming을 사용할 수 없다.


4. React(CSR)에서의 로딩 처리 방식

일반적인 React에서는 클라이언트에서 API를 호출한다.
따라서 로딩 상태를 직접 관리해야 한다.

export default function HomePage() {
  const [movies, setMovies] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  async function getMovies() {
    const response = await fetch(API_URL);
    const json = await response.json();
    setMovies(json);
    setIsLoading(false);
  }

  useEffect(() => {
    getMovies();
  }, []);

  return (
    <div>
      {isLoading ? (
        <h1>Loading...</h1>
      ) : (
        movies.map((movie) => (
          <Movie key={movie.id} {...movie} />
        ))
      )}
    </div>
  );
}

이 방식의 특징은 다음과 같다.

  • useState로 로딩 상태 관리
  • useEffect로 데이터 요청
  • 조건부 렌더링 처리

API 호출이 많아질수록 상태 관리 코드가 늘어나고 복잡도가 증가한다.


5. Next.js(App Router)에서의 Streaming

Next.js 14(App Router)는 React 18의 Streaming + Suspense를 적극적으로 활용한다.

핵심 특징은 다음과 같다.

  • 서버 컴포넌트가 기본
  • 컴포넌트에 async를 직접 사용할 수 있음
  • 서버에서 데이터 fetching 수행
  • 클라이언트에 API가 노출되지 않음
  • DB와 직접 통신 가능
  • loading.tsx를 통한 자동 fallback 처리

즉, 로딩 상태를 직접 관리하지 않아도 되는 구조다.


6. 페이지 단위 Streaming (loading.tsx)

page.tsx와 동일한 폴더에 loading.tsx를 생성하면,
해당 페이지가 로딩 중일 때 자동으로 fallback UI가 표시된다.

page.tsx

async function getMovies() {
  const response = await fetch(API_URL);
  return response.json();
}

export default async function HomePage() {
  const movies = await getMovies();

  return (
    <div>
      {movies.map((movie) => (
        <Movie key={movie.id} {...movie} />
      ))}
    </div>
  );
}

loading.tsx

export default function Loading() {
  return <h2>Loading...</h2>;
}

동작 흐름

  1. 사용자가 페이지에 진입한다.
  2. 서버에서 데이터 요청이 시작된다.
  3. 완료 전까지 loading.tsx가 먼저 렌더링되어 전송된다.
  4. 데이터 준비가 완료되면 실제 콘텐츠로 교체된다.

직접 로딩 상태를 관리할 필요가 없다.

  • useState ❌
  • useEffect ❌
  • 조건부 렌더링 ❌

Streaming 기반 구조 덕분에 코드가 훨씬 단순해진다.


7. 컴포넌트 단위 Streaming (Suspense)

특정 컴포넌트만 지연 렌더링하고 싶다면 Suspense를 활용할 수 있다.

<Suspense fallback={<Loading />}>
  <MovieList />
</Suspense>

여러 API 호출이 있을 경우,
먼저 완료된 컴포넌트부터 렌더링할 수 있다.

이 구조는 “페이지 전체 대기”가 아니라
“준비된 영역부터 점진적 렌더링”이 가능하게 만든다.


8. 정리

1️⃣ 기존 SSR(Server-Side Rendering)의 구조와 한계

  • 모든 데이터 준비 후 HTML 전송
  • 초기 로딩 지연 가능성

2️⃣ Streaming의 해결 방식

  • HTML을 청크 단위로 전송
  • 준비된 영역부터 먼저 렌더링
  • 사용자에게 빠른 시각적 피드백 제공

3️⃣ CSR(Client-Side Rendering)과의 차이

  • CSR은 HTML을 한 번에 전달
  • Streaming 불가능
  • 로딩 상태를 직접 관리해야 함

예를 들면:

  • useState로 로딩 상태 관리
  • useEffect로 API 요청
  • 조건부 렌더링 처리

4️⃣ 렌더링 전략 비교 관점에서의 핵심

  • 기존 SSR은 모든 준비가 끝난 뒤 한 번에 전송하는 구조다.
  • CSR은 클라이언트에서 데이터를 받아 직접 렌더링하는 구조다.
  • Streaming은 SSR의 전송 방식을 개선해, 준비된 영역부터 점진적으로 렌더링하는 구조다.
profile
⏰ Good things take time

0개의 댓글