App Router vs Pages Router в Next.js
В Next.js существуют две системы маршрутизации: Pages Router (директория pages/) и App Router (директория app/). App Router появился в Next.js 13 и стал рекомендуемым подходом начиная с версии 13.4.
Ключевые отличия
| Аспект | Pages Router | App Router |
|---|---|---|
| Директория | pages/ | app/ |
| Компоненты по умолчанию | Клиентские | Серверные (RSC) |
| Загрузка данных | getServerSideProps, getStaticProps | async компоненты с 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 и работает стабильно, миграция не обязательна.
На собеседовании:
Оба роутера могут сосуществовать в одном проекте. Это позволяет мигрировать постепенно, по одному роуту за раз.