App Router
App Router 기존 Page Router와 다른 부분
변경되거나 추가되는 부분
- 페이지 라우팅 설정 방식 변경
- 레이아웃 설정 방식 변경
- 데이터 페칭 방식 변경
- React 18 신규 기능 활용 (Streaming, Suspense 등)
크게 변경되지 않는 사항
- 네비게이팅 방식
- 프리페칭 동작
- 사전 렌더링 개념 (SSR / SSG / ISR)
App Router의 기본 구조
App Router는 app 폴더를 기준으로 동작한다.
less
📁 app
├── page.tsx // ~/
├── 📁 search
│ └── page.tsx // ~/search
└── 📁 book
├── page.tsx // ~/book
└── 📁 [id]
└── page.tsx // ~/book/1app폴더 구조를 기반으로 URL이 자동 매핑된다.- 하지만
page이름의 파일만 실제 페이지로 인식된다. - 폴더는 경로 역할,
page.tsx는 화면 역할을 담당한다.
동적 경로 (Dynamic Route)
URL 파라미터를 사용하는 동적 경로는 대괄호 문법 [param]을 사용한다.
less
📁 book
└── 📁 [id]
└── page.tsx- 위 구조는
/book/1,/book/2,/book/3url에 대응된다.
파라미터 접근 방식
App Router에서 페이지 컴포넌트에 전달되는 props를 콘솔로 확인해보면 다음과 같은 형태를 볼 수 있다.
tsx
export default async function Page({
searchParams,
}: {
searchParams: Promise<{ q: string }>;
}) {
console.log({ searchParams });
const { q } = await searchParams;
return <div>Search 페이지 : {q}</div>;
}bash
{
params: Promise { ... },
searchParams: Promise { ... }
}🔍 console.log가 복잡하게 보이는 이유
bash
{
params: Promise {
{},
[Symbol(async_id_symbol)]: 96763,
[Symbol(trigger_async_id_symbol)]: 96762,
[Symbol(kResourceStore)]: {
isStaticGeneration: false,
page: '/search/page',
route: '/search',
incrementalCache: [IncrementalCache],
cacheLifeProfiles: [Object],
isRevalidate: false,
isBuildTimePrerendering: undefined,
hasReadableErrorStacks: undefined,
fetchCache: undefined,
isOnDemandRevalidate: false,
isDraftMode: false,
isPrefetchRequest: false,
buildId: 'development',
reactLoadableManifest: {},
assetPrefix: '',
afterContext: [AfterContext],
cacheComponentsEnabled: false,
dev: true,
previouslyRevalidatedTags: [],
refreshTagsByCacheKind: [Map],
runInCleanSnapshot: [Function],
shouldTrackFetchMetrics: true,
fetchMetrics: []
},
[Symbol(kResourceStore)]: {
type: 'request',
phase: 'render',
implicitTags: [Object],
url: [Object],
rootParams: {},
headers: [Getter],
cookies: [Getter/Setter],
mutableCookies: [Getter],
userspaceMutableCookies: [Getter],
draftMode: [Getter],
renderResumeDataCache: null,
isHmrRefresh: true,
serverComponentsHmrCache: [LRUCache],
devFallbackParams: null
},
[Symbol(kResourceStore)]: undefined,
[Symbol(kResourceStore)]: undefined,
[Symbol(kResourceStore)]: undefined
},
searchParams: Promise {
<pending>,
q: [Getter/Setter],
[Symbol(async_id_symbol)]: 96686,
[Symbol(trigger_async_id_symbol)]: 96680,
[Symbol(kResourceStore)]: {
isStaticGeneration: false,
page: '/search/page',
route: '/search',
incrementalCache: [IncrementalCache],
cacheLifeProfiles: [Object],
isRevalidate: false,
isBuildTimePrerendering: undefined,
hasReadableErrorStacks: undefined,
fetchCache: undefined,
isOnDemandRevalidate: false,
isDraftMode: false,
isPrefetchRequest: false,
buildId: 'development',
reactLoadableManifest: {},
assetPrefix: '',
afterContext: [AfterContext],
cacheComponentsEnabled: false,
dev: true,
previouslyRevalidatedTags: [],
refreshTagsByCacheKind: [Map],
runInCleanSnapshot: [Function],
shouldTrackFetchMetrics: true,
fetchMetrics: []
},
[Symbol(kResourceStore)]: {
type: 'request',
phase: 'render',
implicitTags: [Object],
url: [Object],
rootParams: {},
headers: [Getter],
cookies: [Getter/Setter],
mutableCookies: [Getter],
userspaceMutableCookies: [Getter],
draftMode: [Getter],
renderResumeDataCache: null,
isHmrRefresh: true,
serverComponentsHmrCache: [LRUCache],
devFallbackParams: null
},
[Symbol(kResourceStore)]: undefined,
[Symbol(kResourceStore)]: undefined,
[Symbol(kResourceStore)]: undefined
}
}실제로 출력된 객체를 보면 위와 같이 정체를 알기 어려운 값들이 포함되어 있다.
이는 개발자가 사용하는 데이터가 아니라, Next.js가 서버 렌더링 과정에서 사용하는 내부 실행 컨텍스트 정보이다.
즉, 현재 요청 상태 / 캐싱 정보 / 렌더링 모드 / 개발 환경 설정 등이 포함된 메타데이터라고 볼 수 있다.
왜 Promise 형태로 전달될까?
App Router에서는 페이지 렌더링 과정에서 필요한 값들이 비동기적으로 처리되도록 설계되어 있다. 따라서 params, searchParams는 즉시 사용 가능한 객체가 아니라 Promise 형태로 전달될 수 있다.
tsx
const { id } = await params;
const { q } = await searchParams;디버깅 시 권장 방식
Promise 객체 자체를 확인하기보다는 resolve된 값만 확인하는 것이 훨씬 직관적이다.
tsx
const resolvedParams = await params;
console.log(resolvedParams);
const resolvedSearchParams = await searchParams;
console.log(resolvedSearchParams);정리
- App Router의 params, searchParams는 Promise 형태로 전달될 수 있다.
- 콘솔에 보이는 복잡한 Symbol 값들은 Next.js 내부 구현이다.
- 실제 사용 시에는 await 후 값만 사용하는 것이 일반적인 패턴이다.
Layout 적용 - layout.tsx
less
📁 app
├── page.tsx // ~/
└── 📁 search
├── page.tsx // ~/search
├── layout.tsx
└── 📁 setting
├── page.tsx
└── layout.tsx/search 페이지 하위에 layout.tsx를 넣으면 레이아웃 안에 칠드런으로 page.tsx 컴포넌트가 렌더링되게 된다. 해당 경로의 레이아웃으로 자동 설정된다.
/search경로로 시작하는 모든 페이지의 레이아웃으로 적용된다./search/setting도/search로 시작하는 경로이므로 레아아웃 자동 적용- 만약 안에 레이아웃을 또 넣으면 중첩된다.
Route Group
Route Group은 URL 경로에는 영향을 주지 않는 폴더 구조다.
less
📁 app
└── 📁 (with-searchbar)소괄호 ( )로 감싼 폴더는 실제 경로에 포함되지 않는다.- 화면 구조를 정리하거나 레이아웃을 그룹화하기 위한 용도로 사용된다.
즉, 파일 구조 정리용 폴더 ≠ 라우팅 경로
less
📁 app
└── 📁 (auth)
├── login
│ └── page.tsx → /login
└── register
└── page.tsx → /register(auth)폴더는 URL에 나타나지 않는다.- 하지만 내부 페이지들은 정상적으로 라우팅된다.