App Router vs Page Router


기능 앱 라우터 페이지 라우터
라우팅 타입 서버 중심 클라이언트 사이드
서버 컴포넌트 지원 O X
복잡도 더 복잡함 더 간단함
성능 더 좋음 더 나쁨
유연성 더 유연함 덜 유연함
파일 기반 라우팅 중첩 폴더를 사용해 라우트 정의 파일이 직접 라우트를 나타냄
컴포넌트 기본적으로 서버 컴포넌트 기본적으로 클라이언트 컴포넌트
데이터 페칭 fetch 함수 사용 getServerSideProps, getStaticProps, getInitialProps 사용
레이아웃 레이아웃 중첩 / 동적 레이아웃 정적 레이아웃
동적 라우트 지원하지만 문법 다름 지원
클라이언트 사이드 네비게이션 router.push로 지원 Link 컴포넌트로 지원
우선순위 페이지 라우터보다 우선 앱 라우트가 없을 경우 대체

서버 컴포넌트


클라이언트 경계 ⭐


아래 같은 구조에서 <Article /> 컴포넌트에 use client 지시문을 사용하면 하위 컴포넌트는 모두 암묵적으로 클라이언트 컴포넌트로 변환된다. 즉, use client 지시문은 클라이언트 경계를 생성한다고 볼 수 있다.

Untitled

만약 어플리케이션의 높은 수준에서(상위) use client 지시문을 사용하여 클라이언트 컴포넌트로 변경하면, 모든 하위 컴포넌트(Header, MainContent)는 클라이언트 컴포넌트가 된다.

"use client";

import { DARK_COLORS, LIGHT_COLORS } from "@/constants.js";
import Header from "./Header";
import MainContent from "./MainContent";

function Homepage() {
  const [colorTheme, setColorTheme] = React.useState("light");
  const colorVariables = colorTheme === "light" ? LIGHT_COLORS : DARK_COLORS;
  
  return (
    <body style={colorVariables}>
      <Header /> {/* 암묵적으로 클라이언트 컴포넌트로 변환 */}
      <MainContent /> {/* 암묵적으로 클라이언트 컴포넌트로 변환 */}
    </body>
  );
}

모든 컴포넌트가 클라이언트 컴포넌트로 변경되면 Next.js 서버 컴포넌트를 사용하는 장점이 사라지므로 아래와 같은 차선책(workaround)으로 서버 컴포넌트가 되도록 만들 수 있다.

  1. 상태를 사용해야 하는 <ColorProvider /> 클라이언트 컴포넌트 따로 분리

    "use client";
    
    import { DARK_COLORS, LIGHT_COLORS } from "@/constants.js";
    
    function ColorProvider({ children }) {
      const [colorTheme, setColorTheme] = React.useState("light");
      const colorVariables = colorTheme === "light" ? LIGHT_COLORS : DARK_COLORS;
      
      return (
    		<body style={colorVariables}>
    			{children}
    		</body>
    	);
    }
    
  2. <Homepage /> 서버 컴포넌트에서 <ColorProvider /> 클라이언트 컴포넌트 임포트해서 사용

    import Header from "./Header";
    import MainContent from "./MainContent";
    import ColorProvider from "./ColorProvider";
    
    function Homepage() {
      return (
        <ColorProvider>
          <Header /> {/* 서버 컴포넌트 */}
          <MainContent /> {/* 서버 컴포넌트 */}
        </ColorProvider>
      );
    }
    

<Header />, <MainContent /> 컴포넌트를 import 한 곳이 <Homepage /> 서버 컴포넌트 이므로, 여기서 렌더링 하는 컴포넌트들은 기본적으로 서버 컴포넌트로 작동한다고 보면 된다. 즉, 어떤 유형의 컴포넌트에서 불러왔는지에 따라 하위 컴포넌트의 서버/클라이언트 작동 방식이 결정된다.

렌더링 흐름 비교


<aside> <img src="/icons/search_gray.svg" alt="/icons/search_gray.svg" width="40px" /> 편의를 위해 Render Shell은 레이아웃 렌더링으로 번역

</aside>

CSR

모든 콘텐츠를 클라이언트에서 렌더링해서 서버 부담을 줄이고 인터랙티브한 사용자 경험을 제공하지만, 초기 로딩 속도가 길어지는 단점이 있다.

Untitled

  1. (클라이언트) JS 번들을 포함한 리소스 다운로드

  2. (클라이언트) 빈 HTML 렌더링

    ===== 빈 화면 표시 | 인터렉션 가능 =====

  3. (클라이언트) 콘텐츠 다운로드

  4. (클라이언트) 콘텐츠 렌더링

SSR

페이지 라우터를 사용해도 서버에서 콘텐츠를 미리 다운로드하고 HTML을 생성할 수 있지만, 컴포넌트 트리 가장 상단에 있는 라우트 레벨에서만 작동하는 단점이 있으며 이를 Pre-RSC라고도 부른다. 아래 이미지는 기본 SSR 렌더링 플로우. CSR에 비해 First Paint가 빨라지는 장점이 있다.

Untitled

  1. (서버) 레이아웃 HTML 생성

  2. (클라이언트) JS 번들을 포함한 리소스 다운로드

    ===== 레이아웃 표시 | 인터렉션 불가 =====

  3. (클라이언트) 하이드레이션

    ===== 인터렉션 가능 =====

  4. (클라이언트) 콘텐츠 다운로드

  5. (클라이언트) 콘텐츠 렌더링

SSR with Server Component

SSR에 비해 First Paint는 다소 늦어지지만, Content Painted(콘텐츠 페인트)가 빨라지는 장점이 있다.

Untitled

  1. (서버) 콘텐츠 다운로드 후 HTML 생성

  2. (클라이언트) JS 번들을 포함한 리소스 다운로드

    ===== 레이아웃 및 콘텐츠 표시 | 인터렉션 불가 =====

  3. (클라이언트) 하이드레이션

    ===== 인터렉션 가능 =====

SSR with Server Component, Streaming(Suspense)

UI를 청크 단위로 나눠서 우선순위가 높거나 데이터에 의존하지 않는 컴포넌트를 먼저 전송(스트리밍), 기존 SSR보다 더 빠르게 콘텐츠를 제공할 수 있다. 서버 컴포넌트가 스트리밍되는 동안 클라이언트에선 하이드레이션이 동시에 진행돼서 더 빠른 하이드레이션 가능 → 초기 인터랙티브 시간(TTI)을 단축할 수 있는 장점이 있다.

Untitled

  1. (서버) 레이아웃 HTML 생성 및 콘텐츠 다운로드

  2. (클라이언트) JS 번들을 포함한 리소스 다운로드

    ===== 레이아웃 표시 | 인터렉션 불가 =====

  3. (클라이언트) 부분 하이드레이션

    ===== 부분 인터렉션 가능 =====

  4. (서버) 콘텐츠를 포함한 HTML 생성

  5. (클라이언트) 콘텐츠 HTML 다운로드 및 하이드레이션

    ===== 전체 인터렉션 가능 =====

레퍼런스


Next.js 14: The Differences Between App Router and Page Router

Making Sense of React Server Components