기능 | 앱 라우터 | 페이지 라우터 |
---|---|---|
라우팅 타입 | 서버 중심 | 클라이언트 사이드 |
서버 컴포넌트 지원 | O | X |
복잡도 | 더 복잡함 | 더 간단함 |
성능 | 더 좋음 | 더 나쁨 |
유연성 | 더 유연함 | 덜 유연함 |
파일 기반 라우팅 | 중첩 폴더를 사용해 라우트 정의 | 파일이 직접 라우트를 나타냄 |
컴포넌트 | 기본적으로 서버 컴포넌트 | 기본적으로 클라이언트 컴포넌트 |
데이터 페칭 | fetch 함수 사용 | getServerSideProps, getStaticProps, getInitialProps 사용 |
레이아웃 | 레이아웃 중첩 / 동적 레이아웃 | 정적 레이아웃 |
동적 라우트 | 지원하지만 문법 다름 | 지원 |
클라이언트 사이드 네비게이션 | router.push로 지원 | Link 컴포넌트로 지원 |
우선순위 | 페이지 라우터보다 우선 | 앱 라우트가 없을 경우 대체 |
useEffect
useState
등 React API는 서버 컴포넌트에서 사용 불가use client
지시문(Directive)을 사용하여 클라이언트 컴포넌트로 옵트인
use client
지시문은 파일/모듈 수준에서 작동아래 같은 구조에서 <Article />
컴포넌트에 use client
지시문을 사용하면 하위 컴포넌트는 모두 암묵적으로 클라이언트 컴포넌트로 변환된다. 즉, use client
지시문은 클라이언트 경계를 생성한다고 볼 수 있다.
만약 어플리케이션의 높은 수준에서(상위) 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)으로 서버 컴포넌트가 되도록 만들 수 있다.
상태를 사용해야 하는 <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>
);
}
<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>
모든 콘텐츠를 클라이언트에서 렌더링해서 서버 부담을 줄이고 인터랙티브한 사용자 경험을 제공하지만, 초기 로딩 속도가 길어지는 단점이 있다.
(클라이언트) JS 번들을 포함한 리소스 다운로드
(클라이언트) 빈 HTML 렌더링
===== 빈 화면 표시 | 인터렉션 가능 =====
(클라이언트) 콘텐츠 다운로드
(클라이언트) 콘텐츠 렌더링
페이지 라우터를 사용해도 서버에서 콘텐츠를 미리 다운로드하고 HTML을 생성할 수 있지만, 컴포넌트 트리 가장 상단에 있는 라우트 레벨에서만 작동하는 단점이 있으며 이를 Pre-RSC라고도 부른다. 아래 이미지는 기본 SSR 렌더링 플로우. CSR에 비해 First Paint가 빨라지는 장점이 있다.
(서버) 레이아웃 HTML 생성
(클라이언트) JS 번들을 포함한 리소스 다운로드
===== 레이아웃 표시 | 인터렉션 불가 =====
(클라이언트) 하이드레이션
===== 인터렉션 가능 =====
(클라이언트) 콘텐츠 다운로드
(클라이언트) 콘텐츠 렌더링
SSR에 비해 First Paint는 다소 늦어지지만, Content Painted(콘텐츠 페인트)가 빨라지는 장점이 있다.
(서버) 콘텐츠 다운로드 후 HTML 생성
(클라이언트) JS 번들을 포함한 리소스 다운로드
===== 레이아웃 및 콘텐츠 표시 | 인터렉션 불가 =====
(클라이언트) 하이드레이션
===== 인터렉션 가능 =====
UI를 청크 단위로 나눠서 우선순위가 높거나 데이터에 의존하지 않는 컴포넌트를 먼저 전송(스트리밍), 기존 SSR보다 더 빠르게 콘텐츠를 제공할 수 있다. 서버 컴포넌트가 스트리밍되는 동안 클라이언트에선 하이드레이션이 동시에 진행돼서 더 빠른 하이드레이션 가능 → 초기 인터랙티브 시간(TTI)을 단축할 수 있는 장점이 있다.
(서버) 레이아웃 HTML 생성 및 콘텐츠 다운로드
(클라이언트) JS 번들을 포함한 리소스 다운로드
===== 레이아웃 표시 | 인터렉션 불가 =====
(클라이언트) 부분 하이드레이션
===== 부분 인터렉션 가능 =====
(서버) 콘텐츠를 포함한 HTML 생성
(클라이언트) 콘텐츠 HTML 다운로드 및 하이드레이션
===== 전체 인터렉션 가능 =====
Next.js 14: The Differences Between App Router and Page Router