Продвинутая разработка на Next.js v16: App Router, SSR и оптимизация

Курс для углубленного изучения Next.js v16 с фокусом на App Router, серверных компонентах и SEO. Вы освоите Server Actions, аутентификацию, Middleware и продвинутые техники оптимизации производительности для реальных проектов.

1. Архитектура App Router: Server Components и работа со специальными хуками (useServerInsertedHTML, usePathname)

Архитектура App Router: Server Components и работа со специальными хуками (useServerInsertedHTML, usePathname)

Next.js v16 продолжает развивать парадигму, заложенную в App Router, где основой архитектуры являются React Server Components (RSC). Понимание того, как серверные и клиентские компоненты взаимодействуют, а также умение управлять потоком данных и стилей через специальные хуки — критически важный навык для создания производительных приложений.

React Server Components (RSC) как стандарт

В директории app все компоненты по умолчанию являются Server Components. Это фундаментальный сдвиг по сравнению с классическим React или Pages Router в Next.js.

Преимущества серверного рендеринга по умолчанию

  • Нулевой размер бандла: Код серверных компонентов никогда не отправляется в браузер. Если вы используете тяжелую библиотеку для форматирования даты (например, moment или date-fns) внутри серверного компонента, она останется на сервере. Клиент получит только готовый HTML.
  • Прямой доступ к бэкенду: Вы можете обращаться к базе данных, файловой системе или внутренним микросервисам напрямую из компонента, без создания промежуточного API слоя.
  • Безопасность: Секретные ключи (API keys, токены базы данных) никогда не утекут на клиент, так как код исполняется в изолированной среде Node.js (или Edge Runtime).
  • Пример типичного серверного компонента:

    Клиентские компоненты и директива 'use client'

    Серверные компоненты не поддерживают интерактивность (onClick, onChange) и хуки React (useState, useEffect). Для этого используются Client Components.

    Чтобы превратить компонент в клиентский, необходимо добавить директиву 'use client' в самой первой строке файла. Это создает границу между серверным и клиентским кодом.

    !Корневой серверный компонент рендерит HTML и передает данные вниз. В середине дерева появляются 'use client' компоненты (листья интерактивности), которые гидратируются в браузере. Стрелки показывают поток данных от сервера к клиенту.

    Паттерн композиции: "Дырки" в клиентских компонентах

    Распространенная ошибка — импортировать серверный компонент внутрь клиентского. Это приведет к тому, что серверный компонент станет клиентским (попадет в бандл). Чтобы сохранить преимущества RSC, используйте паттерн children.

    Неправильно:

    Правильно:

    В правильном варианте ServerComponent рендерится на сервере, а его результат передается в ClientWrapper как проп children. Клиентский компонент отвечает только за расположение этого контента, но не за его генерацию.

    Хук usePathname: Навигация и активные состояния

    В App Router глобальный объект router из next/router больше не используется. Вместо этого навигация разделена на несколько хуков, доступных только в клиентских компонентах ('use client').

    Хук usePathname возвращает текущий путь URL. Это основной инструмент для создания активных ссылок в меню или условного рендеринга элементов интерфейса в зависимости от страницы.

    Важно помнить, что usePathname вызывает повторный рендеринг клиентского компонента при смене маршрута. Поэтому старайтесь использовать его в небольших изолированных компонентах (например, только в навигационной панели), а не в корневом Layout, чтобы избежать лишних перерисовок всего приложения.

    Хук useServerInsertedHTML: CSS-in-JS и потоковый рендеринг

    Одной из самых сложных задач при переходе на App Router стала интеграция библиотек CSS-in-JS (таких как styled-components, emotion или mantine). Проблема заключается в том, что RSC использует потоковую передачу (streaming) HTML. Браузер начинает получать и отображать контент еще до того, как весь React-код будет выполнен.

    Если стили генерируются на лету (runtime), может возникнуть эффект FOUC (Flash of Unstyled Content) — пользователь увидит "голый" HTML, который затем резко изменится после загрузки JS.

    Хук useServerInsertedHTML из next/navigation решает эту проблему. Он позволяет внедрять теги <style> в поток HTML в момент генерации серверных компонентов, до того как они попадут к клиенту.

    Этот хук работает только внутри клиентских компонентов, которые используются в качестве оберток (Registry) в корневом layout.tsx.

    Пример реализации CSS Registry

    Рассмотрим упрощенную схему интеграции библиотеки стилей:

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

  • Next.js рендерит дерево компонентов на сервере.
  • Когда он встречает useServerInsertedHTML, он выполняет колбэк.
  • Колбэк извлекает критические стили, необходимые для текущего куска HTML.
  • Стили вставляются в поток данных прямо перед соответствующим HTML-контентом.
  • Это гарантирует, что даже при медленном интернете пользователь сразу увидит стилизованный контент, так как CSS приходит вместе с HTML.

    Оптимизация и границы сериализации

    При передаче данных (props) от серверного компонента к клиентскому, данные должны быть сериализуемыми. Это означает, что они должны быть представимы в формате JSON.

    Что можно передавать: * Строки, числа, булевы значения. * Массивы и простые объекты. * null, undefined.

    Что нельзя передавать напрямую: * Функции (например, обработчики событий). * Экземпляры классов (кроме Date, который автоматически преобразуется в строку). * Сложные объекты с циклическими ссылками.

    Если вам нужно передать функцию (например, Server Action) в клиентский компонент, она должна быть импортирована из файла с директивой 'use server' или определена внутри серверного компонента как async-функция.

    Итоги

  • Server Components по умолчанию: В App Router весь код исполняется на сервере, если явно не указано иное. Это обеспечивает безопасность и производительность.
  • Граница 'use client': Используйте эту директиву только для компонентов, требующих интерактивности (state, effects, browser API).
  • Навигация: Хук usePathname заменяет router.pathname и работает только в клиентских компонентах для отслеживания URL.
  • Стилизация в SSR: Хук useServerInsertedHTML критически важен для корректной работы CSS-in-JS библиотек в среде потокового рендеринга, предотвращая мигание стилей.
  • Композиция: Передавайте серверные компоненты внутрь клиентских через проп children, чтобы избежать их ненужной конвертации в клиентский код.
  • 2. Мутации данных через Server Actions и управление состоянием с HTTP-only cookies

    Мутации данных через Server Actions и управление состоянием с HTTP-only cookies

    В традиционных React-приложениях (SPA) взаимодействие с сервером обычно строится по схеме: создание API-роута, вызов fetch на клиенте, обработка состояния загрузки и ошибок, а затем обновление локального стейта. Next.js App Router кардинально упрощает этот процесс с помощью Server Actions.

    Концепция Server Actions

    Server Actions — это асинхронные функции, которые выполняются на сервере, но могут быть вызваны из клиентских или серверных компонентов. Они позволяют обрабатывать отправку форм и мутации данных без создания отдельных API-эндпоинтов.

    Чтобы объявить Server Action, используется директива 'use server'. Она может быть размещена двумя способами:

  • В начале файла: Весь файл становится модулем серверных действий. Все экспортируемые функции могут быть вызваны клиентом.
  • Внутри асинхронной функции: Только эта конкретная функция становится серверным действием (полезно для замыканий внутри серверных компонентов).
  • Пример простой мутации

    Рассмотрим создание задачи. Вместо pages/api/todo.ts мы создаем файл действий:

    Использование в компоненте:

    Этот код работает даже без JavaScript в браузере (Progressive Enhancement), так как Next.js обрабатывает отправку формы как стандартный POST-запрос. При включенном JS происходит гидратация, и форма отправляется без перезагрузки страницы.

    !Процесс: Клиент отправляет форму -> Next.js делает POST запрос -> Server Action выполняется в Node.js -> БД обновляется -> revalidatePath обновляет кэш -> Клиент получает обновленный UI

    Управление состоянием через HTTP-only Cookies

    При работе с аутентификацией или сессиями критически важно безопасно хранить токены. localStorage доступен для JavaScript, что делает его уязвимым для XSS-атак. Стандартом безопасности является использование HTTP-only cookies.

    В Next.js App Router для работы с куки используется функция cookies из пакета next/headers. В версиях Next.js 15 и 16 эта функция является асинхронной (await cookies()).

    Установка куки в Server Action

    Рассмотрим сценарий входа в систему. Серверное действие проверяет данные и устанавливает сессионный токен.

    Ключевые параметры куки: * httpOnly: true: Запрещает доступ к куки через document.cookie на клиенте. Это блокирует кражу токена при XSS. * secure: true: Куки передаются только по HTTPS. * sameSite: 'lax': Защищает от CSRF-атак.

    Обработка ошибок и обратная связь: useActionState

    Для реальных приложений недостаточно просто отправить форму. Нам нужно отображать ошибки валидации или сообщения об успехе. Для этого используется хук useActionState (в более ранних версиях React Canary он назывался useFormState).

    Этот хук позволяет связать состояние компонента с результатом выполнения Server Action.

    1. Модификация Server Action

    Действие должно возвращать состояние, а не выбрасывать ошибку или делать редирект (если мы хотим показать ошибку на той же странице).

    Обратите внимание: первым аргументом теперь приходит prevState — предыдущее состояние формы.

    2. Использование в клиентском компоненте

    Инвалидация кэша и обновление UI

    Next.js агрессивно кэширует данные. После мутации (изменения данных в БД) клиентский кэш и серверный Data Cache могут содержать устаревшие данные. Чтобы обновить интерфейс, используются функции ревалидации.

    revalidatePath

    Функция revalidatePath(path, type?) очищает кэш для конкретного пути.

    * revalidatePath('/todos'): Обновит страницу списка задач. * revalidatePath('/todos', 'page'): Обновит только саму страницу, но не вложенные сегменты. * revalidatePath('/', 'layout'): Обновит все страницы, использующие корневой layout.

    revalidateTag

    Если при получении данных вы использовали теги (fetch(url, { next: { tags: ['collection'] } })), то в Server Action можно вызвать revalidateTag('collection'). Это более точечный инструмент, позволяющий обновить данные во всех местах приложения, где используется этот тег, независимо от URL.

    Безопасность Server Actions

    Server Actions — это публичные API-эндпоинты. Даже если они не видны в адресной строке, злоумышленник может вызвать их напрямую, зная идентификатор действия.

  • Аутентификация и Авторизация: Всегда проверяйте права пользователя внутри Server Action.
  • Валидация данных: Никогда не доверяйте FormData. Используйте библиотеки вроде Zod для проверки типов и форматов данных перед отправкой в БД.
  • CSRF: Next.js автоматически защищает Server Actions от CSRF-атак, проверяя заголовки Origin и Host.
  • Итоги

  • Server Actions заменяют традиционные API-роуты для мутаций, позволяя вызывать серверные функции прямо из компонентов.
  • HTTP-only cookies — единственный безопасный способ хранения сессионных токенов, предотвращающий доступ к ним через JavaScript.
  • Хук useActionState необходим для обработки ответов сервера, ошибок валидации и индикации загрузки в клиентских формах.
  • После изменения данных необходимо вызывать revalidatePath или revalidateTag, чтобы сбросить кэш Next.js и отобразить актуальную информацию.
  • Server Actions требуют явных проверок авторизации внутри тела функции, так как они доступны публично.