Spec-Zone.ru › Next.js

Руководство по поэтапному внедрению маршрутизатора приложений

Это руководство поможет вам:

  • Обновить ваше приложение 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.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' в начало файла (перед любыми импортами).
    • Аналогично роутеру страниц, есть шаг оптимизации для предварительной рендеры компонентов клиента в статический 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 это ограничение снято. Глобальные стили можно добавить в любой макет, страницу или компонент.

  • CSS Модули
  • Tailwind CSS
  • Глобальные стили
  • CSS-в-JS
  • Внешние стили
  • Sass

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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API