Как перейти с 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.jspage.js/about.jsabout/page.js/aboutblog/[slug].jsblog/[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.