Next.js 13부터 App Router(app/ 디렉토리)가 등장하면서 기존 Pages Router(pages/ 디렉토리) 방식과 큰 차이가 생겼습니다. 아래 표는 두 방식을 간략하게 비교한 표 입니다.
| 🚀 주요 차이점 | 🏗 Pages Router (기존) | 🌟 App Router (Next.js 13+) |
|---|---|---|
| 디렉토리 구조 | pages/ 사용 | app/ 사용 |
| 라우팅 방식 | 파일 기반 (예: pages/index.tsx) | 폴더 + page.tsx 사용 (예: app/page.tsx) |
| 데이터 패칭 | getServerSideProps, getStaticProps | fetch() 자동 캐싱 및 useEffect, useState 사용 |
| 서버/클라이언트 분리 | 모든 컴포넌트가 클라이언트 컴포넌트 | 기본적으로 Server Component (필요 시 "use client") |
| SSR 지원 방식 | getServerSideProps 사용 | 기본적으로 서버 렌더링 지원 (자동) |
| 글로벌 상태 관리 | Provider를 _app.tsx에서 설정 | Provider를 layout.tsx에서 설정 |
| 라우트 그룹핑 | ❌ 없음 | ✅ app/(group)/page.tsx 사용 가능 |
| Middleware | middleware.ts 사용 | ✅ 동일하게 middleware.ts 지원 |
| SEO 지원 | next/head 사용 | ✅ metadata 내장 지원 |
| React Suspense 지원 | ❌ 제한적 | ✅ 기본 지원 (로딩 처리 간편) |
위의 내용을 좀 더 자세히 설명해 보겠습니다.
기존 Pages Router에서는 모든 컴포넌트가 기본적으로 클라이언트에서 렌더링 되었습니다. SSR을 사용하려면 getServerSideProps, getStaticProps, getInitialProps 같은 API를 따로 사용해야 했고, 이것들이 혼합되면 복잡해졌죠.
App Router에서는 기본적으로 모든 컴포넌트가 서버 컴포넌트(Server Component)로 동작합니다. 서버에서 데이터를 처리하고, 필요한 부분만 클라이언트에서 실행할 수 있습니다.
// ✅ App Router 방식 (서버에서 실행됨)
export default async function Page() {
const data = await fetch("https://api.example.com/data");
const json = await data.json();
return <div>{json.message}</div>;
}
📌 → 클라이언트로 불필요한 데이터 전달을 줄여 성능 최적화
📌 → 서버에서 데이터 가져오는 코드 정리(getServerSideProps 필요 없음)
기존 Next.js에서는 모든 컴포넌트가 클라이언트에서 실행돼야 했습니다. 그래서 SSR을 할 때도 클라이언트에서 불필요한 코드가 로드되는 경우가 많았습니다.
React Server Component(RSC)를 활용해서 클라이언트에서 실행할 필요 없는 로직을 서버에서 처리 가능합니다.
필요한 곳에만 "use client"를 추가해서 클라이언트 컴포넌트로 만들 수 있습니다.
// 🚀 서버 컴포넌트 (기본값)
export default async function ServerComponent() {
const data = await fetch("https://api.example.com/data");
return <div>{data.message}</div>;
}
// 🚀 클라이언트 컴포넌트 (use client 추가 필요)
"use client";
import { useState } from "react";
export default function ClientComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
📌 → 클라이언트 컴포넌트는 UI/상태 관리, 서버 컴포넌트는 데이터 로딩 담당
📌 → 서버 컴포넌트는 무거운 연산도 서버에서 처리 가능
기존에는 getServerSideProps를 사용할 때 모든 데이터가 준비될 때까지 화면이 비어있는 문제가 있었습니다.
App Router는 React Suspense를 기본 지원해서, 데이터가 준비된 부분부터 점진적으로 화면을 렌더링할 수 있습니다.
// 🚀 Suspense를 활용한 코드 예제
import { Suspense } from "react";
import SlowComponent from "./SlowComponent";
export default function Page() {
return (
<div>
<h1>페이지 로딩 중...</h1>
<Suspense fallback={<p>로딩 중...</p>}>
<SlowComponent />
</Suspense>
</div>
);
}
📌 → 일부 데이터가 늦게 오더라도 페이지 전체가 멈추지 않음
📌 → 유저 경험(UX) 개선
pages/는 파일명 기반 라우팅이라 그룹핑이 어려웠습니다. API 라우트와 페이지 라우트가 같은 pages/ 안에 섞여 있어 관리하기 불편했습니다.
폴더 기반 라우팅으로 더 직관적인 구조를 만들 수 있습니다. 레이아웃(layout.tsx) 단위로 공통 UI를 구성할 수 있습니다.
📂 App Router 방식
app/
├── layout.tsx # 공통 레이아웃
├── page.tsx # /
├── about/
│ ├── page.tsx # /about
├── dashboard/
│ ├── layout.tsx # 대시보드 공통 레이아웃
│ ├── page.tsx # /dashboard
│ ├── settings/
│ │ ├── page.tsx # /dashboard/settings
📌 → 공통 레이아웃을 layout.tsx에서 관리 가능
📌 → app/을 이용해 기능별로 폴더를 정리할 수 있음
API 라우트(/pages/api/*)와 프론트엔드 코드가 같은 pages/ 폴더에 있어서 관리가 불편했습니다.
app/api/* 폴더를 따로 제공해서 API 경로를 분리할 수 있습니다.
Middleware(middleware.ts)도 그대로 지원해서 보안/인증 기능을 간편하게 추가 가능합니다.
// app/api/hello/route.ts
export async function GET() {
return Response.json({ message: "Hello from API!" });
}
전반적으로 비교를 해보니 AppRoute 방식을 만든 의도가 보이네요. 핵심 컨셉에 맞게 SSR기능을 좀더 효율적으로 사용할 수 있도록 만들었다는 생각이 듭니다.