
나는 Next.js를 비교적 최근에 접해봤다. 그래서 자연스럽게 App Router만 사용해봤고, Pages Router는 사용해본 적이 없었다.
기술 면접을 준비하면서 두 라우터 방식의 차이와 각각의 장단점을 명확히 알고, 왜 내가 App Router를 선택했는지 설명할 수 있어야 한다는 필요성을 느끼게 되었다. 그래서 이번 글에서는 Next.js의 Pages Router와 App Router의 차이점을 정리해보고, 어떨 때 사용하는 것이 좋은지 적어보고자 한다.
📄 Next.js의 Page Router (Next.js 13 이전까지의 기본 방식)
Pages Router는 Next.js의 전통적인 라우팅 시스템으로,
pages 디렉토리의 파일 구조가 곧 애플리케이션의 라우트 구조가 되는 파일 기반 라우팅을 사용한다.
📦 디렉토리 구조
📦 my-first-next/
├─ public/
├─ src/
│ ├─ components/
│ └─ pages/ # Page Router 기반 라우팅
│ ├─ index.js # '/' 루트
│ ├─ about.js # '/about'
│ ├─ login.js # '/login'
│ └─ team/ # 동적 라우트
│ └─ [name].js # '/team/홍길동', '/team/amy' 등
Pages Router 방식의 폴더 구조에는 pages라는 폴더가 존재한다.
pages 폴더에 파일을 추가하는 것만으로도 페이지를 생성할 수 있고, 자동으로 주소가 매핑된다.
pages/ 디렉토리 안의 파일명이 라우트가 된다 (pages/about.js -> /about)
동적 세그먼트는 파일 또는 폴더 이름을 대괄호로 감싸서 파일을 생성하면 된다.
📚 데이터 패칭 방식
페이지 라우터에서는 getServerSideProps, getStaticProps, getStaticPaths 와 같은 함수를 사용하여 SSR이나 SSG를 구현한다.
1) getServerSideProps
export async function getServerSideProps() {
// 외부 API에서 데이터 가져오기
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const repo = await res.json()
// props를 통해 페이지에 데이터 전달
return { props: { repo } }
}
export default function Page({ repo }) {
return (
<main>
<p>{repo.stargazers_count}</p>
</main>
)
}
getServerSideProps 함수를 호출하게 되면 반환된 데이터를 사용하여 각 요청에 미리 페이지를 렌더링하게 된다.
서버 사이드에서만 실행되며 클라이언트 사이드에서는 실행되지 않는다.
getServerSideProps는 요청이 있을 때마다 데이터를 가져와서 페이지를 렌더링 해야 할 때 사용한다.
굳이 요청마다 데이터를 가져올 필요가 없다면 클라이언트 사이드나 getStaticProps를 사용하면 된다.
2) getStaticProps
export async function getStaticProps() {
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const repo = await res.json()
return { props: { repo } }
}
export default function Page({ repo }) {
return repo.stargazers_count
}
getStaticProps는 빌드 시 외부 데이터를 fetch하여 정적 페이지를 생성하는 함수이다.
반드시 return 되는 값은 객체여야 하며, 호출 시마다 매번 Data를 fetch하지 않아서, getServerSideProps 보다 성능면에서 좋다.
클라이언트에게 페이지를 전달해주기 전 API 요청으로 데이터를 불러온 뒤 정적 페이지를 생성 해야할 때 사용한다.
3) getStaticPaths
export async function getStaticPaths() {
const posts = await fetchAllPosts();
return {
paths: posts.map((post) => ({ params: { id: post.id } })),
fallback: false,
};
}
페이지가 동적 라우팅과 getStaticProps를 사용하는 경우에 정적으로 렌더링 할 경로를 설정하기 위한 함수이다.
getServerSideProps와 함께 사용할 수 없고, 반드시 getStaticProps와 함께 사용해야 한다.
이렇게 async 함수를 작성해주고, params: {} 형태로 paths 객체를 리턴한다.
📄 Next.js의 App Router (Next.js 13 이후의 기본 방식)
App Router는 Next.js13에서 도입된 새로운 라우팅 시스템이다.
React 18의 새로운 기능들을 활용하며, 특히 React Server Components를 기본적으로 지원한다.
app 디렉토리를 루트로, 폴더 기반으로 라우팅을 선언한다.
📦 디렉토리 구조
📦 my-first-next/
├─ public/
├─ src/
│ ├─ components/
│ └─ app/ # App Router 기반 라우팅
│ ├─ about/
│ │ └─ page.tsx # '/about'
│ ├─ login/
│ │ └─ page.tsx # '/login'
│ └─ team/
│ ├─ layout.tsx # '/team/*' 공통 레이아웃
│ ├─ [name]/ # 동적 세그먼트
│ │ └─ page.tsx # '/team/amy' 등
│ ├─ loading.tsx
│ └─ error.tsx
App Router 방식의 폴더 구조에는 app라는 폴더가 존재한다.
/login 으로 라우팅 하기 위해선 /app 디렉토리 안에 /login 디렉토리를 생성하고 page 파일을 생성하면 된다.
동적 라우트도 폴더 이름을 대괄호로 감싸서 하위에 page 파일을 생성하면 된다.
📚 데이터 패칭 방식
App Router에서는 Pages Router에서 사용하던 getServerSideProps와 getStaticProps 등의 함수를 사용하지 못한다.
대신 서버 컴포넌트 내에서 async/await와 fetch API를 직접 사용할 수 있다.
// 데이터 패칭 함수
async function getData() {
const res = await fetch("https://api.example.com/...");
if (!res.ok) {
throw new Error("Failed to fetch data");
}
return res.json();
}
// 페이지 컴포넌트 (Server Component)
export default async function Page() {
const data = await getData();
return (
<main>
<h1>데이터 패칭 예시</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</main>
);
}
서버 컴포넌트는 기본적으로 서버에서 실행되므로 API 호출이 클라이언트에 노출되지 않아 애플리케이션을 더욱 안전하게 보호 가능하다.
또한 Pages Router의 try-catch보다 더 구조적인 에러 관리가 가능하다.
// SSR처럼 매 요청마다 새로 패칭
await fetch("https://api.example.com/data", {
cache: "no-store",
});
// ISR: 60초마다 재검증
await fetch("https://api.example.com/data", {
next: { revalidate: 60 },
});
fetch에 옵션을 추가하여 캐싱 정책을 더욱 세밀하게 조정할 수 있다.
즉, fetch의 옵션만으로 SSR, SSG, ISR을 유연하게 설정할 수 있다.
아무 옵션도 주지 않으면 cache: "force-cache"가 기본값이다.
캐싱 옵션 종류
1) cache : "force-cache"
- 기본 설정이며, 빌드 시점 또는 첫 요청 시 가져온 데이터를 계속 캐싱한다.
- 변경이 거의 없는 정적 데이터에 적합하고, SSG와 동일한 효과를 보여준다.
2) cache : "no-store"
- 캐싱하지 않고, 요청마다 항상 새 데이터를 가져온다.
- Pages Router의 SSR과 동일하고, 자주 변하는 데이터에 적합하다.
3) next : { revalidate : N }
- ISR을 설정하는 옵션으로, 정적 캐시된 데이터를 일정 시간 후에 재검증한다.
- 유저는 항상 빠른 응답을 받을 수 있으며 데이터 최신성을 확보할 수 있다.
🤔 Pages Router vs App Router
| 구분 | Pages Router | App Router |
| 디렉토리 구조 | pages 디렉토리 | app 디렉토리 |
| 라우트 정의 | 파일 이름으로 라우트 정의 | 폴더 이름으로 라우트 정의 |
| 레이아웃 | _app.js, _document.js | layout.js 파일로 중첩 레이아웃 설계 |
| 데이터 패칭 | getStaticProps, getServerSideProps, getStaticPaths 같은 함수 사용 | 컴포넌트 내부에서 async/await + fetch 사용 |
| 서버 컴포넌트 | 클라이언트 컴포넌트만 존재 | 서버 컴포넌트가 기본 지원, 필요한 경우 "use client" 선언 |
| 동적 라우트 | [param].js 파일 생성 | [param] 폴더 안에 page.js 생성 |
| Hydration | 전체 하이드레이션 | 부분 하이드레이션 |
💭 언제 어떤 라우터를 사용해야 할까?
Pages Router는 기존 Next.js에서 오랫동안 사용해온 방식이라 기존에 사용하던 분들이라면 익숙하게 다룰 수 있다는 장점을 가지고 있다. 따라서 예전에 작성된 프로젝트나 프로젝트가 단순한 경우라면 Pages Router를 쓰는게 더 효율적일 수 있다.
App Router는 Next.js 13부터 도입된 새로운 라우팅 방식으로, 중첩 레이아웃, 스트리밍 같은 최신 기능을 활용할 수 있어 복잡한 레이아웃 구조를 다루거나 초기 로딩 속도나 번들 크기 최적화가 중요한 서비스라면 App Router를 사용하는 것이 훨씬 유리하다.
앞으로도 App Router가 훨씬 더 중요해질 것으로 예상되기에, 새로운 프로젝트를 시작한다면 App Router를 고려하는 것이 좋을 것이다.
'Next.js' 카테고리의 다른 글
| [Next.js] Next.js에서 SSR을 실행하는 과정과 hydration (1) | 2025.07.13 |
|---|