From ripples to waves

SSR 에서 브라우저 <-> 클라이언트 서버 <-> 백엔드 동작 구조 본문

Web

SSR 에서 브라우저 <-> 클라이언트 서버 <-> 백엔드 동작 구조

juyeong_ 2025. 9. 7. 15:45

1. [Frontend Side][Browser]

  • 사용자가 https://application.com/products 같은 URL에 접근합니다.
  • 브라우저는 HTML 문서(index.html) 를 받기 위해 요청을 보냅니다.

 

2. [Frontend Side][Node Server: Next.js]

  • Next.js 서버(Node.js + V8)가 요청을 받습니다.
  • Next.js는 해당 경로(/products)에 대응하는 페이지 컴포넌트를 확인합니다.
    • Pages Router라면 getServerSideProps / getStaticProps 같은 특수 함수 export 여부를 확인합니다.
    • App Router라면 page.tsx 컴포넌트가 Server Component인지, async 함수인지를 확인합니다.
  • 만약 데이터 패칭(fetch/axios 등)이 필요하다고 감지하면 → Node 서버에서 JS(V8)로 그 함수를 실행합니다.

 

3. [Frontend Side][Node Server: Next.js] → [Backend Side][Web Server]

  • 페이지를 렌더링하는 과정에서 fetch("/api/…") 또는 axios.get("https://api…") 호출 발생.
  • 이 요청은 Next.js 서버에서 실행되므로, 브라우저가 아닌 서버 → Web Server(Nginx, Caddy 등) 로 전송됩니다.

 

4. [Backend Side][Web Server] → [Backend Side][WAS]

  • Web Server는 리버스 프록시 역할을 하며 요청을 내부 WAS(Spring Boot + Tomcat) 으로 전달합니다.
  • WAS는 비즈니스 로직을 처리하고, DB와 통신하여 데이터를 가져옵니다.

 

5. [Backend Side][WAS] → [Frontend Side][Node Server: Next.js]

  • WAS가 JSON 응답을 Web Server를 거쳐 Next.js 서버로 반환합니다.
  • Next.js 서버는 응답 데이터를 받아서, React 컴포넌트(JS로 트랜스파일된 TSX 코드)를 실행하며 서버에서 HTML을 생성합니다.

 

6. [Frontend Side][Node Server: Next.js] → [Browser]

  • Next.js 서버는 데이터가 채워진 완성된 HTML + 초기 상태(dehydrated state) 를 브라우저에 전송합니다.
  • 브라우저는 HTML을 바로 렌더링해서 사용자에게 첫 화면(FCP, LCP) 을 보여줍니다.

 

7. [Frontend Side][Browser]

  • 브라우저가 JS 번들(트랜스파일된 React 코드) 을 로드합니다.
  • React가 실행되면서 서버에서 내려온 HTML과 클라이언트 JS를 연결 → Hydration 발생.
  • 이후에는 CSR처럼 작동:
    • 사용자가 버튼 클릭 → 클라이언트에서 직접 fetch/axios 호출
    • Tanstack Query 같은 상태 관리 라이브러리가 캐시/리패치 관리

 

 

 

RSC 를 사용할 경우

 

1) [Frontend Side][Browser]

  • 사용자가 https://application.com/products 접근 → HTML을 요청.
  • 브라우저는 아직 JS 실행 전이며, 서버가 스트리밍하는 HTML shell + RSC(Flight) 스트림을 받을 준비만 함.

 

2) [Frontend Side][Node Server: Next.js (RSC)]

  • 라우트(/products)에 해당하는 Server Component 트리를 로드.
    • use client가 없는 컴포넌트는 Server Component로 분류되어 브라우저에 JS 번들로 보내지지 않음(실행은 서버 전용).
    • use client가 선언된 컴포넌트는 Client Component로 분류(브라우저에서 하이드레이트됨).
  • RSC 렌더링 시작: 서버(V8/Edge Runtime) 가 Server Component 함수를 실행하며 필요한 데이터를 그 자리에서 요구.

 

3) [Frontend Side][Node Server: Next.js (RSC)] → (데이터 패칭 트리거)

  • Server Component 내부 fetch(...) 호출 발생 시:
    • Next.js Data Cache 및 **요청 중복 제거(memoization/dedup)**가 자동 적용.
    • fetch 옵션(cache, next: { revalidate })에 따라 정적/동적/ISR 전략이 결정.
  • 중요: RSC에서는 네이티브 fetch 사용이 권장(Next의 캐시·리밸리데이션과 1급 통합). axios도 가능하지만 캐시/ISR 통합 이점이 사라짐.

 

