ReactNextCentral

앱 라우터 점진적 적용 가이드

Published on
페이지 라우터에서 앱 라우터로 기존의 Next.js 애플리케이션을 업그레이드하는 방법을 배워보세요.
Table of Contents

이 가이드는 다음을 도와줍니다.


업그레이딩

Node.js 버전

최소 Node.js 버전은 이제 v16.14 입니다. Node.js 문서에서 자세한 내용을 확인하세요.

Next.js 버전

Next.js 버전 13으로 업데이트하려면 선호하는 패키지 매니저를 사용하여 다음 명령을 실행하세요.

터미널
npm install next@latest react@latest react-dom@latest

ESLint 버전

ESLint를 사용하는 경우 ESLint 버전을 업그레이드해야 합니다.

터미널
npm install -D eslint-config-next@latest

ESLint 변경 사항이 적용되려면 VS Code의 ESLint 서버를 재시작해야 할 수 있습니다. 명령 팔레트 (Mac에서 cmd+shift+p; Windows에서 ctrl+shift+p)를 열고 ESLint: ESLint 서버 재시작을 검색하세요.


다음 단계

업데이트한 후 다음 섹션을 참조하여 다음 단계를 확인하세요.


새로운 기능 업그레이드

Next.js 13은 새로운 기능과 규칙을 가진 새로운 앱 라우터를 도입했습니다. 새로운 라우터는 app 디렉터리에서 사용할 수 있으며 pages 디렉터리와 함께 존재합니다.

Next.js 13으로 업그레이드하는 것은 새로운 앱 라우터를 사용하는 것을 요구하지 않습니다. 두 디렉터리에서 모두 작동하는 새로운 기능, 예를 들어 업데이트된 이미지 컴포넌트, 링크 컴포넌트, 스크립트 컴포넌트, 폰트 최적화와 같은 것을 계속 사용할 수 있습니다.

<Image/> 컴포넌트

Next.js 12는 임시 임포트인 next/future/image와 함께 이미지 컴포넌트에 새로운 개선 사항을 도입했습니다. 이러한 개선 사항에는 클라이언트 측 자바스크립트 감소, 이미지 확장 및 스타일링을 더 쉽게 하는 방법, 더 나은 접근성, 기본 브라우저 지연 로딩이 포함되었습니다.

버전 13에서는 이 새로운 동작이 이제 next/image에 대한 기본값이 되었습니다.

새로운 이미지 컴포넌트로 마이그레이션하는데 도움이 되는 두 가지 코드 모드가 있습니다.

  • next-image-to-legacy-image 코드모드: 안전하게 및 자동으로 next/image 임포트를 next/legacy/image로 이름을 변경합니다. 기존 컴포넌트는 동일한 동작을 유지합니다.
  • next-image-experimental 코드모드: 위험하게 인라인 스타일을 추가하고 사용되지 않는 속성을 제거합니다. 기존 컴포넌트의 동작이 새로운 기본값과 일치하도록 변경됩니다. 이 코드모드를 사용하려면 먼저 next-image-to-legacy-image 코드모드를 실행해야 합니다.

<Link> 컴포넌트는 이제 자식으로 <a> 태그를 수동으로 추가하는 것을 필요로 하지 않습니다. 이 동작은 버전 12.2에서 실험적 옵션으로 추가되었으며 이제 기본값입니다. Next.js 13에서는 <Link>가 항상 <a>를 렌더링하며 기본 태그에 대한 속성을 전달할 수 있습니다.

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> 컴포넌트

next/script의 동작이 업데이트되어 pagesapp 모두를 지원하게 되었습니다. 그러나 원활한 마이그레이션을 위해 몇 가지 변경이 필요합니다.

  • 이전에 _document.js에 포함했던 beforeInteractive 스크립트를 루트 레이아웃 파일(app/layout.tsx)로 이동하세요.
  • 실험적인 worker 전략은 app에서 아직 작동하지 않으며, 이 전략으로 표시된 스크립트는 제거하거나 다른 전략(예: lazyOnload)을 사용하여 수정해야 합니다.
  • onLoad, onReady, onError 핸들러는 서버 컴포넌트에서 작동하지 않으므로 클라이언트 컴포넌트로 이동하거나 완전히 제거해야 합니다.

폰트 최적화

이전에는 Next.js가 폰트 CSS 인라인을 통해 폰트를 최적화하는 데 도움을 주었습니다. 버전 13은 성능과 프라이버시를 보장하면서 폰트 로딩 경험을 사용자 정의할 수 있는 새로운 next/font 모듈을 도입했습니다. next/fontpagesapp 디렉토리 모두에서 지원됩니다.

