Использование Preact библиотек

Fresh построен вокруг идеи серверного рендеринга без сборки и минимального JavaScript на клиенте. В основе клиентской части используется Preact — лёгкая и быстрая альтернатива React с совместимым API. Это означает, что практически все библиотеки, экосистема и паттерны React применимы и во Fresh, с учётом особенностей его архитектуры.

Ключевое отличие Fresh заключается в том, что JavaScript на клиенте загружается только для интерактивных компонентов, называемых islands. Все остальные части страницы остаются статическими HTML-фрагментами, отрендеренными на сервере.

Islands и подключение Preact-библиотек

Принцип islands-архитектуры

Islands — это Preact-компоненты, которые:

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

Любая Preact-библиотека используется внутри islands, а не на уровне всей страницы.

Пример структуры:

routes/
  index.tsx
islands/
  Counter.tsx
// 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>
  );
}

Этот компонент может использовать любые хуки и сторонние библиотеки, совместимые с Preact.

Использование Preact Hooks

Fresh поставляется с preact и preact/hooks из коробки. Поддерживаются все базовые хуки:

  • useState
  • useEffect
  • useMemo
  • useCallback
  • useRef
  • useContext
import { useEffect, useState } from "preact/hooks";

export function Timer() {
  const [time, setTime] = useState(0);

  useEffect(() => {
    const id = setInterval(() => setTime(t => t + 1), 1000);
    return () => clearInterval(id);
  }, []);

  return <span>{time}</span>;
}

Хуки выполняются только на клиенте, что важно учитывать при работе с API браузера.

Сторонние библиотеки состояния

Zustand

Zustand отлично подходит для Fresh благодаря отсутствию привязки к React DOM.

import { create } from "zustand";

export const useStore = create(set => ({
  count: 0,
  inc: () => set(state => ({ count: state.count + 1 })),
}));

Использование в island:

import { useStore } from "../store.ts";

export function Counter() {
  const { count, inc } = useStore();
  return <button onCl ick={inc}>{count}</button>;
}

Каждый island получает доступ к общему состоянию, если они используются на одной странице.

Context API

Контексты работают стандартным образом, но должны быть инициализированы внутри islands:

import { createContext } from "preact";
import { useContext } from "preact/hooks";

const ThemeContext = createContext("light");

export function ThemeProvider({ children }) {
  return (
    <ThemeContext.Provider value="dark">
      {children}
    </ThemeContext.Provider>
  );
}

UI-библиотеки и компоненты

Совместимость с React-библиотеками

Большинство библиотек, не использующих специфичный React DOM API, работают без изменений:

  • Headless UI
  • Radix UI (частично)
  • Preact Signals
  • date-fns
  • clsx

Библиотеки, жёстко привязанные к react-dom, требуют адаптации или не работают.

Preact Signals

Signals — рекомендуемый способ реактивного состояния во Fresh.

import { signal } from "@preact/signals";

const count = signal(0);

export function Counter() {
  return (
    <button onCl ick={() => count.value++}>
      {count}
    </button>
  );
}

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

  • автоматическая оптимизация ререндеров,
  • отсутствие хуков,
  • идеальная интеграция с Fresh.

Работа с формами и валидацией

Клиентская логика

Для интерактивных форм используются islands:

import { useState } from "preact/hooks";

export function LoginForm() {
  const [error, setError] = useState("");

  return (
    <form>
      <input type="email" />
      <button>Войти</button>
      {error && <p>{error}</p>}
    </form>
  );
}

Серверная обработка

Fresh поощряет перенос логики в обработчики POST:

export const handler = {
  async POST(req) {
    const data = await req.formData();
    return new Response("OK");
  },
};

UI-библиотеки применяются только к клиентской части, сервер остаётся чистым.

Анимации и визуальные эффекты

CSS-first подход

Fresh не навязывает JS-анимации. Большинство эффектов реализуются через CSS, а Preact используется только для переключения состояний.

JS-анимации

Поддерживаются библиотеки:

  • Motion One
  • GSAP (в islands)
  • AutoAnimate
import autoAnimate from "@formkit/auto-animate";
import { useEffect, useRef } from "preact/hooks";

export function List({ items }) {
  const ref = useRef(null);

  useEffect(() => {
    if (ref.current) autoAnimate(ref.current);
  }, []);

  return <ul ref={ref}>{items.map(i => <li>{i}</li>)}</ul>;
}

Ограничения и особенности

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

  • код islands выполняется дважды: на сервере и на клиенте,
  • доступ к window, document, localStorage — только внутри эффектов,
  • глобальное состояние между запросами не сохраняется.

Использование Preact-библиотек требует понимания, где заканчивается сервер и начинается клиент.

Оптимизация и размер бандла

Fresh автоматически:

  • удаляет неиспользуемый JS,
  • не бандлит код,
  • загружает islands по требованию.

Для Preact-библиотек рекомендуется:

  • избегать тяжёлых UI-фреймворков,
  • использовать ES-модули,
  • отдавать предпочтение signals вместо глобальных сторов.

Практический паттерн комбинирования

Типичная схема:

  • сервер: маршруты, загрузка данных, HTML,
  • islands: состояние, события, UI-библиотеки,
  • signals: глобальная реактивность,
  • CSS: визуальная часть.

Такой подход сохраняет производительность, масштабируемость и простоту поддержки кода во Fresh.