[TIL] CSR, SSR, RSC

vanLan·2026년 7월 30일

프론트엔드

목록 보기
1/4
post-thumbnail

☂️ CSR: Client-Side Rendering

  • 조립되지 않은 부품들만 들어있는 형태. (서버는 빈 HTML + JS Bundle 전송)
  • 동작 순서:
    1. 사용자 접속 -> 서버는 빈 HTML과 거대한 JS Bundle을 전송.
    2. JS Bundle 다운로드 후 해석 및 실행(Parsing).
    3. JS 로드 후 데이터 패칭.
    4. 렌더링.
  • 단점:
    1. JS 로드 후 렌더링 되기 전까지 동작 X. 로딩시간 🔼. 사용자경험(UX) 🔽.
    2. 검색 봇은 빈페이지를 보고 내용이 없다고 판단. (SEO 최적화 실패)

☂️ SSR: Server-Side Rendering

  • 서버에서 미리 완성된 HTML 조립하여 브라우저로 전송하는 방식.
  • 눈으로 볼 수 있지만 상호작용은 불가. (정적 페이지)
  • 기능을 활성(상호작용)하기 위해 마찬가지로 무거운 JS Bundle을 전송해야 함.

☂️ RSC: React Server Components

  • 서버에서 실행될 코드를 아예 브라우저로 전송하지 않음. (Do not send!!)
  • RSC는 Node.js 서버 환경에서 실행되므로 DB에 직접 접근, 무거운 라이브러리를 사용해도 브라우저로 전송되는 번들 사이즈는 0이 됨.
  • async/await을 컴포넌트 레벨에서 직접 사용이 가능하므로 useEffect, useState 같은 복잡한 훅이 필요 없음.
  • API 없이 컴포넌트 내부에서 데이터베이스에 직접 연결되므로 네트워크 지연이 없음.
  • 내부 로직과 DB 라이브러리 코드는 브라우저로 전송되지 않으며 오직 HTML 구조와 데이터만 전송하게 됨.
  • 동작 순서:
    1. 요청(Request): 사용자가 페이지에 접속.
    2. 서버 실행(Server Execution): Next.js 서버가 서버 컴포넌트를 실행.
      => "db.product.findMany()"같은 코드가 서버 내부에서 즉시 실행되어 데이터를 가죠옴.
    3. 직렬화(Serialization): 서버는 컴포넌트의 결과물을 'RSC Payload'라는 특수한 JSON 문자열로 변환.
    4. 전송 및 반영(Streaming & Reconciliation): 변환된 Payload만 브라우저로 전송.
      => 브라우저는 지시서를 해석해 즉시 화면을 렌더링.
  • 장점:
    1. 컴포넌트 안에서 데이터를 직접 조회하므로 엄청난 생산성 🔼.
    2. 민감 정보가 서버 밖으로 전송되지 않아 보안 강화. (브라우저 노출 X)
  • 결론:
    • Next.js는 RSC + Client Components (하이브리드 모델)
      => 무거운 로직은 서버에서, 브라우저는 가볍게 유지(상호작용이 꼭 필요한 부분만 Client Components로).

🌂 Hydration (수분 공급)

  • 서버에서 온 HTML(DRY STATE) + JS Bundle(Water) => Interactive App
  • 동작 순서:
    1. Visual Ready: HTML 뼈대 도착. (사용자는 즉시 볼 수 있음)
    2. Background Fetch: JS Bundle을 백그라운드 다운로드.
    3. Matching: 트리 순회 및 결합.
    4. Alive: 앱의 활성화.
  • 결론:
    • 정적 뷰에서 동적 Interaction으로 변환!
      => Server -> HTML (SSR, 빠른 뷰) -> JS Load -> Hydrate (연결, Bridge) -> Interactive (완성, Done).
  • 선택적 hydration:
    • 오직 'use client'가 붙은 Client Component만 hydration 진행.
      => Fater & Lighter

☔ Hydration Mismatch (홀리 쉿)

  • 서버쪽 HTML 렌더링과 브라우저 쪽 렌더링이 1픽셀이라도 다르면 Hydration Mismatch가 발생 함.

  • 흔히 하는 실수 예시 코드:

    /* ❌ [안티 패턴] Mismatch를 유발하는 전형적인 초보자의 실수 */
    export default function TimeDisplay() {
    
    // 🚨 실수 1: 시간 (Time)
    // 서버가 HTML을 만든 시간(10:00:00)과,
    // 인터넷 선을 타고 브라우저에 도착해 JS가 실행된 시간(10:00:01)은 영원히 다릅니다!
    const time = new Date().toLocaleTimeString();
    
    // 🚨 실수 2: 랜덤 값 (Random)
    // 서버가 굴린 주사위 값(0.12)과 브라우저가 굴린 주사위 값(0.99)은 일치할 수 없습니다!
    const randomId = Math.random();
    
    return (
      <div id={randomId.toString()}>
        <h1>현재 시각</h1>
        <p>{time}</p>
    
        {/* 🚨 실수 3: 잘못된 HTML 구조 (Invalid Nesting) */}
        <p>
          {/* p태그(문단) 안에는 div태그(구역)가 들어갈 수 없습니다. (HTML 문법 위반) */}
          {/* 브라우저가 이걸 몰래 고치려고 시도하다가 리액트에게 딱 걸려서 에러가 납니다. */}
          <div>Description</div>
        </p>
      </div>
    );
    }
  • 해결방안:

    1. 패턴1 (Two-Pass Rendering): 서버와 브라우저의 초기값을 강제로 일치 시킨 후 브라우저가 준비된 이후(useEffect)로 데이터를 넣는 가장 안전한 방식.
    2. 패턴2 (suppressHydrationWaring): 어쩔 수 없이 텍스트 차이가 날 때 사용하는 리액트 전용 'escape' 명령어.
profile
프론트엔드 개발자를 꿈꾸는 이

0개의 댓글