이번 글에서는 Next.js의 Page Router 방식에 대해 공부하면서 정리하려고합니다.
"근데 이제는 App Router 쓰는 거 아니야?"라고 생각하실 수도 있습니다.
👍 맞습니다. 최신 Next.js에서도 App Router가 권장되는것을 확인할수있습니다.
하지만 여전히 많은 기업과 팀에서는 Page Router를 기반으로 한 기존 서비스를 운영하고 있고,
이 구조에 대한 이해는 필수적이라고 생각을 합니다.
또한 Page Router를 제대로 이해하면, App Router의 구조와 장점이 왜 등장했는지도 더 명확하게 이해할 수 있다고 판단하였습니다.
Next.js는 두 가지 라우팅 방식이 존재합니다.
1. Page Router – 기존 방식인 (pages 디렉토리 기반)
2. App Router – 최신 방식인 (app 디렉토리 기반, Next.js 13 이상)
Next.js의 Page Router는 React Router처럼 파일 기반의 라우팅 시스템을 제공합니다.
즉, pages 폴더 안의 파일 이름만으로 자동으로 라우팅이 처리됩니다
pages/ 폴더 아래에 있는 파일의 이름이 곧 라우터 경로가 됩니다.
pages에 파일로만 라우터 맵핑
pages
├── index.tsx
├── about.tsx
└── profile.tsx
정리해보면 이렇게 router처리가 된다고 알수있습니다.
하지만 컴포넌트 방식을 사용하다보면 사실 pages에 파일로 있는것은 말이 안되긴하죠??
컴포넌트 분리를 위해 폴더 단위로도 당연히 라우터를 구성할 수 있습니다.
pages에 폴더 라우터 맵핑
pages
├── index.tsx
├── about
│ └── index.tsx
└── profile
└── index.tsx
Velog 사이트 기준으로 예시를 들어보겠습니다 :
Ex) /유저ID/게시글제목 형태의 URL이 필요한 경우, 동적 라우팅을 사용할 수 있습니다.
Next.js에서는 [] 대괄호를 사용해 URL 파라미터를 받습니다.
pages에 파일로만 라우터 맵핑
pages/
├── [userId]
└── [title].tsx
그리고 그 파라미터는 아래와 같이 컴포넌트에서 활용이 가능합니다.
//pages/[userId]/[title].tsx
import { useRouter } from 'next/router';
export default function Test() {
const { userId, title } = useRouter().query;
return (
<div>
<h1>제목 : {title}, 유저 : {userId}</h1>
</div>
);
}
pages/[userId]/[title].tsx
위에는 이렇게 되어있습니다. 그래서 /userId자리/title자리 이렇게 처리 가능했습니다.
근데 /userId자리/title1/title2/title3 이렇게 들어온다면 가능할까요?
그때는 catch all segments를 사용해야합니다.
❌ pages/[userId]/[title].tsx
✅ pages/[userId]/[...title].tsx
이렇게 변경해줘야합니다.
이렇게 하면 title을 useRouter로 출력해보면
[title1,title2,title3 ...] 이와 같이 배열 형태로 저장이 되어있습니다.
이건 무엇이냐?
pages/[userId]/[...title].tsx 이렇게 정의하면
pages/userId 이렇게 title자리를 주지 않았을때 404가 나오게 됩니다.
방법
1. pages/[userId]/index.tsx 만든다. 그러면 이 index가 나오게 됩니다. (근데 나는 title.tsx안에 로직을 쓰고싶은데?
2. pages/[userId]/[[...title].tsx] 이것이 optional catch all segment입니다. title의 값이 있을때도, 없을때도 다 처리 가능한 옵션입니다.
처음 npm-create-next-app을 통해 next를 생성하였습니다.
이번은 page router에 대해서 학습하기 위해 app router대신 page router를 선택해서 생성하였습니다.
page router 프로젝트 기본세팅 구조입니다.

_app.tsx는 모든 페이지의 공통 부모 컴포넌트입니다.
React의 App.tsx와 동일한 역할을 수행하며, 전역 상태 관리나 레이아웃 적용 시 주로 사용한다고 생각하시면 됩니다.
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
App의 두개의 props가 존재합니다. Component와 pageProps
개발자 도구에서 component를 확인해보면 이렇게 App/Test.tsx가 온것을 확인할수 있습니다.

document 파일은 아마 내부 코드를 보시면 대략 아실수있습니다.
이 프로젝트에 공통적으로 적용되는 HTML파일입니다.
index.tsx라고 생각해도 될거같습니다.
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<body className="antialiased">
<Main />
<NextScript />
</body>
</Html>
);
}
next 프로젝트의 설정을 담당하는 파일입니다.
reactStrictMode는 컴포넌트 랜더링을 두번 시키기 때문에 지금 실습과정에서 필요없어 false로 진행합니다.
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
reactStrictMode: false,
};
export default nextConfig;
왜 Next에서는 a태그 대신 Link태그를 사용할까요?
서비스 마운트시 서버 사이드 랜더링을 통해 생성된 HTML 받아와 화면에 보여주게 됩니다.
이후 Link를 통해 다른 페이지 이동을 할 때는 클라이언트 사이드 랜더링 방식을 진행하게 됩니다.
즉, 브라우저는 전체 페이지를 다시 로드하지 않고, 변경된 부분만 동적으로 렌더링합니다.
페이지 이동 시간이 빨라 UX측면에서 좋습니다.
url에 직접 접근하는 방식을 사용하고있습니다. 그래서 들어갈때마다 서버로 부터 html파일을 다시 받고, 다시 보여주는 형식을 진행하고있습니다.
그래서 a보단 Link를 통해 활용하는것이 Next.js의 장점을 살릴수있는 방법입니다.
하지만 외부 서비스로 네이게이트를 진행할 시 a와 Link는 동일합니다.