Переход с Pages Router на App Router
Это руководство поможет вам:
- Обновить приложение Next.js с версии 12 до версии 13
- Обновить функции, работающие как в директории
pages
, так и вapp
- Постепенно перенести существующее приложение с
pages
наapp
Обновление
Версия Node.js
Минимальная версия Node.js теперь v16.14. Подробнее см. в документации Node.js.
Версия Next.js
Для обновления до Next.js версии 13 выполните следующую команду с помощью предпочитаемого менеджера пакетов:
npm install next@latest react@latest react-dom@latest
Версия ESLint
Если вы используете ESLint, вам нужно обновить его версию:
npm install -D eslint-config-next@latest
Полезно знать: Возможно, вам потребуется перезапустить сервер ESLint в VS Code, чтобы изменения вступили в силу. Откройте палитру команд (
cmd+shift+p
на Mac;ctrl+shift+p
на Windows) и найдитеESLint: Restart ESLint Server
.
Следующие шаги
После обновления ознакомьтесь со следующими разделами:
- Обновление новых функций: Руководство по переходу на новые функции, такие как улучшенные компоненты Image и Link.
- Миграция с
pages
наapp
: Пошаговое руководство по постепенному переходу с директорииpages
наapp
.
Обновление новых функций
Next.js 13 представил новый App Router с новыми функциями и соглашениями. Новый роутер доступен в директории app
и сосуществует с директорией pages
.
Обновление до Next.js 13 не требует использования нового App Router. Вы можете продолжать использовать pages
с новыми функциями, работающими в обеих директориях, такими как обновлённые компонент Image, компонент Link, компонент Script и оптимизация шрифтов.
Компонент <Image/>
Next.js 12 представил улучшения для компонента Image с временным импортом: next/future/image
. Эти улучшения включали меньше клиентского JavaScript, более простые способы расширения и стилизации изображений, лучшую доступность и нативную ленивую загрузку в браузере.
В версии 13 это новое поведение стало стандартным для next/image
.
Доступны два кодмода для помощи в миграции на новый компонент Image:
- Кодмод
next-image-to-legacy-image
: Безопасно и автоматически переименовывает импортыnext/image
вnext/legacy/image
. Существующие компоненты сохранят прежнее поведение. - Кодмод
next-image-experimental
: Опасный кодмод, добавляющий встроенные стили и удаляющий неиспользуемые пропсы. Это изменит поведение существующих компонентов в соответствии с новыми стандартами. Для использования этого кодмода сначала нужно выполнитьnext-image-to-legacy-image
.
Компонент <Link>
Компонент <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
было обновлено для поддержки как pages
, так и app
, но для плавной миграции необходимо внести некоторые изменения:
- Переместите все скрипты
beforeInteractive
, которые ранее включались в_document.js
, в корневой файл макета (app/layout.tsx
). - Экспериментальная стратегия
worker
пока не работает вapp
, и скрипты с этой стратегией придётся либо удалить, либо изменить на другую стратегию (например,lazyOnload
). - Обработчики
onLoad
,onReady
иonError
не работают в серверных компонентах, поэтому их нужно переместить в клиентский компонент или полностью удалить.
Оптимизация шрифтов
Ранее Next.js помогал оптимизировать шрифты с помощью встраивания CSS шрифтов. Версия 13 представляет новый модуль next/font
, который позволяет настраивать процесс загрузки шрифтов, сохраняя при этом высокую производительность и приватность. next/font
поддерживается как в директории pages
, так и в app
.
Хотя встраивание CSS по-прежнему работает в pages
, оно не работает в app
. Вместо этого следует использовать next/font
.
Подробнее об использовании next/font
см. на странице Оптимизация шрифтов.
Миграция с pages
на app
🎥 Видео: Узнайте, как постепенно внедрять App Router → YouTube (16 минут).
Переход на App Router может быть первым опытом использования функций React, на которых построен Next.js, таких как серверные компоненты, Suspense и другие. В сочетании с новыми функциями Next.js, такими как специальные файлы и макеты, миграция означает изучение новых концепций, моделей мышления и изменений в поведении.
Мы рекомендуем снизить сложность этих обновлений, разбив миграцию на меньшие шаги. Директория app
специально разработана для одновременной работы с директорией pages
, что позволяет постепенно переносить приложение страница за страницей.
- Директория
app
поддерживает вложенные маршруты и макеты. Подробнее. - Используйте вложенные папки для определения маршрутов и специальный файл
page.js
, чтобы сделать сегмент маршрута общедоступным. Подробнее. - Специальные файлы используются для создания UI для каждого сегмента маршрута. Наиболее распространённые специальные файлы —
page.js
иlayout.js
.- Используйте
page.js
для определения UI, уникального для маршрута. - Используйте
layout.js
для определения UI, общего для нескольких маршрутов. - Для специальных файлов можно использовать расширения
.js
,.jsx
или.tsx
.
- Используйте
- В директории
app
можно размещать другие файлы, такие как компоненты, стили, тесты и т. д. Подробнее. - Функции получения данных, такие как
getServerSideProps
иgetStaticProps
, заменены на новый API внутриapp
.getStaticPaths
заменён наgenerateStaticParams
. pages/_app.js
иpages/_document.js
заменены одним корневым макетомapp/layout.js
. Подробнее.pages/_error.js
заменён более детализированными специальными файламиerror.js
. Подробнее.pages/404.js
заменён файломnot-found.js
.pages/api/*
пока остаются в директорииpages
.
Шаг 1: Создание директории app
Обновитесь до последней версии Next.js (требуется 13.4 или выше):
npm install next@latest
Затем создайте новую директорию app
в корне вашего проекта (или в директории src/
).
Шаг 2: Создание корневого макета
Создайте новый файл app/layout.tsx
внутри директории app
. Это корневой макет, который будет применяться ко всем маршрутам внутри app
.
export default function RootLayout({
// Макеты должны принимать проп children.
// Это будет заполнено вложенными макетами или страницами
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
export default function RootLayout({
// Макеты должны принимать проп children.
// Это будет заполнено вложенными макетами или страницами
children,
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
- Директория
app
обязательно должна включать корневой макет. - Корневой макет должен определять теги
<html>
и<body>
, так как Next.js не создаёт их автоматически. - Корневой макет заменяет файлы
pages/_app.tsx
иpages/_document.tsx
. - Для файлов макетов можно использовать расширения
.js
,.jsx
или.tsx
.
Для управления HTML-элементами <head>
можно использовать встроенную поддержку SEO:
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Home',
description: 'Welcome to Next.js',
}
export const metadata = {
title: 'Home',
description: 'Welcome to Next.js',
}
Миграция _document.js
и _app.js
Если у вас есть существующие файлы _app
или _document
, вы можете скопировать их содержимое (например, глобальные стили) в корневой макет (app/layout.tsx
). Стили в app/layout.tsx
не будут применяться к pages/*
. Вы должны сохранить _app
/_document
во время миграции, чтобы маршруты pages/*
продолжали работать. После полной миграции их можно безопасно удалить.
Если вы используете провайдеры React Context, их нужно переместить в клиентский компонент.
Миграция шаблона getLayout()
на макеты (опционально)
Next.js рекомендовал добавлять свойство к компонентам страниц для реализации макетов на уровне страниц в директории pages
. Этот шаблон можно заменить нативную поддержку вложенных макетов в директории app
.
Пример до и после
До
export default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
}
import DashboardLayout from '../components/DashboardLayout'
export default function Page() {
return <p>My Page</p>
}
Page.getLayout = function getLayout(page) {
return <DashboardLayout>{page}</DashboardLayout>
}
После
-
Удалите свойство
Page.getLayout
изpages/dashboard/index.js
и следуйте шагам миграции страниц в директориюapp
.app/dashboard/page.js export default function Page() { return <p>My Page</p> }
-
Переместите содержимое
DashboardLayout
в новый клиентский компонент, чтобы сохранить поведение директорииpages
.app/dashboard/DashboardLayout.js 'use client' // Эта директива должна быть вверху файла, перед любыми импортами. // Это клиентский компонент export default function DashboardLayout({ children }) { return ( <div> <h2>My Dashboard</h2> {children} </div> ) }
-
Импортируйте
DashboardLayout
в новый файлlayout.js
внутри директорииapp
.app/dashboard/layout.js import DashboardLayout from './DashboardLayout' // Это серверный компонент export default function Layout({ children }) { return <DashboardLayout>{children}</DashboardLayout> }
-
Вы можете постепенно перемещать неинтерактивные части
DashboardLayout.js
(клиентский компонент) вlayout.js
(серверный компонент), чтобы уменьшить количество JavaScript, отправляемого клиенту.
Шаг 3: Миграция next/head
В директории pages
React-компонент next/head
использовался для управления HTML-элементами <head>
, такими как title
и meta
. В директории app
next/head
заменён новой встроенной поддержкой SEO.
До:
import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}
import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}
После:
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}
export const metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}
Шаг 4: Перенос страниц
- Страницы в директории
app
по умолчанию являются Серверными компонентами (Server Components). Это отличается от директорииpages
, где страницы являются Клиентскими компонентами (Client Components). - Получение данных (Data fetching) изменилось в
app
. МетодыgetServerSideProps
,getStaticProps
иgetInitialProps
заменены на более простой API. - Директория
app
использует вложенные папки для определения маршрутов (defining routes) и специальный файлpage.js
, чтобы сделать сегмент маршрута общедоступным. -
Директория pages
Директория app
Маршрут index.js
page.js
/
about.js
about/page.js
/about
blog/[slug].js
blog/[slug]/page.js
/blog/post-1
Рекомендуем разбить перенос страницы на два основных шага:
- Шаг 1: Перенести экспортируемый по умолчанию компонент страницы в новый Клиентский компонент.
- Шаг 2: Импортировать новый Клиентский компонент в файл
page.js
внутри директорииapp
.
Полезно знать: Это самый простой путь миграции, так как он наиболее близок к поведению директории
pages
.
Шаг 1: Создание нового Клиентского компонента
- Создайте новый отдельный файл внутри директории
app
(например,app/home-page.tsx
или аналогичный), который экспортирует Клиентский компонент. Чтобы определить Клиентские компоненты, добавьте директиву'use client'
в начало файла (перед любыми импортами). - Перенесите экспортируемый по умолчанию компонент страницы из
pages/index.js
вapp/home-page.tsx
.
'use client'
// Это Клиентский компонент. Он получает данные через пропсы и
// имеет доступ к состоянию и эффектам, как и компоненты страниц
// в директории `pages`.
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
'use client'
// Это Клиентский компонент. Он получает данные через пропсы и
// имеет доступ к состоянию и эффектам, как и компоненты страниц
// в директории `pages`.
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
Шаг 2: Создание новой страницы
-
Создайте новый файл
app/page.tsx
внутри директорииapp
. Это по умолчанию Серверный компонент. -
Импортируйте Клиентский компонент
home-page.tsx
в страницу. -
Если вы получали данные в
pages/index.js
, перенесите логику получения данных непосредственно в Серверный компонент, используя новый API для получения данных (data fetching APIs). Подробнее см. в руководстве по обновлению получения данных.// Импортируйте ваш Клиентский компонент 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} /> }
// Импортируйте ваш Клиентский компонент 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
. Вы должны увидеть ваш существующий маршрут index, теперь обслуживаемый через директорию app.
Шаг 5: Перенос хуков маршрутизации
Добавлен новый роутер для поддержки нового поведения в директории app
.
В app
следует использовать три новых хука, импортируемых из next/navigation
: useRouter()
, usePathname()
и useSearchParams()
.
- Новый хук
useRouter
импортируется изnext/navigation
и ведёт себя иначе, чем хукuseRouter
вpages
, который импортируется изnext/router
.- Хук
useRouter
, импортируемый изnext/router
, не поддерживается в директорииapp
, но может продолжать использоваться в директорииpages
.
- Хук
- Новый
useRouter
не возвращает строкуpathname
. Вместо этого используйте отдельный хукusePathname
. - Новый
useRouter
не возвращает объектquery
. Вместо этого используйте отдельный хукuseSearchParams
. - Вы можете использовать
useSearchParams
иusePathname
вместе для отслеживания изменений страницы. Подробнее см. в разделе События роутера (Router Events). - Эти новые хуки поддерживаются только в Клиентских компонентах. Они не могут использоваться в Серверных компонентах.
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}
Кроме того, новый хук useRouter
имеет следующие изменения:
isFallback
удалён, так какfallback
был заменён.- Значения
locale
,locales
,defaultLocales
,domainLocales
удалены, так как встроенные функции i18n Next.js больше не нужны в директорииapp
. Подробнее о i18n. basePath
удалён. Альтернатива не будет частьюuseRouter
. Она ещё не реализована.asPath
удалён, так как концепцияas
была удалена из нового роутера.isReady
удалён, так как больше не нужен. Во время статического рендеринга (static rendering) любой компонент, использующий хукuseSearchParams()
, пропускает этап предварительного рендеринга и вместо этого рендерится на клиенте во время выполнения.
См. справочник API useRouter()
.
Шаг 6: Перенос методов получения данных
В директории pages
используются getServerSideProps
и getStaticProps
для получения данных для страниц. В директории app
эти методы заменены на более простой API, построенный на основе fetch()
и асинхронных Серверных компонентов React.
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 секунд.
// Аналогично `getStaticProps` с опцией `revalidate`.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}
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 секунд.
// Аналогично `getStaticProps` с опцией `revalidate`.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}
Рендеринг на стороне сервера (getServerSideProps
)
В директории pages
getServerSideProps
используется для получения данных на сервере и передачи пропсов в экспортируемый по умолчанию React-компонент в файле. Начальный HTML для страницы предварительно рендерится на сервере, после чего страница "гидратируется" в браузере (становится интерактивной).
// Директория `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
мы можем разместить получение данных внутри наших React-компонентов, используя Серверные компоненты (Server Components). Это позволяет отправлять меньше JavaScript на клиент, сохраняя при этом HTML, отрендеренный на сервере.
Установив опцию cache
в no-store
, мы можем указать, что полученные данные никогда не должны кэшироваться. Это аналогично getServerSideProps
в директории pages
.
// Директория `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>
)
}
// Директория `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.
Например, вы можете получить объект req
из getServerSideProps
и использовать его для получения cookies и заголовков запроса.
// Директория `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
предоставляет новые функции только для чтения для получения данных запроса:
headers()
: Основана на Web Headers API и может использоваться внутри Серверных компонентов (Server Components) для получения заголовков запроса.cookies()
: Основана на Web Cookies API и может использоваться внутри Серверных компонентов (Server Components) для получения cookies.
// Директория `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 '...'
}
// Директория `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`
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'
, что кэширует данные запроса до ручной инвалидации. Это аналогично getStaticProps
в директории pages
.
// Директория `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`
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 для возврата параметров маршрута и может использоваться внутри макетов (layouts). В отличие от getStaticPaths
, которая возвращает массив вложенных объектов param
или строку с готовыми путями, generateStaticParams
возвращает массив сегментов.
// Директория `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
заменен на более описательный generate
, что лучше соответствует новой модели, где getStaticProps
и getServerSideProps
больше не нужны. Суффикс Paths
заменен на Params
, что более подходит для вложенной маршрутизации с несколькими динамическими сегментами.
Замена fallback
В директории pages
свойство fallback
, возвращаемое из getStaticPaths
, используется для определения поведения страницы, которая не была предварительно отрендерена во время сборки. Это свойство может быть установлено в true
для показа резервной страницы во время генерации, false
для показа страницы 404 или blocking
для генерации страницы во время запроса.
// Директория `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.
Это заменяет опцию fallback: true | false | 'blocking'
из getStaticPaths
в директории pages
. Опция fallback: 'blocking'
не включена в dynamicParams
, так как разница между 'blocking'
и true
незначительна при использовании потоковой передачи.
// Директория `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 ...
}
При установке dynamicParams
в true
(значение по умолчанию), если запрашивается сегмент маршрута, который не был сгенерирован, он будет отрендерен на сервере и закэширован.
Инкрементальная статическая регенерация (getStaticProps
с revalidate
)
В директории pages
функция getStaticProps
позволяет добавить поле revalidate
для автоматической регенерации страницы через определенный промежуток времени.
// Директория `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`
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).
Обработчики маршрутов позволяют создавать пользовательские обработчики запросов для заданного маршрута с использованием Web API Request и Response.
export async function GET(request: Request) {}
export async function GET(request) {}
Полезно знать: Если вы ранее использовали API-маршруты для вызова внешнего API из клиента, теперь вы можете использовать Серверные компоненты (Server Components) для безопасного получения данных. Подробнее о запросе данных.
Шаг 7: Стилизация
В директории pages
глобальные стили ограничены только файлом pages/_app.js
. В директории app
это ограничение снято. Глобальные стили можно добавлять в любой макет, страницу или компонент.
Tailwind CSS
Если вы используете Tailwind CSS, вам нужно добавить директорию 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
:
import '../styles/globals.css'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
Подробнее о стилизации с Tailwind CSS
Codemods
Next.js предоставляет преобразования Codemod для помощи в обновлении кодовой базы при устаревании функциональности. Подробнее см. Codemods.