Page Router vs App Router
Next.js의 아키텍처가 App Router로 넘어오면서 프론트엔드 생태계에 많은 변화가 있었는데요. 프론트엔드 개발자로서 실무에서 Page Router 기반의 레거시 프로젝트와 App Router 기반의 신규 프로젝트를 모두 마주칠 확률이 높을 텐데, 두 라우터가 내부적으로 어떻게 다르게 동작하는지 핵심 차이점을 4가지 개발 포인트로 짚어보겠습니다.
1. 라우팅 단위
라우팅이 일어날 때 기존 화면을 어떻게 처리하는지가 가장 큰 차이입니다.
-
Page Router: 라우팅 단위가 **페이지 전체(Page-level)**입니다
/dashboard에서/dashboard/settings로 이동하면, URL에 매핑된 페이지 컴포넌트 전체를 완전히 언마운트(Unmount)하고 새 페이지를 마운트합니다. 이 과정에서 컴포넌트 타입이 유지되는 최상단의_app.tsx정도만 상태가 보존되고 컴포넌트의 로컬 State나 스크롤 위치는 모두 날아갑니다. -
App Router: 라우팅 단위가 **세그먼트(Segment)**라는 조각으로 나뉩니다. Next.js 서버는 루트 레이아웃부터 말단 페이지까지 이어지는 모듈 묶음인
loaderTree구조를 구성해 라우팅을 처리합니다. 페이지를 이동하면 전체 화면을 새로고침하는 대신, 클라이언트 메모리의 **라우터 캐시(Router Cache)**에서 변경된 settings 세그먼트 데이터만 새롭게 교체합니다. 덕분에 공통 부모인 레이아웃(Layout)의 마운트 상태나 로컬 상태가 끊김 없이 그대로 보존됩니다.
2. 렌더링 위치와 번들
작성한 React 컴포넌트가 "어디서" 실행되고 "어떻게" 브라우저로 내려오는지 패러다임이 다릅니다.
-
Page Router: 페이지 하나가 단일 React 트리로 취급됩니다. 서버에서 완성된 HTML을 내려주지만, 화면을 인터랙티브하게 만들려면 결국 브라우저에서
_app부터 시작해 페이지 전체 컴포넌트를 다시 실행하는 **전체 하이드레이션(Hydration)**을 거쳐야 합니다. 따라서 렌더링에 관여하는 모든 코드가 클라이언트 JS 번들에 포함되어야만 하죠. -
App Router: **RSC(React Server Components)**가 기본입니다. 최상단에
'use client'지시어를 쓰지 않은 서버 컴포넌트들은 클라이언트 JS 번들에 아예 포함되지 않습니다. 대신 서버는 실행이 끝난 React 트리를 Flight 페이로드라는 직렬화된 데이터 포맷으로 브라우저에 스트리밍합니다. 브라우저 측 React는 도착한 Flight 데이터를 읽어서, 이미 서버에서 끝난 부분은 건너뛰고 클라이언트 컴포넌트가 있는 부분만 활성화하는 **부분 하이드레이션(Partial Hydration)**을 수행합니다. 무거운 라이브러리를 서버에 두고 클라이언트 JS 번들을 줄일 수 있습니다.
3. 데이터 로딩 모델
API 응답을 기다리는 동안 브라우저에 무엇을 보여줄 수 있는지도 중요합니다.
- Page Router:
getServerSideProps같은 함수가 백엔드에서 모든 데이터를 다 가져올 때까지 렌더링이 블로킹됩니다. 데이터가 완전히 준비된 이후에야 완성된 단일 HTML을 한 번에 브라우저로 보내기 때문에, 무거운 API 호출이 있으면 사용자는 빈 흰 화면(TTFB 지연)을 오래 봐야 합니다. - App Router: 스트리밍(Streaming) 아키텍처를 도입했습니다. 느린 데이터 응답을 마냥 기다리지 않고,
Suspense경계 바깥에 있는 공통 레이아웃 같은 정적 셸(Static Shell) HTML을 즉시 먼저 흘려보냅니다. 사용자는 UI의 뼈대와 로딩 스피너를 바로 볼 수 있고, 이후 백엔드 데이터가 준비되면 서버가 응답을 닫지 않은 채로 직렬화된 Flight 데이터 청크를 스트림에 덧붙여 보냅니다. 브라우저는 이걸 받아 로딩 스피너 자리를 실제 콘텐츠로 즉시 교체합니다.
4. 클라이언트 라우터와 React 동시성(Concurrent) 모델의 결합
클라이언트 단에서 라우팅 상태를 관리하는 메커니즘도 달라졌습니다.
- Page Router: 라우터가 React 외부에 존재하는 싱글톤 클래스(
singletonRouter)로 동작합니다. 네비게이션은 이 클래스가 이벤트를 발생시키고 페이지 트리를 통째로 교체하는 형태라, React 18의 동시성 렌더링 모델과는 결합이 느슨합니다. - App Router: 라우터의 현재 상태가
useReducer를활용한 **React 내부 상태(State)**로 관리됩니다. 페이지 이동 과정 자체가startTransition같은 동시성 기능으로 감싸져 있어서, 네비게이션 중간에 더 시급한 사용자 입력이 들어오면 우선순위를 양보하거나 자연스럽게Suspense폴백을 띄우는 등 React 18의 핵심 엔진과 깊게 통합되어 동작합니다.
결론
App Router는 단순히 폴더 구조가 바뀐 게 아닙니다. RSC, 부분 하이드레이션, Streaming, 그리고 React 18의 Concurrent 모델을 극한으로 활용하기 위해 Next.js가 라우팅 패러다임을 밑바닥부터 재설계한 결과물입니다. 이 모델을 가지고 있다면, 앞으로 프로젝트에서 "어떤 컴포넌트를 서버 컴포넌트로 두고, 어디에 Suspense를 배치할지" 결정할 때 훨씬 수월할 것입니다.