pages에서는 CSS 인라인이 여전히 작동하지만 app에서는 작동하지 않습니다. 대신 next/font를 사용해야 합니다. next/font의 사용 방법은 폰트 최적화 페이지에서 알아볼 수 있습니다.


pages에서 app으로 마이그레이션

🎥 시청하기: 앱 라우터를 점진적으로 채택하는 방법을 배워보세요 → YouTube (16분).

앱 라우터로 이동하는 것은 리액트의 서버 컴포넌트, Suspense 등 Next.js가 기반으로 하는 기능을 처음 사용하는 경우일 수 있습니다. 특별한 파일레이아웃과 같은 새로운 Next.js 기능과 결합할 때, 마이그레이션은 새로운 개념, 사고 모델 및 행동 변경을 익혀야 합니다.

이러한 업데이트의 복잡성을 줄이기 위해 마이그레이션을 더 작은 단계로 나누는 것이 좋습니다. app 디렉토리는 pages 디렉토리와 동시에 작동하도록 의도적으로 설계되었으므로 페이지별로 점진적으로 마이그레이션 할 수 있습니다.

  • app 디렉토리는 중첩된 루트와 레이아웃을 모두 지원합니다. 자세히 알아보기.
  • 중첩된 폴더를 사용하여 루트를 정의하고 특별한 page.js 파일을 사용하여 루트 세그먼트를 공개적으로 접근 가능하게 만듭니다.

1단계: app 디렉토리 생성

최신 Next.js 버전으로 업데이트하세요. (13.4 이상 필요)

npm install next@latest

그런 다음 프로젝트의 루트(또는 src/ 디렉토리)에 새로운 app 디렉토리를 생성하세요.

2단계: 루트 레이아웃 생성

app 디렉토리 내에 새로운 app/layout.tsx 파일을 생성합니다. 이것은 app 내의 모든 루트에 적용되는 루트 레이아웃입니다.

