Первое приложение на Fresh

Fresh — это фреймворк для веб-разработки на JavaScript и TypeScript, ориентированный на максимально быстрые сайты, минимальный JavaScript на клиенте и нативную интеграцию с Deno. Его ключевая идея — islands architecture: JavaScript выполняется на клиенте только там, где это действительно необходимо.

Fresh работает поверх Deno, поэтому сначала требуется установленный Deno с поддержкой npm и TypeScript.

Создание нового проекта выполняется командой:

deno run -A -r https://fresh.deno.dev my-fresh-app

В процессе генерации создаётся структура проекта с готовой конфигурацией, примерами маршрутов и компонентами. Fresh не использует package.json и node_modules — все зависимости подтягиваются напрямую по URL или через deno.json.

Структура проекта

Базовая структура Fresh-приложения выглядит следующим образом:

my-fresh-app/
├─ routes/
│  ├─ index.tsx
│  └─ _app.tsx
├─ islands/
├─ components/
├─ static/
├─ deno.json
└─ main.ts

Основные директории

routes/ Файловая система маршрутов. Каждый файл автоматически становится HTTP-маршрутом.

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

components/ Обычные серверные компоненты без клиентского JavaScript.

static/ Статические файлы: изображения, CSS, шрифты.

Запуск сервера разработки

Приложение запускается командой:

deno task start

Fresh поднимает сервер с горячей перезагрузкой. Изменения в коде моментально отражаются в браузере.

Первый маршрут

Файл routes/index.tsx определяет обработку корневого пути /.

Пример минимального маршрута:

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

export default function Home(props: PageProps) {
  return (
    <main>
      <h1>Fresh</h1>
      <p>Первое приложение</p>
    </main>
  );
}

Компонент возвращает JSX и рендерится на сервере. В HTML, отправляемом клиенту, нет лишнего JavaScript.

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

Fresh всегда использует SSR. Компоненты:

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

Это поведение является стандартным и не требует конфигурации.

Layout и файл _app.tsx

Файл routes/_app.tsx применяется ко всем страницам и используется как глобальный layout.

Пример:

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

export default function App({ Component }: AppProps) {
  return (
    <html>
      <head>
        <title>Fresh App</title>
      </head>
      <body>
        <Component />
      </body>
    </html>
  );
}

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

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

По умолчанию Fresh не отправляет JavaScript клиенту. Для интерактивности используются острова — компоненты, которые гидратируются в браузере.

Пример острова

Файл 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 (
    <div>
      <Counter />
    </div>
  );
}

В этом случае:

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

Обработка HTTP-запросов

Маршруты могут возвращать не только JSX, но и обрабатывать HTTP-методы.

Пример GET-обработчика:

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

export const handler: Handlers = {
  GET(req) {
    return new Response("Hello from Fresh");
  },
};

Можно совмещать обработчики и JSX:

export const handler: Handlers = {
  GET(req, ctx) {
    return ctx.render({ time: Date.now() });
  },
};

export default function Page(props) {
  return <p>{props.data.time}</p>;
}

Работа с формами

Fresh отлично подходит для серверных форм без клиентского JS.

Пример обработки POST-запроса:

export const handler: Handlers = {
  async POST(req) {
    const form = await req.formData();
    const name = form.get("name");

    return new Response(`Привет, ${name}`);
  },
};

HTML-форма:

<form method="post">
  <input name="name" />
  <button>Отправить</button>
</form>

Форма работает полностью на сервере.

Статические файлы

Файлы из папки static/ доступны напрямую:

/static/style.css → http://localhost:8000/style.css

Подключение CSS:

<link rel="stylesheet" href="/style.css">

Импорты и алиасы

Fresh использует URL-импорты и алиасы:

  • $fresh/ — встроенные модули Fresh
  • preact — встроен по умолчанию
  • зависимости не устанавливаются вручную

Пример:

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

Конфигурация через deno.json

Файл deno.json определяет задачи, алиасы и разрешения:

{
  "tasks": {
    "start": "deno run -A --watch=static/,routes/ main.ts"
  }
}

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

Fresh проектировался с упором на:

  • нулевой JS по умолчанию
  • минимальный размер HTML
  • быструю Time To First Byte
  • отсутствие бандлинга

Бандлер, транспиляция и сборка не требуются. Код исполняется напрямую.

Итоговая модель приложения

Первое приложение на Fresh строится вокруг следующих принципов:

  • файловая маршрутизация
  • серверный рендеринг по умолчанию
  • клиентский JavaScript только в islands
  • простая работа с HTTP
  • тесная интеграция с Deno

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