Серверный рендеринг по умолчанию

Fresh — это веб-фреймворк для Deno, в основе которого лежит строгий и последовательно реализованный принцип: HTML генерируется на сервере всегда, если нет явной необходимости делать иначе. В отличие от большинства современных JavaScript-фреймворков, где серверный рендеринг является опцией или надстройкой, в Fresh он выступает базовым режимом работы.

Этот подход влияет на всю архитектуру приложения: маршрутизацию, загрузку JavaScript, работу с состоянием и производительность.


Что означает «серверный рендеринг по умолчанию»

При каждом HTTP-запросе сервер Fresh:

  1. Определяет маршрут.
  2. Выполняет соответствующий обработчик.
  3. Генерирует HTML на сервере.
  4. Отправляет полностью готовую разметку клиенту.

Браузер получает документ, который уже содержит весь необходимый HTML для отображения страницы. JavaScript на клиенте не требуется для первичного отображения.

Это принципиально отличает Fresh от SPA-подхода, где браузер сначала загружает JavaScript-бандл, а затем строит интерфейс.


Рендеринг компонентов на сервере

Fresh использует Preact для описания UI, но рендеринг компонентов происходит на сервере:

export default function Home() {
  return <h1>Hello Fresh</h1>;
}

Этот компонент:

  • выполняется в среде Deno,
  • преобразуется в HTML-строку,
  • включается в HTTP-ответ.

На клиент отправляется уже отрендеренный <h1>Hello Fresh</h1>, а не описание компонента или виртуальное дерево.


Отсутствие клиентского JavaScript по умолчанию

Ключевая особенность Fresh — нулевой JavaScript на клиенте, если явно не указано обратное.

  • Компоненты, используемые в маршрутах, не гидратируются автоматически.
  • В браузер не отправляется Preact runtime.
  • Нет глобального бандла приложения.

HTML работает сам по себе: ссылки, формы, навигация через обычные HTTP-запросы.

Это означает:

  • минимальный размер ответа,
  • мгновенный First Contentful Paint,
  • отсутствие блокирующей загрузки JS.

Изолированная гидратация через islands

Fresh допускает клиентскую интерактивность, но строго локализованную. Для этого используется концепция islands.

Island — это компонент, который:

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

Пример:

// 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>
  );
}

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

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

export default function Page() {
  return <Counter />;
}

Все остальные части страницы остаются чистым HTML без JavaScript.


SSR и маршрутизация

Маршруты в Fresh соответствуют файловой структуре:

routes/
 ├─ index.tsx
 ├─ blog/
 │   └─ [slug].tsx

Каждый маршрут — это серверный модуль, который может:

  • обращаться к базе данных,
  • читать файлы,
  • вызывать внешние API,
  • использовать секреты окружения.

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

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

export const handler: Handlers = {
  async GET(req, ctx) {
    const data = await fetchData();
    return ctx.render(data);
  },
};

export default function Page(props) {
  return <pre>{JSON.stringify(props.data)}</pre>;
}

Данные загружаются на сервере, а HTML формируется сразу с ними.


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

В SPA и гибридных фреймворках часто возникает ситуация:

  • код выполняется на сервере при SSR,
  • затем выполняется повторно в браузере при гидратации.

Fresh избегает этого:

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

Это снижает количество ошибок, связанных с различиями окружений, и упрощает отладку.


Влияние на производительность

Серверный рендеринг по умолчанию даёт измеримые преимущества:

  • TTFB минимален, так как нет ожидания сборки JS-бандла.
  • FCP происходит сразу после получения HTML.
  • CLS отсутствует, так как разметка окончательная.
  • JS execution time практически нулевой без islands.

Fresh хорошо масштабируется на edge-платформах благодаря короткому времени жизни запросов и отсутствию тяжёлых клиентских бандлов.


SEO и доступность

Так как HTML формируется на сервере:

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

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


Работа с формами и навигацией

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

<form method="post">
  <input name="email" />
  <button>Send</button>
</form>

POST-запрос обрабатывается сервером, страница перерендеривается и возвращается клиенту. Это:

  • не требует JS,
  • корректно работает без гидратации,
  • предсказуемо и надёжно.

Навигация по <a href> также приводит к полноценному серверному рендерингу следующей страницы.


Контраст с CSR и гибридными подходами

Характеристика Fresh Типичный SPA
Рендеринг Серверный Клиентский
JS по умолчанию Нет Да
Гидратация Только islands Вся страница
Бандлы Нет глобальных Крупные
SEO Из коробки Через SSR

Fresh возвращает разработку к классической модели веба, добавляя интерактивность точечно, а не повсеместно.


Архитектурное следствие

Серверный рендеринг по умолчанию в Fresh — не оптимизация, а фундамент. Он:

  • диктует структуру проекта,
  • ограничивает избыточную интерактивность,
  • поощряет простые и быстрые решения,
  • снижает когнитивную нагрузку при разработке.

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