4) [Frontend Side][Node Server: Next.js (RSC)] → [Backend Side][Web Server]

  • Server Component의 fetch 타깃이 백엔드 API라면, Next.js 서버가 리버스 프록시(예: Nginx/Caddy) 앞으로 요청을 발신.
  • (아키텍처에 따라) RSC에서 직접 DB 쿼리도 가능하지만, 보통은 사내/외부 API 게이트웨이로 호출.

 

5) [Backend Side][Web Server] → [Backend Side][WAS]

  • Web Server는 요청을 WAS(Spring Boot + Tomcat) 로 포워딩.
  • WAS는 비즈니스 로직/인증/DB 처리를 수행하고 JSON으로 응답.

 

6) [Backend Side][WAS] → [Frontend Side][Node Server: Next.js (RSC)]

  • Next.js(RSC)가 JSON 응답을 수신 → Server Component 렌더링을 계속 진행.
  • Suspense 경계를 활용해 부분 스트리밍(Streaming): 준비된 컴포넌트부터 순차 전송할 수 있음.
  • PPR(Partial Pre-Rendering) 사용 시, 정적 가능 영역은 미리 렌더링하고, 동적 경계는 준비되는 대로 스트림.

 

7) [Frontend Side][Node Server: Next.js (RSC)] → [Frontend Side][Browser]

  • 서버는 HTML shell + RSC Flight 페이로드스트리밍으로 전송.
    • Flight: 서버가 계산한 컴포넌트 트리의 설명(serialize)이며, 서버 컴포넌트 코드는 브라우저로 보내지지 않음.
  • 브라우저는 도착 즉시 HTML을 렌더(FCP/LCP 유리).
  • 동시에 React 런타임이 Flight 스트림을 클라이언트 컴포넌트와 결합하여 필요한 부분만 하이드레이션.
    • 서버 컴포넌트는 하이드레이션 대상이 아님(코드 미전송 - 이미 HTML 에 포함되어 있으므로).
    • 클라이언트 컴포넌트만 JS 번들을 받아 하이드레이트되어 상호작용을 담당.

 

8) [Frontend Side][Browser] (상호작용 이후)

  • 사용자가 버튼/폼/탭 등을 조작 → Client Component가 이벤트 처리.
  • 서버 액션(Server Actions) 사용 시:
    • Client에서 use server 함수 호출 → [Browser] ⟷ [Next.js (RSC)] RPC처럼 통신 → 서버에서 변이 수행(예: DB write) → revalidatePath/Tag로 관련 경로/태그 캐시 무효화 → 해당 RSC 경계만 재계산/스트림 업데이트.
  • 라우팅(예: next/link) 시에도 RSC 경계를 다시 계산해서 필요한 부분만 새로 스트리밍 → 빠른 전환 + 최소 JS.

 


+ 보충 메모 (RSC 관련)

  • 데이터 패칭 위치 철학:
    • RSC = “데이터는 서버에서”. Server Component 안에서 fetch로 받아오며, 브라우저로 데이터 패칭 코드를 보내지 않음.
    • 보안/성능 이점: 토큰/시크릿/DB 접근을 서버에 가둠, 번들 크기↓, 네트워크 왕복↓.
  • 캐싱/ISR 통합:
    • fetch 기본 통합: cache: 'force-cache', no-store, next: { revalidate: n } 등으로 세밀 제어.
    • 태그 기반 무효화(revalidateTag)와 경로 무효화(revalidatePath)로 선택적 갱신.
  • 스트리밍 & Suspense:
    • 준비된 UI부터 부분 렌더 전송 → TTFB/FCP 개선.
    • 느린 경계는 스켈레톤/플레이스홀더로 감싸고, 준비되면 교체.
  • Client JS 최소화:
    • Server Component는 번들에 포함되지 않음.
    • 상호작용 필요한 부분만 use client로 분리 → 하이드레이션 최소화.
  • TanStack Query와의 역할 분담:
    • 초기 로딩/SEO/캐시 일관성은 RSC+fetch가 우선.
    • 클라이언트 전용 상호작용 데이터(사용자 입력 기반, 폴링/웹소켓, 즉각 반응 UI) 에 한해 Client Component에서 Tanstack Query 사용(Optional).
    • 초기 데이터는 RSC로 내려주고, 클라에서는 React Query로 추가/실시간 갱신 패턴이 깔끔.
  • Axios 사용 여부:
    • RSC에서도 가능하지만, Next.js의 캐시/ISR/재검증 이점을 최대화하려면 fetch 권장.
    • 사내 공통 인터셉터/로깅 때문에 axios를 써야 한다면, 서버 액션 또는 Route Handler(API Route) 에서 axios를 감싸고 RSC에선 해당 핸들러를 fetch로 호출하는 식으로 가능.

 


+ 서버 사이드 flow 도식화

이미지 출처:  https://www.youtube.com/watch?v=M8E6vYAIuzQ