Как работает Server-Side Rendering (SSR) в Next.js

SSR (Server-Side Rendering) это стратегия, при которой HTML генерируется на сервере при каждом запросе пользователя. В отличие от SSG, страница не создается заранее, а формируется в реальном времени.

Как это работает

1

Пользователь делает запрос

Браузер отправляет запрос на сервер. Например, пользователь открывает страницу профиля на Hack Frontend.

2

Сервер рендерит страницу

Next.js выполняет серверный компонент: обращается к базе данных, проверяет авторизацию, собирает данные и генерирует HTML.

3

HTML отправляется клиенту

Пользователь получает готовый HTML с актуальными данными. Затем React гидратирует страницу на клиенте, делая ее интерактивной.

SSR в App Router

В App Router SSR включается, когда компонент использует динамические данные. Достаточно указать cache: 'no-store' в fetch-запросе или использовать динамические функции (cookies(), headers()):

// app/profile/page.tsx
import { cookies } from 'next/headers'
import { db } from '@/lib/db'

export default async function ProfilePage() {
  const session = cookies().get('session')
  if (!session) return <LoginPrompt />

  const user = await db.user.findUnique({
    where: { sessionId: session.value }
  })

  return (
    <div>
      <h1>Привет, {user.name}!</h1>
      <p>Решено задач: {user.solvedProblems.length}</p>
    </div>
  )
}

Использование cookies() автоматически делает эту страницу динамической (SSR). Next.js понимает, что результат зависит от запроса и не может быть закеширован.

Явное указание динамического рендеринга

Можно явно отключить кеширование для fetch-запросов:

// app/feed/page.tsx
export default async function FeedPage() {
  const res = await fetch('https://api.hackfrontend.com/feed', {
    cache: 'no-store'
  })
  const posts = await res.json()

  return <FeedList posts={posts} />
}

Или задать режим на уровне всего сегмента роута:

// app/dashboard/layout.tsx
export const dynamic = 'force-dynamic'

export default function DashboardLayout({
  children
}: {
  children: React.ReactNode
}) {
  return <div className="dashboard">{children}</div>
}

SSR в Pages Router (Legacy)

В Pages Router для SSR используется getServerSideProps:

// pages/profile.tsx
export async function getServerSideProps(context) {
  const { req } = context
  const session = req.cookies.session

  if (!session) {
    return { redirect: { destination: '/auth/login', permanent: false } }
  }

  const user = await db.user.findUnique({
    where: { sessionId: session }
  })

  return { props: { user } }
}

export default function ProfilePage({ user }) {
  return (
    <div>
      <h1>Привет, {user.name}!</h1>
      <p>Решено задач: {user.solvedProblems.length}</p>
    </div>
  )
}

Когда использовать SSR

Подходит:

  • Страницы с персонализацией (профиль, настройки, дашборд)
  • Контент, зависящий от cookies или заголовков (авторизация, A/B тесты)
  • Данные, которые должны быть актуальными на момент запроса
  • Страницы с часто меняющимся контентом, где ISR недостаточно

Не подходит:

  • Статический контент (документация, блог). SSG будет быстрее и дешевле.
  • Страницы, где допустимо показать данные с задержкой. ISR будет эффективнее.

SSR vs SSG

SSGSSR
Когда генерируетсяПри сборкеПри каждом запросе
Скорость ответаМгновенная (CDN)Зависит от сервера
Актуальность данныхНа момент сборкиВсегда актуальные
Нагрузка на серверНетПри каждом запросе
ПерсонализацияНетДа

Важно:

SSR увеличивает Time to First Byte (TTFB), потому что сервер тратит время на рендеринг перед отправкой ответа. Если страница делает тяжелые запросы к БД, пользователь будет ждать. Используй SSR осознанно, а не по умолчанию.

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

В App Router Next.js поддерживает потоковую передачу HTML. Это позволяет отправлять части страницы по мере их готовности, не дожидаясь завершения всех запросов:

// app/dashboard/page.tsx
import { Suspense } from 'react'

export default function DashboardPage() {
  return (
    <div>
      <h1>Дашборд</h1>
      <Suspense fallback={<p>Загрузка статистики...</p>}>
        <UserStats />
      </Suspense>
      <Suspense fallback={<p>Загрузка активности...</p>}>
        <RecentActivity />
      </Suspense>
    </div>
  )
}

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

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

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