애드블럭 종료 후 사이트를 이용해 주세요.

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Next.js App Router Server Components와 Server Actions 알아보기 Part.1
    Next.js 2025. 4. 7. 00:27
    728x90
    반응형

    Next.js 15의 App Router는 기존 Pages Router 기반의 클라이언트 중심 렌더링(CSR)에서 서버 중심 모델로의 큰 전환을 가져왔습니다.

    특히 React Server Components(서버 컴포넌트)Server Actions(서버 액션) 기능 도입을 통해 아키텍처 구조와 성능 측면에서 중요한 변화가 있었습니다.

     

    이번 포스트에서는 Next.js 15 App Router의 서버 컴포넌트의 소개와 이전 Page Router와의 차이점, SSR/ISR 등 기존 렌더링 기법과의 비교를 다루겠습니다.

    App Router vs Page Router: 서버 중심 모델의 등장

    기존 Next.js Pages Router (pages 디렉터리 기반 라우팅)는 주로 CSR을 보완하기 위해 SSR(서버 사이드 렌더링)이나 SSG(정적 사이트 생성)를 도입한 구조였습니다.

    즉, 초기에는 서버에서 HTML을 생성해 보내주지만, 이후에는 모든 것이 클라이언트 측 React로 동작하는 전형적인 SPA 패턴이었습니다.

    반면 Next.js 15의 App Router(app 디렉터리 기반 라우팅)는 React 18의 최신 기능을 활용하여 초기 렌더링부터 서버 중심으로 동작하도록 설계되었습니다​.

     

    React의 서버 컴포넌트(RSC) 개념이 등장하면서 Vercel은 라우팅 방식을 근본적으로 재고하게 되었고, 이에 따라 App Router가 도입되었습니다.

    Page Router vs App Router의 주요 차이점

    1. 렌더링 방식

    Page Router에서는 각 페이지를 요청할 때마다 getServerSideProps 등을 통해 데이터를 가져와 HTML을 만들고, 클라이언트로 전송 후 전체 페이지를 hydrate(클라이언트 React로 변환)합니다.

    App Router에서는 기본적으로 대부분의 컴포넌트가 서버에서 렌더링 되며, 클라이언트에는 필요한 인터랙티브 부분만 전송됩니다.

    즉 서버 컴포넌트와 클라이언트 컴포넌트를 조합해 화면을 구성합니다.

     

    2. 데이터 패칭 구조

    Pages Router에서는 데이터 패칭을 위한 별도 함수 (getServerSideProps, getStaticProps 등)나 API Route가 필요했습니다.

    App Router에서는 서버 컴포넌트 내에서 fetch() 등을 직접 호출하여 데이터를 가져오고, React가 이를 활용해 렌더링합니다​.

    추가로 Server Actions를 통해 데이터 변형/업데이트 로직을 직접 서버에서 수행할 수도 있습니다​.

     

    3. 라우팅 및 레이아웃

    App Router는 폴더 구조로 중첩 라우팅과 레이아웃을 지원합니다.

    layout.tsx, loading.tsx, error.tsx 등의 파일을 통해 각 경로별 공통 레이아웃, 로딩 상태 UI, 에러 UI를 손쉽게 구성할 수 있습니다.

    Page Router에서도 _app.js나 _document.js로 공통 레이아웃을 처리했지만, App Router는 중첩된 레이아웃을 페이지별로 지정할 수 있어 유연성이 높습니다.

     

    4. 스트리밍(Streaming) 지원

    App Router의 큰 혁신 중 하나는 HTML 스트리밍 렌더링입니다.

    서버에서 페이지 일부가 준비되면 즉시 클라이언트로 전송하여 브라우저가 먼저 화면 일부를 그릴 수 있고, 나머지 부분은 준비되는 대로 이어받는 방식입니다​.

    반면 Page Router의 SSR은 모든 데이터가 준비되고 전체 HTML이 완성될 때까지 클라이언트가 빈 화면으로 대기해야 했습니다.

     

    이러한 차이로 인해 App Router는 "서버 우선(server-first)" 패러다임을 지향합니다​.

     

    예를 들어, 이전 Page Router 방식으로 e커머스 제품 상세 페이지를 만든다면, 제품 정보뿐만 아니라 리뷰, 관련 상품, 추천 목록 등의 데이터를 한꺼번에 SSR로 가져와야 했습니다.

    그 동안 사용자는 화면에 아무것도 보지 못한 채 기다려야 했지요.

    Page Router에서는 모든 데이터가 준비될 때까지 사용자에게 빈 화면만 보여주기 때문에 초기 로드 지연이 발생합니다​.

     

    반면 App Router에서는 이러한 보조 데이터들을 개별 Suspense 경계로 감싸서 병렬로 불러올 수 있습니다.

    즉, 주요 콘텐츠(예: 제품 상세 정보)는 즉시 표시하고, 부가 콘텐츠(예: 리뷰 목록 등)는 로딩 중임을 skeleton UI 등으로 보여주다가 준비되면 순차적으로 화면에 그려줍니다​.

     

    React의 Suspense를 통한 스트리밍 덕분에 사용자는 페이지 일부라도 더 빨리 볼 수 있게 되고, 나머지는 자연스럽게 로드되어 UX를 개선합니다.

    이처럼 App Router는 레이아웃과 로딩 상태가 기본 제공되며, 개발자가 특별히 복잡한 코드를 작성하지 않아도 점진적 렌더링이 가능해졌습니다.

     

    정리하면, App Router는 Next.js 애플리케이션을 구성하는 철학 자체를 "클라이언트에서 모두 처리하던 것을 이제는 최대한 서버에서 처리하고 필요한 것만 클라이언트에 맡기자"라는 방향으로 전환합니다.

    이는 성능과 개발 생산성 모두에 큰 영향을 줍니다.

    CSR, SSR, SSG, ISR 그리고 App Router의 차이

    Next.js 15 App Router를 이해하려면, 먼저 전통적인 렌더링 전략들(CSR, SSR, SSG, ISR)과 어떻게 다른지 비교할 필요가 있습니다.

    1. CSR (Client-Side Rendering)

    초기에는 서버에서 최소한의 HTML(대체로 비어 있는 <div id="root"></div>만 있는 HTML)만 보내주고, 브라우저가 모든 JavaScript를 받아 실행하여 화면을 그리는 방식입니다.

    React SPA가 전형적인 CSR입니다.

    장점은 초기 서버 부하가 적고, 인터랙티브한 페이지 전환이 빠르다는 것이지만, 단점은 첫 페이지 로드 시 사용자가 바로 볼 수 있는 콘텐츠가 없어 로딩 인디케이터만 보게 되거나, 검색 엔진 크롤러가 빈 페이지를 보게 되는 문제가 있었습니다​.

    (물론 현재는 hydration 이후 콘텐츠가 채워지므로 SEO 이슈는 예전만큼 크진 않습니다.)

    또한 각 컴포넌트가 개별적으로 데이터를 가져오면 워터폴 요청으로 인해 성능 저하를 불러올 수 있다는 문제가 제기되었습니다​.

    2. SSR (Server-Side Rendering)

    서버에서 요청이 올 때마다 해당 페이지의 완전한 HTML을 생성해 응답하는 방식입니다.

    React도 Next.js를 통해 SSR을 도입하면서 초기 로드 성능과 SEO 문제가 개선되었습니다.

    첫 콘텐츠 표시(FCP)가 CSR보다 빨라지고, 크롤러에게도 완성된 HTML을 제공합니다.

    하지만 SSR에도 단점이 있습니다. 매 요청마다 서버가 HTML을 다시 생성하므로 서버 부하 증가가 불가피하고, Time to First Byte (TTFB) 가 느려질 수 있습니다​.

    사용자 입장에서는 서버가 HTML 생성 및 데이터 패칭을 마칠 때까지 첫 바이트를 받지 못해 기다림이 길어지는 겁니다​.

    또한 일단 HTML을 받고 나서도, 브라우저가 React 코드를 hydrate하는 동안 그 페이지는 인터랙션이 잠시 막혀 있습니다​.

    즉, SSR 페이지는 초기 화면은 빨리 보여주지만 완전히 인터랙티브해지기까지는 클라이언트 JS 로드/실행 시간이 추가로 드는 단점이 있습니다.

    3. SSG (Static Site Generation)

    빌드 시점에 미리 HTML을 생성해 두고 요청 시마다 같은 정적 파일을 서빙하는 방식입니다.

    Next.js에서는 getStaticProps로 구현합니다.

    변경이 자주 일어나지 않는 페이지 (예: 블로그, 마케팅 페이지)에 유용하며, CDN 캐시를 활용해 TTFB를 극단적으로 낮출 수 있다는 장점이 있습니다​.

    초기 요청시 서버 연산이 거의 없으므로 매우 빠르지만, 페이지 콘텐츠가 바뀌면 다시 빌드하기 전까지는 업데이트가 반영되지 않는 정적성이 한계입니다.

    4. ISR (Incremental Static Regeneration)

    SSG의 한계를 보완하기 위해 Next.js가 도입한 하이브리드 방식으로, 정적 생성을 하면서 일부 페이지는 백그라운드에서 재생성할 수 있게 합니다.

    예를 들어 revalidate: 60으로 설정하면, 해당 페이지 요청 시 캐시된 정적 페이지가 제공되다가 60초가 지난 후 처음 들어오는 요청에서 새로운 HTML을 생성하여 업데이트해 둡니다​.

    이를 통해 자주 변경되는 데이터도 정적처럼 제공하면서 일정 주기로 최신화를 해주는 것입니다.

    하지만 ISR도 완벽하진 않아, 재생성되기 직전에 요청하는 사용자는 여전히 오래된 데이터를 볼 수 있는 타이밍 이슈가 있고, 결국 서버에서 재생성을 트리거해야 하므로 완전히 서버 부하를 없애지는 못합니다​.

     

    이러한 기존 전략들은 페이지 단위로 동작한다는 공통점이 있습니다.

    반면 React Server Components (RSC) 기반의 App Router는 컴포넌트 단위로 SSR과 CSR의 장점을 취합한 새로운 접근이라고 볼 수 있습니다.

    RSC의 핵심 아이디어는 컴포넌트별로 "이건 서버에서 렌더링하고, 이건 클라이언트에서 동작한다"를 구분함으로써, 페이지를 구성하는 각각의 조각마다 최적의 렌더링 방식을 적용하는 것입니다.

     

    예를 들어, 무거운 데이터 연산이나 DB 접근이 필요한 컴포넌트는 서버에서 렌더링하고 결과만 보내며, 사용자와의 상호작용이 필요한 컴포넌트만 클라이언트로 보내는 식이죠​.

     

    이는 SSR처럼 초기 화면을 서버에서 그리면서도, CSR처럼 부분적인 비동기 로드와 인터랙티브성을 병행합니다​.

    RSC를 사용하면 SSR의 단점이던 TTFB 지연 문제도 스트리밍으로 상쇄할 수 있습니다.

    서버는 가능한 빨리 첫 부분의 HTML을 스트림으로 보내고 (예: FCP 개선), 나머지 데이터는 준비되는 대로 추가 전송함으로써 TTFB를 최소화합니다​.

     

    실제로 한 사례에서 Page Router (전통 SSR) 대비 App Router(RSC 사용)로 전환하자, First Contentful Paint가 약 4.4초에서 0.75초 수준으로 크게 단축되고, TTFB도 0.4초에서 0.15초 수준으로 개선되었다는 보고가 있습니다​.

    이러한 개선은 스트리밍 덕분에 브라우저가 전체 응답이 완료되기 전에 리소스 로드를 시작할 수 있기 때문입니다​.

    Next.js Page Router(좌) SSR과 App Router(우) RSC 적용 시의 웹 바이탈 지표 비교 예시

    좌측은 SSR로 전체 요청이 완료된 후에야 리소스 로딩이 시작되므로 FCP가 4초 이상인 반면, 우측은 RSC 스트리밍 덕분에 요청이 진행되는 동안 리소스들이 미리 로드되어 FCP가 0.7초대로 크게 향상되었습니다. 또한 TTFB, 요청 시간 등도 크게 개선된 것을 볼 수 있습니다.

     

    정리하면, App Router의 RSC는 SSR과 CSR의 하이브리드라 할 수 있습니다.

    SSR처럼 초기 HTML을 제공하지만, CSR처럼 필요한 자원 로딩을 병렬화하고, SSG/ISR처럼 캐싱을 활용할 수 있습니다.

    개발자는 각 컴포넌트마다 fetch의 cache 옵션이나 revalidate 설정을 통해 SSG처럼 캐시할지, ISR처럼 주기적 갱신할지, 항상 최신 데이터로 SSR할지 선택할 수 있습니다​.

     

    이런 세밀한 제어는 Page Router 시절의 SSR/SSG 옵션보다 훨씬 유연합니다​.

    결국 App Router는 이전의 SSR, SSG, ISR 개념을 한데 통합하여 컴포넌트 단위로 최적화 전략을 적용하도록 발전한 것이라 볼 수 있습니다.

    React Server Components: 아키텍처 혁신과 동작 원리

    React Server Components(서버 컴포넌트)는 Next.js App Router의 핵심적인 기반입니다.

    RSC 도입으로 Next.js의 아키텍처는 크게 달라졌는데, 우선 컴포넌트가 실행되는 환경에 따라 두 종류로 구분됩니다.

    1. Server Component (서버 컴포넌트)

    서버에서만 실행되고 브라우저에 자바스크립트 번들이 보내지지 않는 컴포넌트입니다.

    app 디렉토리의 컴포넌트들은 기본적으로 모두 서버 컴포넌트로 간주됩니다 (파일 상단에 "use client" 지시어가 없는 경우).

    서버 컴포넌트는 React가 순수한 HTML과 데이터 조각 형태로 렌더링 결과를 만들어내며, 브라우저는 이를 받아 화면에 그립니다.

    서버 컴포넌트 자체는 브라우저에서 실행되지 않기 때문에 그 내부에서 비동기 데이터 패칭 (await fetch)이 자유롭고, 어떤 비밀 키나 서버측 코드도 클라이언트에 노출되지 않는 장점이 있습니다​.

    또 출력 결과가 항상 정적이므로 hydration이 불필요하여, 해당 컴포넌트 부분은 바로 완성된 UI로 작동합니다.

    2. Client Component (클라이언트 컴포넌트)

    말 그대로 클라이언트에서 실행되는 컴포넌트입니다.

    기존 React 컴포넌트와 동일하게 브라우저 번들에 포함되어 동작하며, 주로 이벤트 핸들링이나 브라우저 API 이용처럼 인터랙티브한 UI 부분에 사용됩니다.

    App Router에서는 컴포넌트 파일 상단에 "use client"를 선언하면 그 파일 및 하위에 정의된 컴포넌트를 클라이언트 컴포넌트로 취급합니다.

    클라이언트 컴포넌트도 초기 렌더링은 서버에서 일부분 수행될 수 있지만(SSR과 유사하게 HTML 생성), 최종 동작은 브라우저 JS로 이루어지고 hydration이 필요합니다.

    따라서 너무 광범위하게 클라이언트 컴포넌트를 사용하면 번들 크기가 다시 커질 수 있으므로, 가이드에서는 정말 필요한 부분만 클라이언트 컴포넌트로 만들 것을 권장합니다.

     

    App Router에서는 이 서버/클라이언트 컴포넌트들이 한 트리 안에서 공존합니다.

    루트 레이아웃부터 페이지, 내부 위젯들까지 기본적으로 서버 컴포넌트로 구성하고, 그 중 사용자와 상호작용이 필요한 일부만 클라이언트 컴포넌트로 둡니다.

    React가 알아서 이 둘을 조합하여 렌더링 스트림을 생성해주기 때문에 개발자는 마치 SSR과 CSR을 동시에 코딩하는 효과를 얻습니다.

    이 다이어그램은 App Router에서 서버 컴포넌트(SC)와 클라이언트 컴포넌트(CC)가 혼합된 구성 예시를 보여줍니다.

     

    회색 원(SC)은 서버 컴포넌트, 파란 원(CC)은 클라이언트 컴포넌트를 나타냅니다.

    루트 경로와 레이아웃, 대부분의 UI 조각들은 서버 컴포넌트로 렌더링되고, 폼이나 입력같이 상호작용이 필요한 부분만 클라이언트 컴포넌트로 구현됩니다.

    이렇게 하면 대다수 UI는 서버에서 그려져 전송되고, 클라이언트에는 최소한의 JS만 전달되어 성능을 최적화합니다.

    서버 컴포넌트 방식이 가져오는 성능 및 DX 측면의 이점

    1. 전송 JS 감소 및 로드 성능 향상

    서버 컴포넌트는 그 코드가 브라우저로 보내지지 않기 때문에, 페이지 로드를 위해 필요한 자바스크립트 번들 크기가 줄어듭니다​.

    예컨대 서버 컴포넌트 내에서 거대한 차트 라이브러리를 사용하더라도, 결과적으로 브라우저엔 렌더링된 HTML (예: <svg>...</svg>)만 보내므로 클라이언트는 거대한 차트 라이브러리를 다운받을 필요가 없습니다​.

    이는 곧 JS 파싱/실행 시간 단축으로 이어져 Time to Interactive(TTI) 개선을 가져옵니다.

    또한 서버 컴포넌트 출력은 정적이므로 추가적인 hydration 없이 바로 완성된 DOM으로 간주되어, 초기 로드 후 메인 쓰레드 작업량을 크게 줄여줍니다.


    2. 데이터 접근과 보안

    서버 컴포넌트는 데이터 소스와 가까운 서버 측에서 실행되므로, DB 쿼리나 서버 API 호출 같은 작업에 유리합니다​.

    네트워크 지연이 적고, 직접 백엔드에 접근할 수 있으므로 클라이언트에서 API를 호출하는 것보다 효율적입니다.

    또한 서버에서 실행되므로 비밀 키, 토큰, 민감 로직을 브라우저에 노출하지 않고 처리할 수 있습니다​.

    예를 들어 서드파티 API 키를 직접 React 컴포넌트에 넣으면 CSR에서는 위험하지만, 서버 컴포넌트라면 문제 없습니다.


    3. 병렬 패칭 및 무지연 렌더링

    여러 서버 컴포넌트들이 한 페이지에서 함께 렌더링될 때, Next.js는 필요한 데이터를 병렬로 가져오고 한 번에 렌더링 합니다.

    따라서 워터폴 요청이 제거되고, 종속되지 않은 여러 데이터 fetch가 동시에 이루어져 전체 로드 시간이 단축됩니다​.

    React 18의 특성상 서버에서 컴포넌트 트리를 렌더링할 때 알아서 최적의 경로로 수행되며, 각 컴포넌트 출력은 스트림으로 이어붙여집니다.

    개발자는 단순히 각 컴포넌트에서 await으로 데이터를 가져오면 되고, Next.js가 요청 중복 제거(deduplication)까지 해주므로 같은 URL을 여러번 호출하면 한 번만 패칭하고 결과를 공유하는 식으로 최적화됩니다​.


    4. 결과 캐싱

    서버 컴포넌트의 결과는 요청 간에 캐시될 수 있습니다.

    Next.js는 fetch() 호출에 대해 자동 캐싱을 지원하고, 개발자가 원하면 force-cache(완전 캐시)나 no-store(매번 요청) 또는 revalidate 기간 등을 지정해 컴포넌트별로 캐싱 전략을 제어할 수 있습니다​.

    이 뜻은 동일한 입력에 대한 서버 컴포넌트 출력이 여러 사용자에게 재사용될 수 있다는 것이며, SSR일지라도 반복 렌더링 비용을 줄일 수 있습니다.

    캐시된 HTML 조각을 서빙하므로 SSG에 가까운 효율을 내기도 합니다.

     

    이런 RSC 기반 아키텍처로 인해 Next.js 15에서는 "가능한 한 많은 UI를 서버 컴포넌트로 작성하고, 꼭 필요한 상호작용 부분만 클라이언트 컴포넌트로 작성"하는 것이 성능과 코드 관리 양면에서 최선의 방법이 되었습니다​.

     

    예를 들어 제품 카드 컴포넌트를 만든다면, 제품 이미지나 설명, 가격 표시 등은 서버 컴포넌트로 구현하고 (서버에서 직접 DB나 API 호출하여 데이터 준비), "장바구니에 추가" 버튼처럼 클릭 이벤트 처리가 필요한 부분만 내부에서 클라이언트 컴포넌트로 분리하는 식입니다​.

    이렇게 하면 UI 대부분은 서버에서 그려져 전송되고, 클라이언트에는 작은 이벤트 처리 코드만 보내주면 되므로 성능 최적화와 유지보수성에서 유리합니다​.

     

    다음 편에서는 서버 컴포넌트 및 서버 액션을 중심으로, 성능 향상 효과 그리고 개발자 경험(DX)의 변화에 대해 알아보겠습니다.

    반응형

    댓글

Designed by Tistory.