Loading...
본문 바로가기
👥
총 방문자
📖
0개 이상
총 포스팅
🧑
오늘 방문자 수
📅
0일째
블로그 운영

여러분의 방문을 환영해요! 🎉

다양한 개발 지식을 쉽고 재미있게 알려드리는 블로그가 될게요. 함께 성장해요! 😊

코딩 정보/NextJs

[개념부터 nextjs] 페이지 라우터의 한계, 서버 컴포넌트 도입

by 꽁이꽁설꽁돌 2025. 8. 19.
728x90
반응형
     

목차

     

     

     

     

     

     

    내가 생각했을 때 페이지 레이아웃, 폴더 구조를 통한 동적 파람, 서버 컴포넌트 등 여러 차이점들이 있지만

    가장 중요하다고 생각한 것은 서버 컴포넌트를 통한 데이터 스트리밍이다.

     

    서버 컴포넌트

    아래를 보면 ssr의 진행 방식을 알 수 있다.

    https://be-senior-developer.tistory.com/377

     

    [개념부터 nextjs] 서버 컴포넌트

    목차 서버컴포넌트의 이점데이터 페칭: Server Components를 사용하면 데이터 소스에 더 가까운 서버에서 데이터 페칭을 수행할 수 있습니다. 이는 렌더링에 필요한 데이터를 가져오는 시간을 줄이

    be-senior-developer.tistory.com

     

     

    페이지 라우팅 방식

    페이지 라우팅 방식은 서버컴포넌트가 없이 ssr을 진행했다. 과연 어떻게 진행했는지 알아보자

     

    만약 페이지가 서버 사이드 렌더링(SSR) 을 사용한다면, 해당 페이지의 HTML은 매 요청마다 생성된다. 페이지를 SSR로 만들고 싶다면, getServerSideProps 함수를 사용한다. 이는 Next.js 프레임워크의 일종의 문법으로 받아들일 수 있다.

     

     

    getServerSideProps 함수

    getServerSideProps 함수는 컴포넌트보다 먼저 실행되어서, 컴포넌트에 필요한 데이터를 불러오는 함수이다. getServerSideProps 함수를 만들어 주기만 하면, 해당 페이지는 SSR로 사전 렌더링을 진행한다.

    function Page({ data }) {
      <p>{data.title}</p>
      //...
    }
    
    export async function getServerSideProps() {
      const res = await fetch(`url`)
      const data = await res.json()
      return { props: { data } }
    }
    
    export default Page

     

    Page 컴포넌트를 서버사이드 렌더링하기 위해서는 getServerSideProps함수를 같은 모듈안에 함께 작성해주어야 한다. 반드시 getServerSideProps라는 이름이어야 하고, 이것은 컴포넌트에서 렌더링할 때 필요한 데이터를 리턴해주는 형태여야 한다. 더군다나 우리는 이 함수를 컴포넌트 내부에서 사용하지도 않는데 export까지 해주어야 한다.

     

    왜 그런지 이해하기 위해 내부로직을 알아보자

     

    Next.js에서 getServerSideProps를 사용해 pre rendering을 하는 로직은 다음과 같다.

    // ...
    if (isSSR && !cachedHTML) {
      const result = ReactDOM.renderToString(<Page />);
      const initialData = { name: "ssr" };
      const preRenderedProps = await Page.getServerSideProps(initialData);
    
      indexHTML = indexHTML
        .replace(
          '<div id="root"></div>', 
          `<div id="root">${result}</div>` 
        )
        .replace("__DATA_FROM_SERVER__", JSON.stringify(preRenderedProps));

     

    1. 컴포넌트 직렬화

    SSR을 적용하고 캐싱된 내용이 없을 경우, 해당 컴포넌트를 renderToString()함수로 직렬화한다.

     

    2. getServerSideProps()에 정의된 데이터를 서버에서 fetch

    그리고 prerendering할 때 이 컴포넌트 파일에 export된 getServerSideProps()함수를 실행해 서버 사이드 렌더링에 필요한 데이터를 서버에서 불러온다.

     

    3. 서버에서 불러온 데이터로 교체

    html 문자열에서 DATA_FROM_SERVER 문자열을 이 pre rendering된 데이터로 교체해 새로 업데이트할 html 문서를 만든다.

    쉽게 말하면, getServerSideProps()라는 메서드 안에 서버에서 실행이 필요한 로직을 넣어놓으면, Next.js에서 이 함수로 접근해 대신 서버에서 실행 후 렌더링할 수 있도록 하는 겁니다.

    SSR이 어떤식으로 동작하는지 간단하게 정리해보았다.
    이제 여기에서 어떤 부분이 동기적으로 실행되어 메인 스레드를 블로킹하는지 알아보자

     

     

    기존 ssr의 문제점

    ReactDomServer.renderToString()

    ssr을 위해 가장 먼저 호출하는 renderToString()은 동기함수이므로 처리 속도가 오래걸리게 되면 

    그 다음 작업 역시 지연된다.

     

    그래서 react 서버 렌더링이 동기적으로 작동하기 때문에 다음과 같은 단점이 존재한다.

     

    1. 사용자에게 페이지를 표시하기 위해서는 모든 데이터를 전부 가져와야 한다.

    즉, 화면을 그리는데 아래 3가지 데이터를 필요로 하고 각각 데이터를 받아오는데 딜레이가 다르다고 가정하자.

    Article API (100ms ~ 300ms)

    Comment API (200ms ~ 1000ms)

    User API (300ms ~ 500ms)

    이러한 경우 3종류의 API를 동시에 호출한다고 해도 가장 늦게 끝나는 Comment API를 기다려야하고, 최장 1초의 딜레이를 감수해야한다.

     

    2.hydration을 시작하기 전 모든 html과 js를 로드해야 한다.

    JS 로드시에도 마찬가지로 모두 한번에 이루어져야한다. 화면의 특정 영역이 많은 JS 로직을 가지고 있다면, 해당 영역 JS를 로드한다고 다른 영역 모두 hydration을 진행하지 못한다.

     

    기사 본문 (100KB)

    댓글 (200KB)

    사용자 정보 (50KB)

     

    3. hydration은 한번에 이루어지므로 react는 해당 작업이 완료될 때까지 멈추지 않는다.

    JS 로드와 비슷하게, hydration 이 한번에 이루어지므로 JS 로드 뿐 아니라 hydration도 한번에 이루어져야한다. 각 영역의 hydration이 이루어지는 시간이 아래와 같다고 가정하자. (hydration 시간은 추상적이다.)

     

    기사 본문 (10ms)

    댓글 (50ms)

    사용자 정보 (20ms)

     

    따라서 모든 컴포넌트가 상호작용하기 전에 모든 컴포넌트가 수화될 때까지 기다려야 한다.

     

    따라서 이를 해결하기 위해서 react 18에서 concurrency를 도입하게 된다.

    1. streaming HTML - renderTosTring() 메서드 대신 renderToPipableStream()메서드로 전환했다.
    2. selective hydration로 컴포넌트를 랩핑해 적용한다.

    Streaming HTML

    그렇다면 streaming SSR은 어떻게 가능한 걸까?
    html을 streaming할 수 있어야 한다. streaming HTML은 이렇게 작동한다.

    streaming에 사용되는 HTTP를 살펴보면,

    HTTP/1.1 스펙의 header 값 중 Transfer-Encoding: chunked
    (HTTP/2에서는 기본 지원)



    브라우저/Node.js의 표준 Streams API (특히 WHATWG Streams) 를 nextjs의 렌더링 파이프라인에 활용하였다.

    https://developer.mozilla.org/ko/docs/Web/API/Streams_API

     

    Streams API - Web API | MDN

    Streaming은 네트워크를 통해 받은 리소스를 작은 조각으로 나누어, Bit 단위로 처리합니다. 이는 브라우저가 수신한 자원을 웹페이지에 표현할 때 주로 사용하는 방법입니다. — Video buffer는 재생

    developer.mozilla.org

     

     

    Transfer-Encoding

    MDN - Transfer-Encoding

    • Transfer-Encoding 헤더값은 페이로드 바디를 안전하게 전송하기 위한 인코딩 형태를 정의합니다.
    • chunk directive는 데이터가 일련의 chunk로 전송된다는 것을 의미합니다. Content-Length헤더값은 이 경우 생략됩니다.
    • Content-Length는 일반적으로 응답에 함께 보내어 전송되는 콘텐츠의 길이를 알려주는데, 스트리밍 요청은 그 길이를 처음부터 알려줄 수 없습니다.
      브라우저는 남은 chunk의 길이가 0이 될 때까지 커넥션을 닫지 않고 기다리면서 TCP/IP 핸드쉐이크 비용을 절약합니다.
    <Layout>
      <NavBar />
      <Sidebar />
      <RightPane>
        <Post />
        {/* Comments is expensive cost component */}
        <Suspense fallback={<Spinner />}>
          <Comments />
        </Suspense>
      </RightPane>
    </Layout>

     

     

    Selective Hydration

    SSR에서 페이지를 완성하는 순서는 다음과 같다.

     

    Server에서 HTML을 생성한다.

    Client에서 JS를 로드한다.

    Client에서 hydration을 진행한다.

    여기서 Suspense와 Lazy를 사용해서 2번과 3번 과정을 부분적으로 진행 할 수 있게 된다.

    import { lazy } from 'react';
    
    const Comments = lazy(() => import('./Comments.js'));
    
    // ...
    
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>

     

    selective hydration은 리액트 18의 도입으로 페이지 라우터에서도 가능하지만

    서버 컴포넌트와 selective hydration은 묶어서 쓰이기 때문에 둘 다 알아야 한다.

     

     

    출처

    https://hmos.dev/recent-server-side-rendering

     

    왜 React는 Server Component를 만들게 되었을까? | hmos.dev

    React 18에서 Server Side Rendering은 왜 나오게 되었을까?

    hmos.dev

    반응형