Remix и Fresh имеют принципиальные различия в подходе к рендерингу и
маршрутизации. Remix основан на React и использует концепцию
nested routes с серверными и клиентскими загрузками
данных через loader и action. Fresh, напротив,
построен на Deno и использует islands architecture, где
рендеринг по умолчанию выполняется на сервере, а интерактивные части
страницы выделяются как независимые “острова”.
Ключевое различие заключается в том, что в Fresh не требуется отдельный клиентский бандл для всего приложения. Каждая интерактивная компонента компилируется отдельно, что снижает объем загружаемого кода и ускоряет загрузку страниц.
В Remix проект структурирован вокруг папки routes, где
каждая страница или вложенный маршрут определяется отдельным файлом.
Fresh использует папку routes аналогично, но каждая
страница может содержать компонент Island, который
добавляет интерактивность без полной загрузки React на клиент.
Пример миграции маршрута:
Remix:
// app/routes/index.jsx
import { json, LoaderFunction } from "@remix-run/node";
export const loader: LoaderFunction = async () => {
return json({ message: "Привет, Remix!" });
};
export default function Index() {
const data = useLoaderData();
return <h1>{data.message}</h1>;
}
Fresh:
// routes/index.tsx
import { Handlers, PageProps } from "$fresh/server.ts";
export const handler: Handlers = {
GET(req, ctx) {
return ctx.render({ message: "Привет, Fresh!" });
},
};
export default function Home({ data }: PageProps) {
return <h1>{data.message}</h1>;
}
Разница в том, что Fresh использует серверный рендеринг
напрямую через Handlers, а данные передаются через
объект data.
В Remix формы могут использовать action для обработки
POST-запросов и мутаций данных. В Fresh формы обрабатываются через
HTTP-обработчики (Handlers) или
стандартные API-эндпойнты. Пример POST-запроса:
// routes/contact.tsx
import { Handlers, PageProps } from "$fresh/server.ts";
export const handler: Handlers = {
POST(req, ctx) {
const formData = await req.formData();
const name = formData.get("name");
return new Response(`Принято имя: ${name}`);
},
};
export default function Contact() {
return (
<form method="POST">
<input name="name" type="text" />
<button type="submit">Отправить</button>
</form>
);
}
Все обработчики Fresh работают на сервере и возвращают Response, что упрощает миграцию, так как логика не разделяется на клиентскую и серверную части.
В Remix интерактивность реализуется через React-компоненты с состоянием и эффектами. Fresh предлагает острова, которые включаются только там, где нужна интерактивность.
Пример интерактивного острова:
// 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>
);
}
Использение на странице:
// routes/index.tsx
import Counter from "../islands/Counter.tsx";
export default function Home() {
return (
<>
<h1>Пример острова</h1>
<Counter />
</>
);
}
Такая архитектура уменьшает объем JavaScript на клиенте, так как остальные элементы страницы остаются статическими.
Remix активно использует loader и
useLoaderData для загрузки данных, а Fresh применяет
контроллеры в Handlers и передает данные в
компонент через PageProps. Для глобального состояния и
кэширования можно использовать useState внутри островов или
сторонние state-менеджеры, но важно помнить, что большая часть
рендеринга остаётся серверной.
Dynamically generated routes в Remix — это файлы с квадратными
скобками [id].jsx. В Fresh аналог реализуется через
динамические сегменты в имени файла
[id].tsx.
Пример:
// routes/posts/[id].tsx
import { Handlers, PageProps } from "$fresh/server.ts";
export const handler: Handlers = {
GET(req, ctx) {
const { id } = ctx.params;
return ctx.render({ id });
},
};
export default function Post({ data }: PageProps) {
return <h1>Пост {data.id}</h1>;
}
Контекст ctx.params напрямую предоставляет параметры
маршрута без необходимости использовать отдельные хуки.
Fresh позволяет использовать Deno KV, сторонние API или
fetch прямо в Handlers. Для кэширования можно
применять заголовки HTTP (Cache-Control) или
middleware-подобные функции.
Пример кэширования:
export const handler: Handlers = {
GET(req, ctx) {
const headers = new Headers();
headers.set("Cache-Control", "max-age=60");
return new Response("Данные закэшированы на 60 секунд", { headers });
},
};
Это упрощает управление данными по сравнению с Remix, где приходится комбинировать клиентский и серверный кэш.
Большинство React-библиотек можно использовать внутри островов. При этом важно учитывать, что острова используют Preact, поэтому некоторые специфичные React-хуки или контексты могут требовать адаптации.
routes Fresh с
обработчиком Handlers.islands для
минимизации клиентского кода.ctx.params.Handlers,
возвращая Response.Такая структура обеспечивает чистый серверный рендеринг с минимальной загрузкой JavaScript на клиенте и полностью меняет подход к построению приложений по сравнению с Remix.