마이그레이션: App Router
마이그레이션: App Router | Next.js
섹션 제목: “마이그레이션: App Router | Next.js”출처 URL: https://nextjs.org/docs/app/guides/migrating/app-router-migration
Pages에서 App Router로 마이그레이션하는 방법
섹션 제목: “Pages에서 App Router로 마이그레이션하는 방법”마지막 업데이트 2026년 2월 20일
이 가이드를 통해 다음을 수행할 수 있습니다:
- Next.js 애플리케이션을 12버전에서 13버전으로 업데이트하기
pages와app디렉터리 모두에서 동작하는 기능 업그레이드- 기존 애플리케이션을
pages에서app으로 점진적으로 마이그레이션하기](https://nextjs.org/docs/app/guides/migrating/app-router-migration#migrating-from-pages-to-app)
업그레이드
섹션 제목: “업그레이드”Node.js 버전
섹션 제목: “Node.js 버전”최소 Node.js 버전은 이제 v18.17입니다. 자세한 내용은 Node.js 문서를 참고하세요.
Next.js 버전
섹션 제목: “Next.js 버전”Next.js 13버전으로 업데이트하려면 선호하는 패키지 매니저로 다음 명령을 실행하세요:
pnpmnpmyarnbun
터미널
pnpm add next@latest react@latest react-dom@latestESLint 버전
섹션 제목: “ESLint 버전”ESLint를 사용 중이라면 ESLint 버전을 업그레이드해야 합니다:
pnpmnpmyarnbun
터미널
pnpm add -D eslint-config-next@latest알아두면 좋아요 : ESLint 변경 사항을 적용하려면 VS Code의 ESLint 서버를 재시작해야 할 수 있습니다. 명령 팔레트(
cmd+shift+pon Mac;ctrl+shift+pon Windows)를 열어ESLint: Restart ESLint Server를 검색하세요.
다음 단계
섹션 제목: “다음 단계”업데이트를 마친 후 다음 섹션에서 후속 단계를 확인하세요:
- 새 기능 업그레이드: 개선된 Image 및 Link 컴포넌트 등 새 기능으로 업그레이드하는 방법을 안내합니다.
pages디렉터리에서app디렉터리로 마이그레이션:pages에서app으로 점진적으로 마이그레이션하는 단계별 가이드입니다.
새 기능 업그레이드
섹션 제목: “새 기능 업그레이드”Next.js 13은 새로운 기능과 컨벤션을 갖춘 App Router를 도입했습니다. 새 Router는 app 디렉터리에서 사용할 수 있으며 pages 디렉터리와 공존합니다.
Next.js 13으로 업그레이드한다고 해서 반드시 App Router를 사용해야 하는 것은 아닙니다. 업데이트된 Image 컴포넌트, Link 컴포넌트, Script 컴포넌트, Font 최적화 등 양쪽 디렉터리에서 작동하는 새 기능과 함께 pages를 계속 사용할 수 있습니다.
<Image/> 컴포넌트
섹션 제목: “<Image/> 컴포넌트”Next.js 12에서는 임시 임포트 next/future/image를 통해 Image 컴포넌트의 개선 사항을 도입했습니다. 여기에는 클라이언트 측 JavaScript 감소, 이미지를 확장/스타일링하는 더 간단한 방법, 더 나은 접근성, 기본 브라우저 지연 로딩이 포함됩니다.
13버전에서는 이 새로운 동작이 next/image의 기본값이 되었습니다.
새 Image 컴포넌트로 마이그레이션을 돕는 두 가지 코드모드가 있습니다:
next-image-to-legacy-image코드모드:next/image임포트를next/legacy/image로 안전하게 자동 변경하여 기존 컴포넌트의 동작을 유지합니다.next-image-experimental코드모드: 인라인 스타일을 위험하게 추가하고 사용되지 않는 props를 제거합니다. 기존 컴포넌트의 동작을 새로운 기본값에 맞게 변경합니다. 이 코드모드를 사용하려면 먼저next-image-to-legacy-image코드모드를 실행해야 합니다.
<Link> 컴포넌트
섹션 제목: “<Link> 컴포넌트”<Link> 컴포넌트는 더 이상 자식으로 <a> 태그를 수동으로 추가할 필요가 없습니다. 이 동작은 12.2버전에서 실험 옵션으로 추가되었으며 이제 기본 동작입니다. Next.js 13에서 <Link>는 항상 <a>를 렌더링하며, 기본 태그로 props를 전달할 수 있습니다.
예시:
import Link from 'next/link'
// Next.js 12: `<a>`가 중첩되지 않으면 제외됩니다. <Link href="/about"> <a>About</a> </Link>
// Next.js 13: `<Link>`는 내부적으로 항상 `<a>`를 렌더링합니다. <Link href="/about"> About </Link>링크를 Next.js 13 방식으로 업그레이드하려면 new-link 코드모드를 사용할 수 있습니다.
<Script> 컴포넌트
섹션 제목: “<Script> 컴포넌트”next/script의 동작이 pages와 app 모두를 지원하도록 업데이트되었지만 원활한 마이그레이션을 위해 몇 가지 변경이 필요합니다:
- 이전에
_document.js에 포함했던beforeInteractive스크립트를 루트 레이아웃 파일(app/layout.tsx)로 이동하세요. - 실험적
worker전략은 아직app에서 작동하지 않으므로 이 전략을 사용한 스크립트는 제거하거나 다른 전략(예:lazyOnload)으로 수정해야 합니다. onLoad,onReady,onError핸들러는 Server 컴포넌트에서 동작하지 않으므로 Client Component로 이동하거나 제거해야 합니다.
폰트 최적화
섹션 제목: “폰트 최적화”이전에는 Next.js가 폰트 CSS 인라인을 통해 폰트를 최적화했습니다. 13버전은 next/font 모듈을 도입하여 뛰어난 성능과 개인정보 보호를 지키면서 폰트 로딩 경험을 커스터마이징할 수 있게 합니다. next/font는 pages와 app 디렉터리 모두에서 지원됩니다.
CSS 인라인은 pages에서 계속 동작하지만 app에서는 동작하지 않습니다. 대신 next/font를 사용해야 합니다.
next/font 사용 방법은 Font Optimization 페이지에서 확인하세요.
pages에서 app으로 마이그레이션
섹션 제목: “pages에서 app으로 마이그레이션”🎥 시청: App Router를 점진적으로 도입하는 방법 → YouTube (16 minutes).
App Router로 이동하는 것은 Server Components, Suspense 등 Next.js가 기반으로 하는 React 기능을 처음 사용하는 경험이 될 수 있습니다. 스페셜 파일, 레이아웃 같은 새 Next.js 기능과 결합되면 마이그레이션은 새로운 개념, 사고 모델, 동작 변화 학습이 필요합니다.
이 업데이트에 따른 복합 복잡도를 줄이기 위해 마이그레이션을 작은 단계로 나누기를 권장합니다. app 디렉터리는 의도적으로 pages 디렉터리와 동시에 작동하도록 설계되어 페이지 단위의 점진적 마이그레이션이 가능합니다.
app디렉터리는 중첩 라우트와 레이아웃을 지원합니다. 자세히 알아보기.- 중첩 폴더를 사용하여 라우트를 정의하고
page.js특별 파일을 통해 라우트 세그먼트를 공개합니다. 자세히 알아보기. - 각 라우트 세그먼트의 UI는 특별 파일 컨벤션을 사용해 생성합니다. 가장 일반적인 특별 파일은
page.js와layout.js입니다.page.js는 라우트에 고유한 UI를 정의합니다.layout.js는 여러 라우트에서 공유되는 UI를 정의합니다.- 특별 파일에는
.js,.jsx,.tsx확장자를 사용할 수 있습니다.
- 컴포넌트, 스타일, 테스트 등 다른 파일을
app디렉터리 안에 함께 배치할 수 있습니다. 자세히 알아보기. getServerSideProps,getStaticProps같은 데이터 패칭 함수는app내부의 새 API로 대체되었습니다.getStaticPaths는generateStaticParams로 대체되었습니다.pages/_app.js와pages/_document.js는 단일app/layout.js루트 레이아웃으로 대체됩니다. 자세히 알아보기.pages/_error.js는 더 세분화된error.js특별 파일로 대체되었습니다. 자세히 알아보기.pages/404.js는not-found.js파일로 대체되었습니다.pages/api/*API 라우트는route.js(Route Handler) 특별 파일로 대체되었습니다.
1단계: app 디렉터리 생성
섹션 제목: “1단계: app 디렉터리 생성”최신 Next.js 버전으로 업데이트하세요(13.4 이상 필요):
pnpmnpmyarnbun
터미널
pnpm add next@latest그런 다음 프로젝트 루트(또는 src/ 디렉터리)에 새로운 app 디렉터리를 만드세요.
2단계: 루트 레이아웃 생성
섹션 제목: “2단계: 루트 레이아웃 생성”app 디렉터리 안에 새 app/layout.tsx 파일을 만드세요. 이는 app 내부 모든 라우트에 적용되는 루트 레이아웃입니다.
app/layout.tsx
JavaScriptTypeScript
export default function RootLayout({ // Layouts must accept a children prop. // This will be populated with nested layouts or pages children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}</body> </html> ) }app디렉터리는 반드시 루트 레이아웃을 포함해야 합니다.- Next.js가
<html>,<body>태그를 자동으로 생성하지 않으므로 루트 레이아웃에서 정의해야 합니다. - 루트 레이아웃은
pages/_app.tsx와pages/_document.tsx파일을 대체합니다. - 레이아웃 파일에는
.js,.jsx,.tsx확장자를 사용할 수 있습니다.
<head> HTML 요소를 관리하려면 내장 SEO 지원을 사용할 수 있습니다:
app/layout.tsx
JavaScriptTypeScript
import type { Metadata } from 'next'
export const metadata: Metadata = { title: 'Home', description: 'Welcome to Next.js', }_document.js 및 _app.js 마이그레이션
섹션 제목: “_document.js 및 _app.js 마이그레이션”기존 _app 또는 _document 파일이 있다면, 그 내용(예: 전역 스타일)을 루트 레이아웃(app/layout.tsx)으로 복사할 수 있습니다. app/layout.tsx의 스타일은 pages/*에 적용되지 않습니다. pages/* 라우트가 깨지는 것을 방지하려면 마이그레이션 중에는 _app/_document를 유지해야 합니다. 완전히 마이그레이션한 후에야 안전하게 삭제할 수 있습니다.
React Context 공급자를 사용 중이라면, 이를 Client Component로 옮겨야 합니다.
getLayout() 패턴을 레이아웃으로 마이그레이션(선택 사항)
섹션 제목: “getLayout() 패턴을 레이아웃으로 마이그레이션(선택 사항)”Next.js는 pages 디렉터리에서 페이지별 레이아웃을 구현하기 위해 Page 컴포넌트에 프로퍼티를 추가하는 방식을 권장했습니다. 이 패턴은 app 디렉터리의 중첩 레이아웃에 대한 기본 지원으로 대체할 수 있습니다.
전/후 예시를 확인하세요.
이전
components/DashboardLayout.js
export default function DashboardLayout({ children }) { return ( <div> <h2>My Dashboard</h2> {children} </div> ) }pages/dashboard/index.js
import DashboardLayout from '../components/DashboardLayout'
export default function Page() { return <p>My Page</p> }
Page.getLayout = function getLayout(page) { return <DashboardLayout>{page}</DashboardLayout> }이후
pages/dashboard/index.js에서Page.getLayout속성을 제거하고,pages를app디렉터리로 마이그레이션하는 단계를 따르세요.
app/dashboard/page.js
export default function Page() { return <p>My Page</p> }DashboardLayout의 내용을 새 Client Component로 옮겨pages디렉터리와 동일한 동작을 유지하세요.
app/dashboard/DashboardLayout.js
'use client' // this directive should be at top of the file, before any imports.
// This is a Client Component export default function DashboardLayout({ children }) { return ( <div> <h2>My Dashboard</h2> {children} </div> ) }DashboardLayout을app디렉터리 내부의 새layout.js파일로 가져오세요.
app/dashboard/layout.js
import DashboardLayout from './DashboardLayout'
// This is a Server Component export default function Layout({ children }) { return <DashboardLayout>{children}</DashboardLayout> }- 클라이언트로 전송되는 컴포넌트 JavaScript 양을 줄이기 위해,
DashboardLayout.js(Client Component)의 비대화형 부분을 점진적으로layout.js(Server Component)로 옮길 수 있습니다.
3단계: next/head 마이그레이션
섹션 제목: “3단계: next/head 마이그레이션”pages 디렉터리에서는 next/head React 컴포넌트를 사용해 title, meta 같은 <head> HTML 요소를 관리했습니다. app 디렉터리에서는 next/head가 새로운 내장 SEO 지원으로 대체됩니다.
이전:
pages/index.tsx
JavaScriptTypeScript
import Head from 'next/head'
export default function Page() { return ( <> <Head> <title>My page title</title> </Head> </> ) }이후:
app/page.tsx
JavaScriptTypeScript
import type { Metadata } from 'next'
export const metadata: Metadata = { title: 'My Page Title', }
export default function Page() { return '...' }4단계: 페이지 마이그레이션
섹션 제목: “4단계: 페이지 마이그레이션”app디렉터리의 페이지는 기본적으로 Server Component입니다. 이는 페이지가 Client Component인pages디렉터리와 다릅니다.app에서는 데이터 패칭 방식이 변경되었습니다.getServerSideProps,getStaticProps,getInitialProps가 더 간결한 API로 대체되었습니다.app디렉터리는 중첩 폴더로 라우트를 정의하고, 공개 라우트 세그먼트를 만들기 위해 특수한page.js파일을 사용합니다.pages디렉터리|app디렉터리| 라우트 ---|---|---index.js|page.js|/about.js|about/page.js|/aboutblog/[slug].js|blog/[slug]/page.js|/blog/post-1
페이지 마이그레이션을 두 가지 주요 단계로 나누는 것을 권장합니다.
- 1단계: 기본으로 export되는 Page Component를 새로운 Client Component로 옮깁니다.
- 2단계: 새 Client Component를
app디렉터리의 새로운page.js파일로 가져옵니다.
알아두면 좋아요: 이 경로가
pages디렉터리와 가장 유사한 동작을 제공하므로 마이그레이션이 가장 쉽습니다.
1단계: 새 Client Component 만들기
app디렉터리 안에(app/home-page.tsx등) Client Component를 export하는 별도 파일을 만듭니다. Client Component를 정의하려면 파일 최상단(모든 import 이전)에'use client'지시문을 추가하세요.- Pages Router와 마찬가지로, 초기 페이지 로드 시 Client Component를 정적 HTML로 사전 렌더링하는 최적화 단계가 있습니다.
pages/index.js에서 기본 export 페이지 컴포넌트를app/home-page.tsx로 옮깁니다.
app/home-page.tsx
JavaScriptTypeScript
'use client'
// This is a Client Component (same as components in the `pages` directory) // It receives data as props, has access to state and effects, and is // prerendered on the server during the initial page load. export default function HomePage({ recentPosts }) { return ( <div> {recentPosts.map((post) => ( <div key={post.id}>{post.title}</div> ))} </div> ) }2단계: 새 페이지 만들기
-
app디렉터리 안에 새app/page.tsx파일을 만듭니다. 이는 기본적으로 Server Component입니다. -
home-page.tsxClient Component를 페이지에 import합니다. -
pages/index.js에서 데이터를 가져왔다면, 새로운 데이터 패칭 API를 사용해 서버 컴포넌트로 직접 데이터 패칭 로직을 옮기세요. 자세한 내용은 데이터 패칭 업그레이드 가이드를 참고하세요.
app/page.tsx
JavaScriptTypeScript
// Import your Client Component import HomePage from './home-page'
async function getPosts() { const res = await fetch('https://...') const posts = await res.json() return posts }
export default async function Page() { // Fetch data directly in a Server Component const recentPosts = await getPosts() // Forward fetched data to your Client Component return <HomePage recentPosts={recentPosts} /> }-
이전 페이지에서
useRouter를 사용했다면, 새로운 라우팅 훅으로 업데이트해야 합니다. 더 알아보기. -
개발 서버를 시작하고
http://localhost:3000에 접속하세요. 기존 인덱스 라우트가 이제 app 디렉터리를 통해 제공되는 것을 확인할 수 있습니다.
5단계: 라우팅 훅 마이그레이션
섹션 제목: “5단계: 라우팅 훅 마이그레이션”app 디렉터리의 새로운 동작을 지원하기 위해 새로운 라우터가 추가되었습니다.
app에서는 next/navigation에서 import하는 세 가지 훅, 즉 useRouter(), usePathname(), useSearchParams()를 사용해야 합니다.
- 새로운
useRouter훅은next/navigation에서 import되며,next/router에서 import하는pages용useRouter와는 동작이 다릅니다.next/router에서 import하는useRouter훅은app디렉터리에서 지원되지 않지만pages디렉터리에서는 계속 사용할 수 있습니다.
- 새로운
useRouter는pathname문자열을 반환하지 않습니다. 대신 별도의usePathname훅을 사용하세요. - 새로운
useRouter는query객체를 반환하지 않습니다. 검색 매개변수와 동적 라우트 매개변수가 분리되었으므로useSearchParams와useParams훅을 사용하세요. - 페이지 변경 사항을 수신하려면
useSearchParams와usePathname을 함께 사용할 수 있습니다. 자세한 내용은 Router Events 섹션을 참고하세요. - 이러한 새로운 훅은 Client Component에서만 지원됩니다. Server Component에서는 사용할 수 없습니다.
app/example-client-component.tsx
JavaScriptTypeScript
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() { const router = useRouter() const pathname = usePathname() const searchParams = useSearchParams()
// ... }또한 새로운 useRouter 훅에는 다음과 같은 변경 사항이 있습니다.
fallback이 대체되었기 때문에isFallback이 제거되었습니다.locale,locales,defaultLocales,domainLocales값은app디렉터리에서 더 이상 내장 i18n Next.js 기능이 필요하지 않으므로 제거되었습니다. i18n에 대해 더 알아보기.basePath가 제거되었습니다. 대체 기능은useRouter의 일부가 아니며 아직 구현되지 않았습니다.as개념이 새로운 라우터에서 제거되었으므로asPath도 사라졌습니다.- 더 이상 필요하지 않아
isReady가 제거되었습니다. 정적 렌더링 중에useSearchParams()훅을 사용하는 컴포넌트는 프리렌더링 단계를 건너뛰고 런타임에 클라이언트에서 렌더링됩니다. route가 제거되었습니다. 대안으로usePathname또는useSelectedLayoutSegments()를 사용하세요.
pages와 app 간 컴포넌트 공유
섹션 제목: “pages와 app 간 컴포넌트 공유”컴포넌트를 pages 라우터와 app 라우터 사이에서 호환되게 유지하려면 next/compat/router의 useRouter 훅을 참고하세요. 이는 pages 디렉터리의 useRouter 훅이지만 라우터 간에 컴포넌트를 공유할 때 사용하도록 만들어졌습니다. app 라우터에서만 사용할 준비가 되면 next/navigation의 새로운 useRouter로 업데이트하세요.
6단계: 데이터 패칭 방식 마이그레이션
섹션 제목: “6단계: 데이터 패칭 방식 마이그레이션”pages 디렉터리는 페이지용 데이터를 가져오기 위해 getServerSideProps와 getStaticProps를 사용합니다. app 디렉터리에서는 이러한 이전 데이터 패칭 함수가 fetch()와 async React 서버 컴포넌트를 기반으로 하는 더 간단한 API로 대체됩니다.
app/page.tsx
JavaScriptTypeScript
export default async function Page() { // This request should be cached until manually invalidated. // Similar to `getStaticProps`. // `force-cache` is the default and can be omitted. const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// This request should be refetched on every request. // Similar to `getServerSideProps`. const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// This request should be cached with a lifetime of 10 seconds. // Similar to `getStaticProps` with the `revalidate` option. const revalidatedData = await fetch(`https://...`, { next: { revalidate: 10 }, })
return <div>...</div> }서버 사이드 렌더링(getServerSideProps)
섹션 제목: “서버 사이드 렌더링(getServerSideProps)”pages 디렉터리에서는 getServerSideProps를 사용해 서버에서 데이터를 가져온 뒤 해당 파일의 기본 내보낸 React 컴포넌트로 props를 전달합니다. 페이지의 초기 HTML은 서버에서 프리렌더링되고, 이후 브라우저에서 페이지를 “하이드레이션”하여 인터랙티브하게 만듭니다.
pages/dashboard.js
// `pages` directory
export async function getServerSideProps() { const res = await fetch(`https://...`) const projects = await res.json()
return { props: { projects } } }
export default function Dashboard({ projects }) { return ( <ul> {projects.map((project) => ( <li key={project.id}>{project.name}</li> ))} </ul> ) }App Router에서는 서버 컴포넌트를 활용해 React 컴포넌트 내부에 데이터 패칭을 공존시킬 수 있습니다. 이렇게 하면 서버에서 렌더링된 HTML은 유지하면서 클라이언트로 전송되는 JavaScript 양을 줄일 수 있습니다.
cache 옵션을 no-store로 설정하면 가져온 데이터를 캐시하지 않아야 함을 나타낼 수 있습니다. 이는 pages 디렉터리의 getServerSideProps와 동일한 동작입니다.
app/dashboard/page.tsx
JavaScriptTypeScript
// `app` directory
// This function can be named anything async function getProjects() { const res = await fetch(`https://...`, { cache: 'no-store' }) const projects = await res.json()
return projects }
export default async function Dashboard() { const projects = await getProjects()
return ( <ul> {projects.map((project) => ( <li key={project.id}>{project.name}</li> ))} </ul> ) }요청 객체에 접근하기
섹션 제목: “요청 객체에 접근하기”pages 디렉터리에서는 Node.js HTTP API를 기반으로 요청 정보를 가져올 수 있습니다.
예를 들어 getServerSideProps에서 req 객체를 받아 요청의 쿠키와 헤더를 조회할 수 있습니다.
pages/index.js
// `pages` directory
export async function getServerSideProps({ req, query }) { const authHeader = req.getHeaders()['authorization']; const theme = req.cookies['theme'];
return { props: { ... }} }
export default function Page(props) { return ... }app 디렉터리는 요청 데이터를 읽어오기 위한 새로운 읽기 전용 함수를 제공합니다:
headers: Web Headers API 기반이며, 서버 컴포넌트 내부에서 요청 헤더를 가져올 때 사용할 수 있습니다.cookies: Web Cookies API 기반이며, 서버 컴포넌트 내부에서 쿠키를 가져올 때 사용할 수 있습니다.
app/page.tsx
JavaScriptTypeScript
// `app` directory import { cookies, headers } from 'next/headers'
async function getData() { const authHeader = (await headers()).get('authorization')
return '...' }
export default async function Page() { // You can use `cookies` or `headers` inside Server Components // directly or in your data fetching function const theme = (await cookies()).get('theme') const data = await getData() return '...' }정적 사이트 생성(getStaticProps)
섹션 제목: “정적 사이트 생성(getStaticProps)”pages 디렉터리에서는 getStaticProps 함수가 빌드 타임에 페이지를 프리렌더링하는 데 사용됩니다. 이 함수는 외부 API나 데이터베이스에서 데이터를 가져와 빌드 중에 전체 페이지로 전달할 수 있습니다.
pages/index.js
// `pages` directory
export async function getStaticProps() { const res = await fetch(`https://...`) const projects = await res.json()
return { props: { projects } } }
export default function Index({ projects }) { return projects.map((project) => <div>{project.name}</div>) }app 디렉터리에서는 fetch()를 사용한 데이터 패칭이 기본적으로 cache: 'force-cache'로 동작하며, 수동으로 무효화할 때까지 요청 데이터를 캐시합니다. 이는 pages 디렉터리의 getStaticProps와 유사합니다.
app/page.js
// `app` directory
// This function can be named anything async function getProjects() { const res = await fetch(`https://...`) const projects = await res.json()
return projects }
export default async function Index() { const projects = await getProjects()
return projects.map((project) => <div>{project.name}</div>) }동적 경로(getStaticPaths)
섹션 제목: “동적 경로(getStaticPaths)”pages 디렉터리에서는 getStaticPaths 함수로 빌드 타임에 프리렌더링해야 할 동적 경로를 정의합니다.
pages/posts/[id].js
// `pages` directory import PostLayout from '@/components/post-layout'
export async function getStaticPaths() { return { paths: [{ params: { id: '1' } }, { params: { id: '2' } }], } }
export async function getStaticProps({ params }) { const res = await fetch(`https://.../posts/${params.id}`) const post = await res.json()
return { props: { post } } }
export default function Post({ post }) { return <PostLayout post={post} /> }app 디렉터리에서는 getStaticPaths가 generateStaticParams로 대체됩니다.
generateStaticParams는 getStaticPaths와 비슷하지만, 라우트 파라미터를 반환하는 API가 더 단순화되었으며 레이아웃 내부에서도 사용할 수 있습니다. generateStaticParams의 반환 형태는 중첩된 param 객체 배열이나 해석된 경로 문자열 대신 세그먼트 배열입니다.
app/posts/[id]/page.js
// `app` directory import PostLayout from '@/components/post-layout'
export async function generateStaticParams() { return [{ id: '1' }, { id: '2' }] }
async function getPost(params) { const res = await fetch(`https://.../posts/${(await params).id}`) const post = await res.json()
return post }
export default async function Post({ params }) { const post = await getPost(params)
return <PostLayout post={post} /> }app 디렉터리의 새로운 모델에서는 generateStaticParams라는 이름이 getStaticPaths보다 더 적합합니다. getStaticProps와 getServerSideProps가 더 이상 필요하지 않기 때문에 get 접두사는 더 설명적인 generate로 대체되며, 여러 동적 세그먼트를 포함하는 중첩 라우팅에 더 알맞은 Params 접미사가 Paths 대신 사용됩니다.
fallback 대체하기
섹션 제목: “fallback 대체하기”pages 디렉터리에서는 getStaticPaths가 반환하는 fallback 속성이 빌드 타임에 프리렌더링되지 않은 페이지의 동작을 정의합니다. 페이지가 생성되는 동안 대체 페이지를 보여주려면 true, 404 페이지를 보여주려면 false, 요청 시 페이지를 생성하려면 blocking으로 설정할 수 있습니다.
pages/posts/[id].js
// `pages` directory
export async function getStaticPaths() { return { paths: [], fallback: 'blocking' }; }
export async function getStaticProps({ params }) { ... }
export default function Post({ post }) { return ... }app 디렉터리에서는 config.dynamicParams 속성이 generateStaticParams 외부의 파라미터를 처리하는 방식을 제어합니다:
true: (기본값)generateStaticParams에 포함되지 않은 동적 세그먼트를 온디맨드로 생성합니다.false:generateStaticParams에 포함되지 않은 동적 세그먼트는 404를 반환합니다.
이는 pages 디렉터리의 getStaticPaths에서 사용하던 fallback: true | false | 'blocking' 옵션을 대체합니다. 스트리밍 환경에서는 'blocking'과 true의 차이가 미미하므로 dynamicParams에는 fallback: 'blocking' 옵션이 포함되지 않습니다.
app/posts/[id]/page.js
// `app` directory
export const dynamicParams = true;
export async function generateStaticParams() { return [...] }
async function getPost(params) { ... }
export default async function Post({ params }) { const post = await getPost(params);
return ... }dynamicParams를 true(기본값)로 설정하면 아직 생성되지 않은 라우트 세그먼트가 요청될 때 서버에서 렌더링하고 캐시합니다.
점진적 정적 재생성(revalidate가 있는 getStaticProps)
섹션 제목: “점진적 정적 재생성(revalidate가 있는 getStaticProps)”pages 디렉터리에서는 getStaticProps 함수로 일정 시간이 지나면 페이지를 자동으로 다시 생성하도록 revalidate 필드를 추가할 수 있습니다.
pages/index.js
// `pages` directory
export async function getStaticProps() { const res = await fetch(`https://.../posts`) const posts = await res.json()
return { props: { posts }, revalidate: 60, } }
export default function Index({ posts }) { return ( <Layout> <PostList posts={posts} /> </Layout> ) }app 디렉터리에서는 fetch()로 데이터를 가져올 때 revalidate를 사용할 수 있으며, 지정한 초 동안 요청이 캐시됩니다.
app/page.js
// `app` directory
async function getPosts() { const res = await fetch(`https://.../posts`, { next: { revalidate: 60 } }) const data = await res.json()
return data.posts }
export default async function PostList() { const posts = await getPosts()
return posts.map((post) => <div>{post.name}</div>) }API 라우트
섹션 제목: “API 라우트”API 라우트는 pages/api 디렉터리에서 아무 변경 없이 계속 동작하지만, app 디렉터리에서는 Route Handlers로 대체되었습니다.
Route Handler를 사용하면 Web Request 및 Response API를 활용해 특정 라우트의 사용자 지정 요청 처리기를 만들 수 있습니다.
app/api/route.ts
JavaScriptTypeScript
export async function GET(request: Request) {}알아 두면 좋아요 : 이전에 클라이언트에서 외부 API를 호출하기 위해 API 라우트를 사용했다면, 이제는 Server Components를 사용해 안전하게 데이터를 가져올 수 있습니다. 데이터 패칭에 대해 더 알아보세요.
단일 페이지 애플리케이션
섹션 제목: “단일 페이지 애플리케이션”동시에 단일 페이지 애플리케이션(SPA)에서 Next.js로 마이그레이션 중이라면, 문서를 참고해 자세히 알아보세요.
7단계: 스타일링
섹션 제목: “7단계: 스타일링”pages 디렉터리에서는 전역 스타일시트가 pages/_app.js에만 제한되지만, app 디렉터리에서는 이 제한이 해제되었습니다. 전역 스타일을 어떤 레이아웃, 페이지, 컴포넌트에도 추가할 수 있습니다.
Tailwind CSS
섹션 제목: “Tailwind CSS”Tailwind CSS를 사용 중이라면 tailwind.config.js 파일에 app 디렉터리를 추가해야 합니다.
tailwind.config.js
module.exports = { content: [ './app/**/*.{js,ts,jsx,tsx,mdx}', // <-- Add this line './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', ], }또한 전역 스타일을 app/layout.js 파일에 import해야 합니다.
app/layout.js
import '../styles/globals.css'
export default function RootLayout({ children }) { return ( <html lang="en"> <body>{children}</body> </html> ) }Tailwind CSS로 스타일링에 대해 더 알아보세요.
App Router와 Pages Router를 함께 사용하기
섹션 제목: “App Router와 Pages Router를 함께 사용하기”서로 다른 Next.js 라우터가 제공하는 라우트 사이를 이동할 때는 하드 내비게이션이 발생하며, next/link의 자동 링크 사전 패칭은 라우터 간에는 동작하지 않습니다.
대신 App Router와 Pages Router 간 내비게이션을 최적화해 사전 패칭과 빠른 페이지 전환을 유지할 수 있습니다. 자세히 알아보기.
코드모드
섹션 제목: “코드모드”Next.js는 기능이 더 이상 사용되지 않을 때 코드베이스를 업그레이드하도록 돕는 Codemod 변환을 제공합니다. 자세한 내용은 Codemods를 참고하세요.
보내기