app/layout.tsx
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>
  )
}
JavaScript
app/layout.js
export default function RootLayout({
  // Layouts must accept a children prop.
  // This will be populated with nested layouts or pages
  children,
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

app 디렉토리는 반드시 루트 레이아웃을 포함해야 합니다. 루트 레이아웃은 <html>, <body> 태그를 정의해야 합니다. Next.js는 자동으로 이들을 생성하지 않습니다.

app/layout.tsx
import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Home',
  description: 'Welcome to Next.js',
}
JavaScript
app/layout.js
export const metadata = {
  title: 'Home',
  description: 'Welcome to Next.js',
}

_document.js_app.js 마이그레이션

기존의 _app 또는 _document 파일이 있다면 그 내용을 루트 레이아웃 (app/layout.tsx)으로 복사할 수 있습니다. app/layout.tsx의 스타일은 pages/*에는 적용되지 않습니다. 마이그레이션 중에 pages/* 라우트가 중단되는 것을 방지하기 위해 _app/_document를 유지해야 합니다. 완전히 마이그레이션 된 후에 안전하게 삭제할 수 있습니다.

리액트 Context 제공자를 사용하고 있다면, 이들은 클라이언트 컴포넌트로 이동해야 합니다.

getLayout() 패턴을 레이아웃으로 마이그레이션 (선택사항)

Next.js는 pages 디렉터리에서 페이지별 레이아웃을 달성하기 위해 페이지 컴포넌트에 속성을 추가하는 것을 권장했습니다. 이 패턴은 app 디렉터리에서 중첩 레이아웃을 자연스럽게 지원하는 것으로 대체될 수 있습니다.

단계 3: next/head 마이그레이션

pages 디렉터리에서는 next/head 리액트 컴포넌트를 사용하여 titlemeta와 같은 <head> HTML 요소를 관리합니다. app 디렉터리에서는 next/head가 새로운 내장 SEO 지원으로 대체됩니다.

변경 전:

pages/index.tsx
import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>나의 페이지 제목</title>
      </Head>
    </>
  )
}
JavaScript
pages/index.js
import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>나의 페이지 제목</title>
      </Head>
    </>
  )
}

변경 후:

app/page.tsx
import { Metadata } from 'next'

export const metadata: Metadata = {
  title: '나의 페이지 제목',
}

export default function Page() {
  return '...'
}
JavaScript
app/page.js
export const metadata = {
  title: '나의 페이지 제목',
}

export default function Page() {
  return '...'
}

모든 메타데이터 옵션 보기.

단계 4: 페이지 마이그레이션

  • app 디렉터리의 페이지들은 기본적으로 서버 컴포넌트입니다. 이는 pages 디렉터리의 페이지들이 클라이언트 컴포넌트인 것과는 다릅니다.
  • app에서의 데이터 가져오기 방식이 변경되었습니다. getServerSideProps, getStaticPropsgetInitialProps는 더 간단한 API로 대체되었습니다.
  • app 디렉터리는 루트를 정의하기 위해 중첩 폴더를 사용하며, 루트 세그먼트를 공개적으로 접근 가능하게 하는 특별한 page.js 파일을 사용합니다.
  • pages 디렉터리app 디렉터리루트
    index.jspage.js/
    about.jsabout/page.js/about
    blog/[slug].jsblog/[slug]/page.js/blog/post-1

페이지의 마이그레이션을 두 가지 주요 단계로 나누어 진행할 것을 권장합니다.

  • 단계 1: 기본으로 내보낸 페이지 컴포넌트를 새 클라이언트 컴포넌트로 이동합니다.
  • 단계 2: 새 클라이언트 컴포넌트를 app 디렉터리 내의 새 page.js 파일로 가져옵니다.

알아두면 좋은 점: 이 방법은 pages 디렉터리와 가장 유사한 동작을 가지기 때문에 가장 쉬운 마이그레이션 경로입니다.

단계 1: 새로운 클라이언트 컴포넌트 생성

  • app 디렉터리 내에 새로운 파일(예: app/home-page.tsx 또는 유사한 이름)을 만들어 클라이언트 컴포넌트를 내보냅니다. 클라이언트 컴포넌트를 정의하려면 파일 상단(모든 가져오기 이전)에 'use client' 지시문을 추가합니다.
  • 기본 내보내기된 페이지 컴포넌트를 pages/index.js에서 app/home-page.tsx로 이동합니다.
app/home-page.tsx
'use client'

// 이것은 클라이언트 컴포넌트입니다. 데이터를 props로 받아오며
// `pages` 디렉터리의 페이지 컴포넌트와 마찬가지로 상태와 효과에 접근할 수 있습니다.
export default function HomePage({ recentPosts }) {
  return (
    <div>
      {recentPosts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}
JavaScript
app/home-page.js
'use client'

// 이것은 클라이언트 컴포넌트입니다. 데이터를 props로 받아오며
// `pages` 디렉터리의 페이지 컴포넌트와 마찬가지로 상태와 효과에 접근할 수 있습니다.
export default function HomePage({ recentPosts }) {
  return (
    <div>
      {recentPosts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}

단계 2: 새 페이지 생성

  • app 디렉터리 내에 새 app/page.tsx 파일을 생성합니다. 이것은 기본적으로 서버 컴포넌트입니다.

  • home-page.tsx 클라이언트 컴포넌트를 페이지로 가져옵니다.

  • pages/index.js에서 데이터를 가져오고 있었다면, 새로운 데이터 가져오기 API를 사용하여 데이터 가져오기 로직을 직접 서버 컴포넌트로 이동시킵니다. 자세한 내용은 데이터 가져오기 업그레이드 가이드를 참고하세요.

    app/page.tsx
    // 클라이언트 컴포넌트를 가져옵니다.
    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() {
      // 서버 컴포넌트 내에서 직접 데이터를 가져옵니다.
      const recentPosts = await getPosts()
      // 가져온 데이터를 클라이언트 컴포넌트로 전달합니다.
      return <HomePage recentPosts={recentPosts} />
    }
    
  • 이전 페이지에서 useRouter를 사용하였다면, 새로운 라우팅 후크로 업데이트해야 합니다. 자세히 알아보기.

  • 개발 서버를 시작하고 http://localhost:3000을 방문합니다. 이제 app 디렉터리를 통해 제공되는 기존 인덱스 루트를 볼 수 있어야 합니다.

단계 5: 라우팅 후크 마이그레이션

새로운 라우터가 app 디렉터리의 새로운 동작을 지원하기 위해 추가되었습니다.

app에서는 next/navigation에서 가져온 세 개의 새 후크를 사용해야 합니다. useRouter(), usePathname(), 그리고 useSearchParams().

  • 새로운 useRouter 후크는 next/navigation에서 가져와지며, next/router에서 가져온 pagesuseRouter 후크와는 다른 동작을 합니다.
  • 새로운 useRouterpathname 문자열을 반환하지 않습니다. 대신 별도의 usePathname 후크를 사용합니다.
  • 새로운 useRouterquery 객체를 반환하지 않습니다. 대신 별도의 useSearchParams 후크를 사용합니다.
  • 페이지 변경사항을 감지하기 위해 useSearchParamsusePathname을 함께 사용할 수 있습니다. 자세한 내용은 라우터 이벤트 섹션을 참고하세요.
  • 이 새로운 후크들은 클라이언트 컴포넌트에서만 지원됩니다. 서버 컴포넌트에서는 사용할 수 없습니다.
app/example-client-component.tsx
'use client'

import { useRouter, usePathname, useSearchParams } \
from 'next/navigation'

export default function ExampleClientComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()

  // ...
}

더불어 새로운 useRouter 후크에는 다음과 같은 변경사항이 있습니다.

  • isFallbackfallback대체되었기 때문에 제거되었습니다.
  • locale, locales, defaultLocales, domainLocales 값들은 app 디렉터리에서 더 이상 필요하지 않은 내장 i18n Next.js 기능들 때문에 제거되었습니다. i18n에 대해 자세히 알아보기.
  • basePath가 제거되었습니다. 대체 방법은 useRouter의 일부가 아닙니다. 아직 구현되지 않았습니다.
  • asPath가 제거되었습니다. 왜냐하면 새로운 라우터에서 as 개념이 제거되었기 때문입니다.
  • isReady는 더 이상 필요하지 않기 때문에 제거되었습니다. 정적 렌더링 중에 useSearchParams() 후크를 사용하는 모든 컴포넌트는 프리렌더링 단계를 건너뛰고 대신 런타임에서 클라이언트에 렌더링됩니다.

useRouter() API 레퍼런스 보기.

단계 6: 데이터 가져오기 방법 마이그레이션

pages 디렉터리는 페이지의 데이터를 가져오기 위해 getServerSidePropsgetStaticProps를 사용합니다. app 디렉터리 내에서 이전의 데이터 가져오기 함수들은 fetch()async 리액트 서버 컴포넌트 위에 구축된 더 간단한 API로 대체됩니다.

app/page.tsx
export default async function Page() {
  // 이 요청은 수동으로 무효화될 때까지 캐시되어야 합니다.
  // `getStaticProps`와 유사합니다.
  // `force-cache`는 기본값이며 생략할 수 있습니다.
  const staticData = await fetch(`https://...`, 
    { cache: 'force-cache' })

  // 이 요청은 모든 요청마다 다시 가져와야 합니다.
  // `getServerSideProps`와 유사합니다.
  const dynamicData = await fetch(`https://...`, 
    { cache: 'no-store' })

  // 이 요청은 10초 동안 캐시되어야 합니다.
  // `revalidate` 옵션을 가진 `getStaticProps`와 유사합니다.
  const revalidatedData = await fetch(`https://...`, {
    next: { revalidate: 10 },
  })

  return <div>...</div>
}

서버 사이드 렌더링 (getServerSideProps)

pages 디렉터리에서 getServerSideProps는 서버에서 데이터를 가져와 파일 내 기본으로 내보낸 리액트 컴포넌트에 props를 전달하는데 사용됩니다. 페이지의 초기 HTML은 서버에서 사전 렌더링되고, 이후 브라우저에서 페이지를 "hydrate"합니다(상호 작용 가능하게 만듭니다).

pages/dashboard.js
// `pages` 디렉터리

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 디렉터리에서는 서버 컴포넌트를 사용하여 리액트 컴포넌트 내부에 데이터 가져오기를 함께 위치시킬 수 있습니다. 이렇게하면 클라이언트로 보내는 자바스크립트 양을 줄이면서 서버에서 렌더링된 HTML을 유지할 수 있습니다.

cache 옵션을 no-store로 설정함으로써 가져온 데이터가 저장되지 않아야 함을 나타낼 수 있습니다. 이는 pages 디렉터리의 getServerSideProps와 유사합니다.

app/dashboard/page.tsx
// `app` 디렉터리

// 이 함수는 어떠한 이름으로든 지정할 수 있습니다.
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` 디렉터리

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 디렉터리는 요청 데이터를 검색하기 위한 새로운 읽기 전용 함수를 제공합니다.

app/page.tsx
// `app` 디렉터리
import { cookies, headers } from 'next/headers'

async function getData() {
  const authHeader = headers().get('authorization')

  return '...'
}

export default async function Page() {
  // 서버 컴포넌트 내에서 `cookies()`나 `headers()`를 
  // 직접 사용하거나 데이터 가져오기 함수에서 사용할 수 있습니다.
  const theme = cookies().get('theme')
  const data = await getData()
  return '...'
}

정적 사이트 생성 (getStaticProps)

pages 디렉터리에서 getStaticProps 함수는 빌드 시간에 페이지를 사전 렌더링하는 데 사용됩니다. 이 함수는 외부 API나 직접 데이터베이스에서 데이터를 가져와 빌드 중 페이지 전체에 이 데이터를 전달하는 데 사용할 수 있습니다.

pages/index.js
// `pages` 디렉터리

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` 디렉터리

// 이 함수는 어떠한 이름으로든 지정할 수 있습니다.
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)

pages 디렉터리에서, getStaticPaths 함수는 빌드 시간에 사전 렌더링되어야 할 동적 경로를 정의하는 데 사용됩니다.

pages/posts/[id].js
// `pages` 디렉터리
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 디렉터리에서, getStaticPathsgenerateStaticParams로 대체됩니다.

generateStaticParamsgetStaticPaths와 유사하게 동작하지만, 라우트 매개변수를 반환하기 위한 간소화된 API를 가지고 있으며 레이아웃 내에서 사용될 수 있습니다. generateStaticParams의 반환 형태는 중첩된 param 객체 배열이나 해결된 경로 문자열 대신 세그먼트 배열입니다.

app/posts/[id]/page.js
// `app` 디렉터리
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/${params.id}`)
  const post = await res.json()

  return post
}

export default async function Post({ params }) {
  const post = await getPost(params)

  return <PostLayout post={post} />
}

generateStaticParams라는 이름은 app 디렉터리의 새로운 모델에 getStaticPaths보다 더 적절합니다. get 접두어는 getStaticPropsgetServerSideProps가 더 이상 필요하지 않으므로 이제 더 서술적인 generate로 대체되었습니다. Paths 접미사는 다중 동적 세그먼트로 중첩된 라우팅에 더 적합한 Params로 대체되었습니다.

fallback 대체하기

pages 디렉터리에서, getStaticPaths에서 반환되는 fallback 속성은 빌드 시간에 사전 렌더링되지 않은 페이지의 동작을 정의하는 데 사용됩니다. 이 속성은 페이지가 생성되는 동안 대체 페이지를 표시하려면 true로 설정할 수 있고, 404 페이지를 표시하려면 false로 설정하거나, 요청 시간에 페이지를 생성하려면 blocking으로 설정할 수 있습니다.

pages/posts/[id].js
// `pages` 디렉터리

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 디렉터리의 getStaticPathsfallback: true | false | 'blocking' 옵션을 대체합니다. 스트리밍과의 차이 때문에 dynamicParamsfallback: 'blocking' 옵션이 포함되지 않았습니다.

app/posts/[id]/page.js
// `app` 디렉터리

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 ...
}

dynamicParamstrue로 설정된 경우 (기본값), 생성되지 않은 라우트 세그먼트가 요청되면 서버에서 렌더링되어 캐시됩니다.

증분 정적 재생성 (getStaticPropsrevalidate 사용)

pages 디렉터리에서, getStaticProps 함수를 사용하면 특정 시간 후 페이지를 자동으로 재생성하기 위해 revalidate 필드를 추가할 수 있습니다.

pages/index.js
// `pages` 디렉터리

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` 디렉터리

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 라우트는 pages/api 디렉터리에서 어떠한 변경도 없이 계속 작동합니다. 그러나 app 디렉터리에서는 Route Handlers에 의해 대체되었습니다.

Route Handlers는 Web RequestResponse API를 사용하여 주어진 라우트에 대한 사용자 정의 요청 핸들러를 생성할 수 있게 해줍니다.

"app/api/route.ts
export async function GET(request: Request) {}
"app/api/route.js
export async function GET(request) {}

클라이언트에서 외부 API를 호출하기 위해 API 라우트를 이전에 사용한 경우, 이제 Server Components를 사용하여 데이터를 안전하게 가져올 수 있습니다. data fetching에 대한 자세한 내용은 여기에서 알아보세요.

단계 7: 스타일링

pages 디렉터리에서는 전역 스타일시트는 pages/_app.js에만 제한됩니다. 그러나 app 디렉터리에서는 이 제한이 해제되었습니다. 전역 스타일은 어떤 레이아웃, 페이지 또는 컴포넌트에도 추가될 수 있습니다.

Tailwind CSS

Tailwind CSS를 사용하고 있다면, tailwind.config.js 파일에 app 디렉터리를 추가해야 합니다.

tailwind.config.js
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx,mdx}', // <-- 이 줄을 추가합니다
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
  ],
}

또한 app/layout.js 파일에서 전역 스타일을 가져와야 합니다.

app/layout.js
import '../styles/globals.css'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

Tailwind CSS로 스타일링하기에 대한 자세한 내용은 여기에서 알아보세요.

Codemods

Next.js는 기능이 폐지될 때 코드베이스를 업그레이드하는 데 도움을 주는 Codemod 변환을 제공합니다. 자세한 정보는 Codemods에서 확인하세요.