Архитектурные паттерны

Fresh — это современный фреймворк для разработки веб-приложений на JavaScript/TypeScript, построенный поверх Deno. Он ориентирован на скорость, простоту и использование современных возможностей веб-платформы, включая рендеринг на стороне сервера (SSR) и потоковую доставку контента. Архитектурные паттерны в Fresh формируют основу организации приложений, обеспечивая масштабируемость, поддержку типов и эффективное управление состоянием.


Компонентная структура

В Fresh основная единица — компонент, который представляет собой функцию или модуль, возвращающий JSX-структуру. Каждый компонент:

  • Чисто декларирует структуру интерфейса.
  • Может содержать локальное состояние через хуки (useState, useEffect).
  • Легко переиспользуется в разных частях приложения.

Компоненты в Fresh организуются в папках routes и islands. Паттерн Islands Architecture позволяет рендерить статический HTML сервером, а интерактивные компоненты загружать отдельно на клиенте. Это снижает объем передаваемого JavaScript и ускоряет загрузку страниц.

Пример структуры проекта:

/routes
  index.tsx
  about.tsx
/islands
  Counter.tsx
  Modal.tsx
/components
  Header.tsx
  Footer.tsx
  • routes — страницы приложения, рендерятся сервером.
  • islands — интерактивные компоненты.
  • components — общие UI-блоки.

Серверный рендеринг (SSR)

Fresh использует SSR по умолчанию. Сервер генерирует HTML на основе маршрута и компонентов, а клиент загружает только минимальный JavaScript для интерактивности. Основные преимущества:

  • Быстрая первая отрисовка (First Contentful Paint).
  • SEO-дружественность.
  • Возможность потоковой передачи данных (streaming).

Пример SSR-компонента в Fresh:

import { Handlers } from "$fresh/server.ts";

export const handler: Handlers = {
  GET(req, ctx) {
    return ctx.render({ message: "Привет, Fresh!" });
  },
};

export default function HomePage({ data }: { data: { message: string } }) {
  return <h1>{data.message}</h1>;
}

Здесь ctx.render генерирует HTML на сервере с данными, которые передаются компоненту.


Паттерн Islands Architecture

Islands Architecture — ключевой подход в Fresh для гибридного рендеринга:

  • Статический HTML создается сервером.
  • Интерактивные островки подключаются только там, где нужен JavaScript.
  • Это снижает нагрузку на клиент и сокращает время загрузки.

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

export default function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onCl ick={() => setCount(count + 1)}>
      Счётчик: {count}
    </button>
  );
}

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

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

export default function HomePage() {
  return (
    <div>
      <h1>Главная страница</h1>
      <Counter />
    </div>
  );
}

Здесь только Counter будет интерактивным, а остальной HTML отрендерен сервером.


Маршрутизация и REST-паттерн

Fresh использует файловую маршрутизацию. Каждый файл в routes автоматически становится маршрутом. Для динамических маршрутов применяется синтаксис [param].tsx.

Пример динамического маршрута:

/routes
  posts/
    [id].tsx
export const handler: Handlers = {
  GET(req, ctx) {
    const { id } = ctx.params;
    return ctx.render({ postId: id });
  },
};

export default function Post({ data }: { data: { postId: string } }) {
  return <div>Пост №{data.postId}</div>;
}

Такой подход соответствует REST-подходу, где каждый ресурс имеет уникальный URL.


Управление состоянием

В Fresh состояние обычно локальное для островков (useState). Для глобального состояния рекомендуется:

  • Контекст React (createContext)
  • Использование store-паттернов, например, Zustand или Redux, адаптированных под Fresh.
  • Серверное состояние через SSR и передачу данных через ctx.render.

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

import { createContext } from "preact";
export const ThemeContext = createContext({ theme: "light" });

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


Паттерны асинхронных данных

Fresh поддерживает асинхронное получение данных через обработчики (Handlers). Доступны следующие подходы:

  • SSR с fetch: данные загружаются на сервере перед рендером.
  • Streaming: HTML отдается по мере получения данных.
  • Client-side fetch: интерактивные островки могут загружать данные после рендера.

Пример SSR с асинхронным fetch:

export const handler: Handlers = {
  async GET(req, ctx) {
    const res = await fetch("https://api.example.com/data");
    const json = await res.json();
    return ctx.render({ data: json });
  },
};

Организация проекта и модульность

Ключевые практики:

  • Разделение компонентов по функционалу: страницы, островки, UI-блоки.
  • Использование TypeScript для типизации props и данных.
  • Четкая структура маршрутов для масштабируемости.
  • Минимизация клиентского JavaScript через Islands Architecture.
  • Асинхронная загрузка данных через серверные обработчики.

Архитектурные паттерны Fresh направлены на создание легковесных, быстро загружаемых и SEO-дружественных веб-приложений, где сервер и клиент эффективно разделяют ответственность за рендеринг и интерактивность.