App Router vs Pages Router в Next.js

В Next.js существуют две системы маршрутизации: Pages Router (директория pages/) и App Router (директория app/). App Router появился в Next.js 13 и стал рекомендуемым подходом начиная с версии 13.4.

Ключевые отличия

АспектPages RouterApp Router
Директорияpages/app/
Компоненты по умолчаниюКлиентскиеСерверные (RSC)
Загрузка данныхgetServerSideProps, getStaticPropsasync компоненты с fetch
Layouts_app.tsx, _document.tsxВложенные layout.tsx
Loading UIРучная реализацияloading.tsx из коробки
Ошибки_error.tsx глобальноerror.tsx на уровне роута
StreamingНетДа, через Suspense
Server ActionsНетДа

Pages Router

В Pages Router каждый файл в директории pages/ автоматически становится роутом. Данные загружаются через специальные функции:

// pages/problems/index.tsx
export async function getServerSideProps() {
  const res = await fetch('https://api.hackfrontend.com/problems')
  const problems = await res.json()
  return { props: { problems } }
}

export default function ProblemsPage({ problems }) {
  return (
    <ul>
      {problems.map(p => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  )
}

Layouts реализуются через _app.tsx, который оборачивает все страницы:

// pages/_app.tsx
export default function App({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

App Router

В App Router компоненты по умолчанию серверные. Данные загружаются прямо в компоненте:

// app/problems/page.tsx
import { db } from '@/lib/db'

export default async function ProblemsPage() {
  const problems = await db.problem.findMany()

  return (
    <ul>
      {problems.map(p => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  )
}

Layouts вложенные и сохраняют состояние при навигации:

// app/problems/layout.tsx
export default function ProblemsLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <div className="problems-container">
      <Sidebar />
      <main>{children}</main>
    </div>
  )
}

Что дает App Router

Серверные компоненты по умолчанию

Код серверных компонентов не попадает в клиентский бандл. Тяжелые зависимости остаются на сервере:

// app/docs/[slug]/page.tsx
import { MDXRemote } from 'next-mdx-remote/rsc'
import { getDocBySlug } from '@/lib/docs'

export default async function DocPage({
  params
}: {
  params: { slug: string }
}) {
  const doc = await getDocBySlug(params.slug)
  return <MDXRemote source={doc.content} />
}

Библиотека next-mdx-remote не отправляется клиенту. Именно так работает отображение статей на Hack Frontend.

Потоковый рендеринг

App Router поддерживает Streaming через файлы loading.tsx и React Suspense:

// app/dashboard/loading.tsx
export default function Loading() {
  return <DashboardSkeleton />
}

Пользователь видит скелетон сразу, а данные подгружаются по мере готовности.

Вложенные layouts

В Pages Router один глобальный layout. В App Router layouts вложенные и не перерендериваются при навигации между дочерними страницами:

Когда выбрать какой

App Router подходит для новых проектов. Он является рекомендуемым подходом и получает все новые фичи.

Pages Router продолжает поддерживаться. Если проект уже написан на Pages Router и работает стабильно, миграция не обязательна.

На собеседовании:

Оба роутера могут сосуществовать в одном проекте. Это позволяет мигрировать постепенно, по одному роуту за раз.

Полезные ресурсы

Связанные темы