App Router и серверные компоненты

App Router формирует модель маршрутизации, основанную на файловой структуре и композиции серверных и клиентских компонентов. Основная идея заключается в том, что каждая директория внутри app представляет сегмент маршрута, а файлы внутри неё определяют поведение рендера, загрузки данных и обработки состояний.

В основе лежит сегментирование пути. Каждый каталог — это логическая часть URL, а вложенность определяет иерархию интерфейса.

app/
 ├─ layout.js
 ├─ page.js
 ├─ dashboard/
 │   ├─ layout.js
 │   ├─ page.js
 │   └─ settings/
 │       └─ page.js

layout.js задаёт общий каркас сегмента и сохраняется между переходами, а page.js определяет содержимое конкретного маршрута.

Ключевая особенность — сохранение состояния layout при навигации внутри вложенных сегментов. Это уменьшает количество перерисовок и повышает предсказуемость UI.

Серверные компоненты как базовая модель исполнения

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

Серверные компоненты позволяют:

  • выполнять прямые запросы к базе данных
  • использовать секреты без экспозиции в браузер
  • уменьшать размер клиентского бандла
  • сокращать время гидратации

Пример серверного компонента:

export default async function Page() {
  const data = await fetch('https://api.example.com/items').then(r => r.json());

  return (
    <div>
      {data.map(item => (
        <div key={item.id}>{item.title}</div>
      ))}
    </div>
  );
}

Отсутствие состояния и эффектов в серверных компонентах формирует строгую модель детерминированного рендера.

Клиентские компоненты и граница гидратации

Любой компонент, который требует интерактивности, явно помечается директивой:

"use client";

После этого компонент становится частью клиентского бандла и подлежит гидратации в браузере.

Типичные случаи использования клиентских компонентов:

  • обработка событий (onClick, onChange)
  • использование useState, useEffect
  • доступ к browser API
  • локальная интерактивность UI

Граница между серверными и клиентскими компонентами является ключевым архитектурным ограничением. Импорт клиентского компонента в серверный автоматически расширяет клиентский граф зависимостей.

Композиция серверных и клиентских компонентов

App Router позволяет комбинировать оба типа компонентов в одном дереве:

  • серверный компонент выполняет загрузку данных
  • клиентский отвечает за интерактивность

Пример:

// Server Component
import Counter from './Counter';

export default async function Page() {
  const data = await getData();

  return (
    <div>
      <h1>{data.title}</h1>
      <Counter />
    </div>
  );
}
// Client Component
"use client";

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onCl ick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

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

Streaming и частичный рендеринг

Одним из ключевых механизмов App Router является потоковая передача HTML (streaming). Вместо ожидания полного построения страницы сервер отправляет готовые части UI по мере их готовности.

Это реализуется через Suspense:

import { Suspense } from 'react';

export default function Page() {
  return (
    <>
      <Header />
      <Suspense fallback={<Loading />}>
        <SlowComponent />
      </Suspense>
    </>
  );
}

Преимущества:

  • ускорение First Contentful Paint
  • параллельная загрузка данных
  • постепенное улучшение UX

layout.js как механизм устойчивого интерфейса

layout.js сохраняет состояние между навигациями внутри сегмента. Это отличает App Router от классической модели полной перерисовки страниц.

Характерные свойства:

  • persistent UI (например, sidebar)
  • отсутствие повторного монтирования при переходах
  • возможность вложенных layout-уровней

Пример:

export default function DashboardLayout({ children }) {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>{children}</main>
    </div>
  );
}

Loading и Error сегменты

Каждый маршрут может иметь специализированные файлы управления состояниями загрузки и ошибок:

  • loading.js — отображается во время ожидания данных
  • error.js — обрабатывает ошибки сегмента
// loading.js
export default function Loading() {
  return <div>Загрузка...</div>;
}
// error.js
"use client";

export default function Error({ error }) {
  return <div>Ошибка: {error.message}</div>;
}

Эти механизмы интегрированы в систему Suspense и позволяют локализовать состояния UI.

Data Fetching и кэширование

В серверных компонентах fetch расширен механизмом кэширования:

  • статический рендеринг по умолчанию
  • контроль через cache и next.revalidate
  • возможность ISR (Incremental Static Regeneration)
await fetch('https://api.example.com/data', {
  next: { revalidate: 60 }
});

Такой подход объединяет SSR и SSG в единую модель.

Server Actions как слой мутаций

Server Actions позволяют выполнять серверные операции без явного API слоя.

"use server";

export async function createItem(formData) {
  const title = formData.get('title');
  await db.insert({ title });
}

Использование в форме:

<form action={createItem}>
  <input name="title" />
  <button type="submit">Создать</button>
</form>

Особенности:

  • отсутствие ручного REST/GraphQL слоя
  • прямой вызов серверной логики
  • интеграция с формами и streaming

Параллельные и перехватывающие маршруты

App Router поддерживает сложные схемы навигации:

Parallel Routes

Позволяют отображать несколько независимых сегментов одновременно:

app/
 ├─ dashboard/
 │   ├─ @analytics/
 │   ├─ @team/
 │   └─ page.js

Intercepting Routes

Позволяют перехватывать переходы и отображать модальные окна без выхода из контекста страницы.

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

Архитектура App Router требует контроля границ между сервером и клиентом:

  • минимизация "use client"
  • перенос логики данных на сервер
  • изоляция интерактивных компонентов
  • сокращение клиентского графа зависимостей

Чем больше логики остаётся в серверных компонентах, тем меньше стоимость гидратации и выше производительность.

Модель выполнения и рендеринга

Процесс рендера включает несколько этапов:

  1. построение дерева маршрутов
  2. выполнение серверных компонентов
  3. формирование RSC payload
  4. streaming HTML
  5. гидратация клиентских компонентов

Эта модель позволяет совмещать серверную генерацию и клиентскую интерактивность без жёсткого разделения между SSR и SPA подходами.