Как перейти с Pages Router на App Router
Это руководство поможет вам:
- Обновить приложение Next.js с версии 12 до версии 13
- Обновить функции, работающие как в директории
pages
, так и вapp
- Постепенно перенести существующее приложение с
pages
наapp
Обновление
Версия Node.js
Минимальная версия Node.js теперь v18.17. Подробнее см. в документации 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
.
Подробнее см. на странице Оптимизация шрифтов.
Миграция с 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
.- API Routes
pages/api/*
заменены на специальный файлroute.js
(Route Handler).
Шаг 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
.
Для управления элементами <head>
можно использовать встроенную поддержку SEO:
import type { 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
используется для управления элементами <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 type { 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
использует вложенные папки для определения маршрутов и специальный файл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 Router), есть этап оптимизации для предварительного рендеринга клиентских компонентов в статический HTML при первоначальной загрузке страницы.
- Перенесите экспортируемый по умолчанию компонент страницы из
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 для получения данных. Подробнее см. в руководстве по обновлению методов получения данных.// Импортируйте ваш клиентский компонент 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
иuseParams
. - Вы можете использовать
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()
, пропустит этап предварительного рендеринга и вместо этого будет отрендерен на клиенте во время выполнения.route
удалён. Альтернативой являютсяusePathname
илиuseSelectedLayoutSegments()
.
См. справочник API useRouter()
.
Совместное использование компонентов между pages
и app
Чтобы сохранить совместимость компонентов между маршрутизаторами pages
и app
, обратитесь к хуку useRouter
из next/compat/router
.
Это хук useRouter
из директории pages
, но предназначенный для использования при совместном использовании компонентов между маршрутизаторами. Когда вы будете готовы использовать его только в маршрутизаторе app
, обновитесь до нового useRouter
из next/navigation
.
Шаг 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 Router) мы можем размещать получение данных внутри наших 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 = (await headers()).get('authorization')
return '...'
}
export default async function Page() {
// Вы можете использовать `cookies` или `headers` внутри серверных компонентов
// напрямую или в вашей функции получения данных
const theme = (await cookies()).get('theme')
const data = await getData()
return '...'
}
// Директория `app`
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = (await headers()).get('authorization')
return '...'
}
export default async function Page() {
// Вы можете использовать `cookies` или `headers` внутри серверных компонентов
// напрямую или в вашей функции получения данных
const theme = (await 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
, generateStaticParams
возвращает массив сегментов вместо массива вложенных объектов param
или строки с разрешёнными путями.
// Директория `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/${(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} />
}
Использование имени 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) для безопасного получения данных. Подробнее о получении данных.
Одностраничные приложения (SPA)
Если вы одновременно переходите на Next.js с Одностраничного приложения (SPA), ознакомьтесь с нашей документацией для получения дополнительной информации.
Шаг 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
Использование App Router вместе с Pages Router
При переходе между маршрутами, обслуживаемыми разными роутерами Next.js, будет происходить жёсткая навигация. Автоматический префетчинг ссылок с next/link
не будет работать между роутерами.
Вместо этого вы можете оптимизировать навигацию между App Router и Pages Router для сохранения префетчинга и быстрых переходов между страницами. Подробнее.
Codemods
Next.js предоставляет преобразования Codemod для помощи в обновлении кодовой базы при устаревании функциональности. Подробнее см. Codemods.