Руководство по поэтапному внедрению маршрутизатора приложений
Это руководство поможет вам:
- Обновить ваше приложение 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, вам необходимо обновить вашу версию ESLint:
npm install -D eslint-config-next@latest
Важно знать: Возможно, вам потребуется перезапустить сервер ESLint в VS Code, чтобы изменения в ESLint вступили в силу. Откройте диалоговое окно команд (
cmd+shift+pна Mac;ctrl+shift+pна Windows) и найдитеESLint: Restart ESLint Server.
Следующие шаги
После обновления ознакомьтесь со следующими разделами для получения следующих шагов:
- Обновить новые функции: Руководство по обновлению новых функций, таких как улучшенные компоненты Image и Link.
-
Переход из каталога
pagesв каталогapp: Пошаговое руководство по поэтапной миграции из каталогаpagesв каталогapp.
Обновление новых функций
Next.js 13 представил новый маршрутизатор приложений с новыми функциями и соглашениями. Новый маршрутизатор доступен в каталоге app и сосуществует с каталогом pages.
Обновление до Next.js 13 не требует использования нового маршрутизатора приложений. Вы можете продолжать использовать 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>` has to be nested otherwise it's excluded <Link href="/about"> <a>About</a> </Link> // Next.js 13: `<Link>` always renders `<a>` under the hood <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
🎥 Смотреть: Узнайте, как поэтапно перейти на маршрутизатор приложений → YouTube (16 минут).
Переход на маршрутизатор приложений может быть первым опытом использования функций React, на которых Next.js строится, таких как компоненты сервера, Suspense и многое другое. В сочетании с новыми функциями Next.js, такими как специальные файлы и макеты, миграция означает новые концепции, модели мышления и изменения поведения, которые необходимо изучить.
Мы рекомендуем уменьшить совокупную сложность этих обновлений, разделив миграцию на меньшие шаги. Каталог app преднамеренно разработан для одновременной работы с каталогом pages для поэтапной миграции страницы за страницей.
- Каталог
appподдерживает вложенные маршруты и макеты. Подробнее. - Используйте вложенные папки для определения маршрутов и специальный файл
page.jsдля публичного доступа к сегменту маршрута. Подробнее. -
Соглашения об именах специальных файлов используются для создания пользовательского интерфейса для каждого сегмента маршрута. Наиболее распространенными специальными файлами являются
page.jsиlayout.js.- Используйте
page.jsдля определения пользовательского интерфейса, уникального для маршрута. - Используйте
layout.jsдля определения пользовательского интерфейса, общего для нескольких маршрутов. -
Расширения файлов
.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
pages/api/*были заменены специальным файломroute.js(обработчик маршрутов).
Шаг 1: Создание каталога app
Обновите до последней версии Next.js (требуется 13.4 или выше):
npm install next@latest
Затем создайте новый каталог app в корне вашего проекта (или каталог src/).
Шаг 2: Создание корневого макета
Создайте новый файл app/layout.tsx внутри каталога app. Это корневой макет, который будет применяться ко всем маршрутам внутри app.
export default function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}- Каталог
appобязательно должен содержать корневой макет. - Корневой макет должен определять теги
<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',
}Миграция _document.js и _app.js
Если у вас есть существующий файл _app или _document, вы можете скопировать содержимое (например, глобальные стили) в корневой макет (app/layout.tsx). Стиль в app/layout.tsx не будут применяться к pages/*. Следует сохранить _app/_document, чтобы предотвратить разрыв маршрутов pages/*. После полной миграции их можно безопасно удалить.
Если вы используете поставщиков контекста React, их необходимо переместить в компонент клиента.
END_OF_DOCUMENT_MARKERМиграция шаблона getLayout() на Layouts (необязательно)
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.export default function Page() { return <p>My Page</p> } -
Переместите содержимое
DashboardLayoutв новый компонент клиента, чтобы сохранить поведение каталогаpages.'use client' // this directive should be at top of the file, before any imports. // This is a Client Component export default function DashboardLayout({ children }) { return ( <div> <h2>My Dashboard</h2> {children} </div> ) } -
Импортируйте
DashboardLayoutв новый файлlayout.jsвнутри каталогаapp.import DashboardLayout from './DashboardLayout' // This is a Server Component 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 { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}Шаг 4: Миграция страниц
- Страницы в каталоге
appпо умолчанию являются компонентами сервера. Это отличается от каталогаpages, где страницы являются компонентами клиента. - Загрузка данных изменилась в
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'в начало файла (перед любыми импортами).- Аналогично роутеру страниц, есть шаг оптимизации для предварительной рендеры компонентов клиента в статический HTML при первоначальной загрузке страницы.
- Переместите стандартный экспортируемый компонент страницы из
pages/index.jsвapp/home-page.tsx.
'use client'
// This is a Client Component (same as components in the `pages` directory)
// It receives data as props, has access to state and effects, and is
// prerendered on the server during the initial page load.
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}Шаг 2: Создание новой страницы
-
Создайте новый файл
app/page.tsxвнутри каталогаapp. Это компонент сервера по умолчанию. -
Импортируйте компонент клиента
home-page.tsxна страницу. -
Если вы загружали данные в
pages/index.js, переместите логику загрузки данных непосредственно в компонент сервера, используя новые API для загрузки данных. Подробнее см. в руководстве по обновлению загрузки данных.// Import your Client Component import HomePage from './home-page' async function getPosts() { const res = await fetch('https://...') const posts = await res.json() return posts } export default async function Page() { // Fetch data directly in a Server Component const recentPosts = await getPosts() // Forward fetched data to your Client Component return <HomePage recentPosts={recentPosts} /> } -
Если ваша предыдущая страница использовала
useRouter, вам нужно будет обновить её до новых хуков маршрутизации. Узнать больше. -
Запустите сервер разработки и перейдите по ссылке
http://localhost:3000. Вы должны увидеть свой существующий маршрут index, теперь он обслуживается через каталог приложения.
Шаг 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вместе, чтобы отслеживать изменения страниц. Более подробную информацию см. в разделе События маршрутизатора. - Эти новые хуки поддерживаются только в компонентах клиента. Они не могут использоваться в компонентах сервера.
'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были удалены, так как встроенные функции Next.js по интернационализации больше не нужны в каталогеapp. Узнайте больше об интернационализации. -
basePathбыл удален. Альтернатива не будет частьюuseRouter. Она еще не реализована. -
asPathбыл удален, потому что понятиеasбыло удалено из нового роутера. -
isReadyбыл удален, так как он больше не нужен. Во время статической рендеры любой компонент, использующий хукuseSearchParams(), пропустит шаг предварительной рендеры и вместо этого будет отрисован на клиенте во время выполнения.
Просмотреть useRouter() справочник API.
Шаг 6: Миграция методов загрузки данных
Каталог pages использует getServerSideProps и getStaticProps для загрузки данных для страниц. В каталоге app эти предыдущие функции загрузки данных заменены на более простой API, построенный на основе fetch() и async React компонентов сервера.
export default async function Page() {
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}Рендеринг на стороне сервера (getServerSideProps)
В каталоге pages, getServerSideProps используется для загрузки данных на сервере и передачи свойств в стандартный экспортируемый компонент React в файле. Изначальный HTML страницы предварительно рендерится на сервере, а затем страница «гидратируется» в браузере (делая её интерактивной).
// `pages` directory
export async function getServerSideProps() {
const res = await fetch(`https://...`)
const projects = await res.json()
return { props: { projects } }
}
export default function Dashboard({ projects }) {
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}В каталоге app, мы можем разместить загрузку данных внутри наших компонентов React, используя компоненты сервера. Это позволяет нам отправлять меньше JavaScript на клиент, сохраняя при этом HTML, отрисованный с сервера.
Установив опцию cache в no-store, мы можем указать, что загруженные данные никогда не должны кэшироваться. Это аналогично getServerSideProps в каталоге pages.
// `app` directory
// This function can be named anything
async function getProjects() {
const res = await fetch(`https://...`, { cache: 'no-store' })
const projects = await res.json()
return projects
}
export default async function Dashboard() {
const projects = await getProjects()
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}Доступ к объекту запроса
В каталоге pages, вы можете получить данные, основанные на запросе, на основе Node.js API HTTP.
Например, вы можете получить объект req из getServerSideProps и использовать его для получения куки и заголовков запроса.
// `pages` directory
export async function getServerSideProps({ req, query }) {
const authHeader = req.getHeaders()['authorization'];
const theme = req.cookies['theme'];
return { props: { ... }}
}
export default function Page(props) {
return ...
}Каталог app предоставляет новые только для чтения функции для получения данных запроса:
-
headers(): Основан на API Веб-заголовков и может использоваться внутри Серверных компонентов для получения заголовков запроса. -
cookies(): Основан на API Веб-куки и может использоваться внутри Серверных компонентов для получения куки.
// `app` directory
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = headers().get('authorization')
return '...'
}
export default async function Page() {
// You can use `cookies()` or `headers()` inside Server Components
// directly or in your data fetching function
const theme = cookies().get('theme')
const data = await getData()
return '...'
}Статическое создание сайта (getStaticProps)
В каталоге pages, функция getStaticProps используется для предварительного рендеринга страницы во время сборки. Эта функция может использоваться для получения данных из внешнего API или непосредственно из базы данных и передачи этих данных на всю страницу во время её генерации на этапе сборки.
// `pages` directory
export async function getStaticProps() {
const res = await fetch(`https://...`)
const projects = await res.json()
return { props: { projects } }
}
export default function Index({ projects }) {
return projects.map((project) => <div>{project.name}</div>)
}В каталоге app, получение данных с помощью fetch() по умолчанию будет cache: 'force-cache', что будет кешировать данные запроса до их ручного обновления. Это аналогично getStaticProps в каталоге pages.
// `app` directory
// This function can be named anything
async function getProjects() {
const res = await fetch(`https://...`)
const projects = await res.json()
return projects
}
export default async function Index() {
const projects = await getProjects()
return projects.map((project) => <div>{project.name}</div>)
}Динамические пути (getStaticPaths)
В каталоге pages, функция getStaticPaths используется для определения динамических путей, которые должны быть предварительно рендерены во время сборки.
// `pages` directory
import PostLayout from '@/components/post-layout'
export async function getStaticPaths() {
return {
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
}
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
return { props: { post } }
}
export default function Post({ post }) {
return <PostLayout post={post} />
}В каталоге app, getStaticPaths заменяется на generateStaticParams.
generateStaticParams ведет себя аналогично getStaticPaths, но имеет упрощённый API для возвращения параметров маршрута и может использоваться внутри макетов. Формат возвращаемых значений generateStaticParams — массив сегментов вместо массива вложенных объектов param или строки разрешённых путей.
// `app` directory
import PostLayout from '@/components/post-layout'
export async function generateStaticParams() {
return [{ id: '1' }, { id: '2' }]
}
async function getPost(params) {
const res = await fetch(`https://.../posts/${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 более подходящее, чем getStaticPaths, для новой модели в каталоге app. Префикс get заменён на более описательный generate, который теперь лучше подходит, так как getStaticProps и getServerSideProps больше не нужны. Суффикс Paths заменён на Params, что более подходит для вложенного маршрутирования с несколькими динамическими сегментами.
Замена значения fallback
В каталоге pages, свойство fallback, возвращаемое из getStaticPaths, используется для определения поведения страницы, которая не предварительно рендерится во время сборки. Это свойство может быть установлено в значение true для отображения страниц по умолчанию во время генерации страницы, false для отображения страницы 404 или blocking для генерации страницы в момент запроса.
// `pages` directory
export async function getStaticPaths() {
return {
paths: [],
fallback: 'blocking'
};
}
export async function getStaticProps({ params }) {
...
}
export default function Post({ post }) {
return ...
}В каталоге app свойство config.dynamicParams контролирует, как обрабатываются параметры вне generateStaticParams:
-
true: (по умолчанию) Динамические сегменты, не включенные вgenerateStaticParams, генерируются по требованию. -
false: Динамические сегменты, не включенные вgenerateStaticParams, вернут код ошибки 404.
Это заменяет опцию fallback: true | false | 'blocking' в getStaticPaths каталоге pages. Опция fallback: 'blocking' не включена в dynamicParams, потому что разница между 'blocking' и true несущественна при потоковой передаче.
// `app` directory
export const dynamicParams = true;
export async function generateStaticParams() {
return [...]
}
async function getPost(params) {
...
}
export default async function Post({ params }) {
const post = await getPost(params);
return ...
}С dynamicParams, установленным в true (по умолчанию), при запросе сегмента маршрута, который не был сгенерирован, он будет рендериться на сервере и кешироваться.
Инкрементальная статическая регенерация (getStaticProps с revalidate)
В каталоге pages, функция getStaticProps позволяет добавить поле revalidate для автоматической регенерации страницы через определённое время.
// `pages` directory
export async function getStaticProps() {
const res = await fetch(`https://.../posts`)
const posts = await res.json()
return {
props: { posts },
revalidate: 60,
}
}
export default function Index({ posts }) {
return (
<Layout>
<PostList posts={posts} />
</Layout>
)
}В каталоге app, получение данных с помощью fetch() может использовать revalidate, что будет кешировать запрос на указанное количество секунд.
// `app` directory
async function getPosts() {
const res = await fetch(`https://.../posts`, { next: { revalidate: 60 } })
const data = await res.json()
return data.posts
}
export default async function PostList() {
const posts = await getPosts()
return posts.map((post) => <div>{post.name}</div>)
}Маршруты API
Маршруты API по-прежнему работают в каталоге pages/api без изменений. Однако они были заменены Обработчиками маршрутов в каталоге app.
Обработчики маршрутов позволяют создавать пользовательские обработчики запросов для заданного маршрута с использованием API Веб-запросов Request и Response.
export async function GET(request: Request) {}Важно знать: Если вы ранее использовали маршруты API для вызова внешнего API из клиента, теперь вы можете использовать Серверные компоненты для безопасного получения данных. Узнайте больше о получении данных.
Шаг 7: Стиль
В каталоге pages глобальные стили ограничены только pages/_app.js. В каталоге app это ограничение снято. Глобальные стили можно добавить в любой макет, страницу или компонент.
Tailwind CSS
Если вы используете Tailwind CSS, вам необходимо добавить каталог app в ваш файл tailwind.config.js.
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}', // <-- Add this line
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
}Вам также необходимо импортировать свои глобальные стили в ваш файл app/layout.js.
import '../styles/globals.css'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}Узнайте больше о стилях с Tailwind CSS
Codemods
Next.js предоставляет преобразования Codemod для помощи в обновлении вашей кодовой базы, когда функция устарела. См. Codemods для получения дополнительной информации.
© 2024 Vercel, Inc.
Licensed under the MIT License.
https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration