YEV.log

Next.js | App Router 와 Page Router 본문

Web/React & Next.js

Next.js | App Router 와 Page Router

일렁이는코드 2025. 4. 13. 19:11

 

2년 전쯤 생성한  프로젝트는 Next.js 13을 기반으로 한 page router를 사용하고 있었다.

이제 작은 프로젝트를 시작하려고 Next.js 15로 세팅했더니, 'use client'를 반드시 써야 한다는 등의 에러가.. 

 

이유는 App Router로 세팅되었기 때문이었다. 두개의 차이에 대해 알아보자

  Page Router App Router
사용가능 버전 next.js 13 이하 next.js 13.4 이상 (15에서 많이 개선됨)
디렉토리 pages/ app/
라우팅 방식 파일 기반 (pages/about.txs -> /about) 유연한 파일 기반 (pages/about/page.tsx -> /about)
데이터 패칭 - getStatidProps
- getServerSideProps
- getInitialProps
- fetch() 기반 (React Server Components 사용)
- async 컴포넌트에서 직접 데이터 패칭 가능
레이아웃  - 중첩 레이아웃 구현이 복잡
- 클라이언트 컴포넌트와 서버 컴포넌트 분리가 어려움
- 중첩 레이아웃 쉽게 구성 가능
- 클라이언트 컴포넌트와 서버 컴포넌트 구분 ('use client')

 

App Router 방식 폴더 구조 (app/)

app/
├─ layout.tsx              ← 전체 사이트 공통 레이아웃 (헤더, 푸터 등)
├─ mypage/
│  ├─ layout.tsx           ← mypage 전용 레이아웃 (좌측 메뉴 포함!)
│  ├─ page.tsx             ← /mypage → 인덱스 페이지
│  ├─ orders/
│  │  └─ page.tsx          ← /mypage/orders
│  └─ profile/
│     └─ page.tsx          ← /mypage/profile

 

 

 

파일 정리

layout.tsx 이 디렉토리 이하의 모든 route에서 공통 적용되는 레이아웃
page.tsx 이 경로에 해당하는 페이지 본문 (실제 내용)
loading.tsx 로딩 중 보여줄 화면
error.tsx 에러 났을 때 보여줄 화면
not-found.tsx 404일 때 보여줄 화면

 

Page Router는 라우팅 파일만 /pages에 정의할 수 있어 공통 레이아웃 관리가 번거로웠지만
App Router는 /app 내에서 layout.tsx와 page.tsx를 함께 정의할 수 있어 구조가 더 직관적이라는 차이점이 있다.

 

 

서버 컴포넌트

Page Router에서는 페이지 단위에서 서버 데이터를 불러올 때, 아래 함수를 꼭 사용해주었어야 했다.

- getServerSideProps() : 서버에서 매 요청마다 데이터 불러오기

- getStaticProps() : 정적 페이지 생성용 (빌드 시점 데이터)

- getStaticPaths() : 동적 라우팅 시 정적 경로 지정

 

App Router 방식에서는

React Server Component + fetch()의 개선 덕분에 페이지뿐 아니라 서버 컴포넌트 단위에서도 서버 데이터를 직접 불러올 수 있게 되었다. (이게 개인적으로 가장 기존 방식의 불편함을 개선해준 부분이지 않을까 생각했다!)

 

// app/products/page.tsx (Server Component)
async function getProducts() {
  const res = await fetch('https://api.example.com/products', { cache: 'no-store' })
  return res.json()
}

export default async function ProductPage() {
  const products = await getProducts()

  return (
    <div>
      {products.map(p => <div key={p.id}>{p.name}</div>)}
    </div>
  )
}

 

App Router에서 쿼리와 파라미터 처리 방식

Page Router에서 쿼리, 파라미터 값을 서버 데이터로 받아오기 위해서는 getServerSideProps를 사용하여야 했고, 그걸 페이지 단위로 내려주어 사용했었다. App Router는 컴포넌트에서 직접 받을 수 있기 때문에 구조가 더 단순하고 유연해졌다.

 

쿼리 

export default function MyPage({ searchParams }: { searchParams: { tab?: string } }) {
  const tab = searchParams.tab || 'orders'
  return <div>{tab === 'orders' ? '주문 탭' : '프로필 탭'}</div>
}

 

파라미터

export default function MyPage({ params }: { params: { userId: string } }) {
  return <div>{params.userId}님의 마이페이지</div>
}

 

 

서버 컴포넌트 vs 클라이언트 컴포넌트

app router 에서 'use client'를 써야하는 이유는 서버 컴포넌트와 클라이언트 컴포넌트를 구분하였기 때문이다.

 

Page router에서는 클라이언트 컴포넌트와 서버 컴포넌트의 구분이 없었다. 모든 컴포넌트가 클라이언트 컴포넌트로 동작했었다.

- 서버에서 데이터 로딩은 getServerSideProps() 또는 getStaticProps()로 해결.

- 데이터가 서버에서 전달되니 후 클라이언트에서 렌더링이 시작되었다.

 

App Route방식은 서버 컴포넌트와 클라이언트 컴포넌트를 구분해서 사용하여야 한다.

- 클라이언트 컴포넌트는 'use client' 지시어를 상단에 명시적으로 선언해야한다.

- 서버 컴포넌트인 pages.tsx / layout.tsx 등에는 클라이언트에서 사용하는 useEffect, useState, window 를 사용할 수 없다.

 

 

이러한 차이가 필요한 이유는 성능 최적화가 용이하다는 점이다. 서버 컴포넌트는 서버에서 렌더링되므로, 클라이언트 측에 불필요한 JavaScript 코드가 전달되지 않게 되어 성능이 최적화된다. 또한, 서버 컴포넌트와 클라이언트 컴포넌트를 명확히 구분함으로써 각 컴포넌트의 역할이 분명해지고, 코드의 유지보수성이 높아지는 장점이 있다.

 

반응형