Адаптация компонентов

Fresh — серверный фреймворк для Deno, ориентированный на минимизацию JavaScript на клиенте и строгую изоляцию интерактивности. Компоненты во Fresh по умолчанию рендерятся на сервере и не гидратируются в браузере. Адаптация компонентов заключается в осознанном разделении ответственности между серверным рендерингом и клиентским поведением, а также в умении встраивать интерактивность точечно, без разрушения общей архитектуры.

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


Серверные компоненты как основа

Любой JSX-компонент во Fresh является серверным, если он:

  • расположен вне каталога islands
  • не использует хуки состояния или эффекты
  • не зависит от браузерных API

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

export default function UserCard(props: { name: string }) {
  return (
    <div class="card">
      <h3>{props.name}</h3>
    </div>
  );
}

Такой компонент:

  • полностью отрисовывается на сервере
  • не попадает в клиентский бандл
  • не требует гидратации

Адаптация здесь заключается в проектировании компонентов так, чтобы логика оставалась чисто декларативной, без скрытой зависимости от клиента.


Islands Architecture и локальная интерактивность

Fresh реализует архитектуру «островов»: интерактивные компоненты изолируются и подключаются только там, где это необходимо. Для этого используется каталог islands.

Компонент, перенесённый в islands, автоматически становится клиентским и гидратируется.

// islands/Counter.tsx
import { useState } from "preact/hooks";

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

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

Адаптация серверной разметки под island-компонент обычно происходит через композицию:

import Counter from "../islands/Counter.tsx";

export default function Page() {
  return (
    <section>
      <h1>Статистика</h1>
      <Counter />
    </section>
  );
}

Границы адаптации: что не должно попадать в island

Ошибкой является перенос всего компонента в islands, если интерактивность нужна лишь частично. Адаптация должна происходить через декомпозицию.

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

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

Правильно:

  • выделение минимального интерактивного фрагмента
  • передача серверных данных через props
  • отсутствие fetch-запросов в island без крайней необходимости

Передача данных из сервера в адаптированный компонент

Fresh позволяет безопасно и эффективно передавать данные из серверного контекста в клиентский island через props. Данные сериализуются автоматически.

<Counter initial={data.start} />
export default function Counter(props: { initial: number }) {
  const [count, setCount] = useState(props.initial);
}

Важно учитывать:

  • props должны быть сериализуемыми
  • нельзя передавать функции или классы
  • объём данных напрямую влияет на размер HTML

Адаптация заключается в предварительной обработке данных на сервере, чтобы клиентский компонент был максимально простым.


Условная адаптация компонентов

Иногда интерактивность требуется не всегда. Во Fresh отсутствует условная гидратация на уровне фреймворка, поэтому адаптация решается архитектурно.

Подходы:

  • серверное ветвление: подключение island только при необходимости
  • разделение на два компонента: статический и интерактивный
  • управление через маршруты или параметры запроса

Пример серверного выбора:

{isEditable ? <Editor /> : <Preview />}

Где Editor — island, а Preview — серверный компонент.


Адаптация компонентов под разные устройства

Fresh не использует client-side media queries для логики, поэтому адаптация под устройство должна происходить:

  • через CSS
  • через серверный анализ заголовков запроса
  • через разные маршруты или layouts

Пример серверной адаптации:

const ua = req.headers.get("user-agent");
const isMobile = ua?.includes("Mobile");

Далее выбирается нужный компонент или вариант разметки. Это сохраняет нулевой JavaScript на клиенте.


Повторное использование компонентов при адаптации

Компоненты во Fresh легко переиспользуются между серверной и клиентской средой, если:

  • отсутствуют прямые вызовы window, document
  • логика отделена от представления
  • состояние инкапсулировано

Хорошая практика — вынос чистых функций форматирования и вычислений в отдельные модули, используемые и сервером, и island-компонентами.


Адаптация через layouts и partials

Fresh поддерживает layouts, которые позволяют адаптировать компоненты на уровне структуры страницы. Это особенно полезно для:

  • разных ролей пользователя
  • административных интерфейсов
  • A/B-вариантов

Компонент может быть одним и тем же, но обёрнут в разные layouts с разным поведением и стилями, без дублирования логики.


Ограничения и архитектурные последствия

Адаптация компонентов во Fresh накладывает строгие ограничения:

  • нельзя полагаться на клиентское состояние как на источник истины
  • нельзя использовать привычные SPA-паттерны
  • нельзя «постепенно» гидратировать всё приложение

Взамен получается:

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

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