Fresh — серверный фреймворк для Deno, ориентированный на минимизацию JavaScript на клиенте и строгую изоляцию интерактивности. Компоненты во Fresh по умолчанию рендерятся на сервере и не гидратируются в браузере. Адаптация компонентов заключается в осознанном разделении ответственности между серверным рендерингом и клиентским поведением, а также в умении встраивать интерактивность точечно, без разрушения общей архитектуры.
Ключевая идея: компонент должен оставаться серверным, пока нет прямой необходимости в клиентском JavaScript.
Любой JSX-компонент во Fresh является серверным, если он:
islandsПример серверного компонента:
export default function UserCard(props: { name: string }) {
return (
<div class="card">
<h3>{props.name}</h3>
</div>
);
}
Такой компонент:
Адаптация здесь заключается в проектировании компонентов так, чтобы логика оставалась чисто декларативной, без скрытой зависимости от клиента.
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>
);
}
Ошибкой является перенос всего компонента в islands,
если интерактивность нужна лишь частично. Адаптация должна происходить
через декомпозицию.
Неправильно:
islandsПравильно:
Fresh позволяет безопасно и эффективно передавать данные из серверного контекста в клиентский island через props. Данные сериализуются автоматически.
<Counter initial={data.start} />
export default function Counter(props: { initial: number }) {
const [count, setCount] = useState(props.initial);
}
Важно учитывать:
Адаптация заключается в предварительной обработке данных на сервере, чтобы клиентский компонент был максимально простым.
Иногда интерактивность требуется не всегда. Во Fresh отсутствует условная гидратация на уровне фреймворка, поэтому адаптация решается архитектурно.
Подходы:
Пример серверного выбора:
{isEditable ? <Editor /> : <Preview />}
Где Editor — island, а Preview — серверный
компонент.
Fresh не использует client-side media queries для логики, поэтому адаптация под устройство должна происходить:
Пример серверной адаптации:
const ua = req.headers.get("user-agent");
const isMobile = ua?.includes("Mobile");
Далее выбирается нужный компонент или вариант разметки. Это сохраняет нулевой JavaScript на клиенте.
Компоненты во Fresh легко переиспользуются между серверной и клиентской средой, если:
window,
documentХорошая практика — вынос чистых функций форматирования и вычислений в отдельные модули, используемые и сервером, и island-компонентами.
Fresh поддерживает layouts, которые позволяют
адаптировать компоненты на уровне структуры страницы. Это особенно
полезно для:
Компонент может быть одним и тем же, но обёрнут в разные layouts с разным поведением и стилями, без дублирования логики.
Адаптация компонентов во Fresh накладывает строгие ограничения:
Взамен получается:
Компонент считается корректно адаптированным, если его интерактивная часть изолирована, серверная логика самодостаточна, а клиентский код минимален и не влияет на остальную систему.