Настройка SSR окружения

Server-Side Rendering (SSR) в Mithril позволяет рендерить компоненты на сервере, возвращая готовый HTML-код клиенту. Это ускоряет время загрузки страницы и улучшает SEO, сохраняя при этом реактивность приложения после инициализации на клиенте. Основные этапы настройки SSR в Mithril включают конфигурацию серверной среды, подготовку компонентов для рендеринга и интеграцию с клиентским кодом.


Подключение Mithril на сервере

Mithril может использоваться в Node.js без каких-либо модификаций. Для SSR достаточно установить пакет mithril:

npm install mithril

После установки в Node.js импортируется модуль:

const m = require('mithril');

Для поддержки JSX можно подключить Babel и соответствующие пресеты, но стандартный синтаксис m() является достаточным для большинства сценариев SSR.


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

Метод m.render() позволяет генерировать HTML-строку на сервере. В отличие от клиентского рендеринга, где m.mount() связывает DOM с компонентом, на сервере создаётся чистый HTML.

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

const m = require('mithril');

const App = {
    view: () => m("div", [
        m("h1", "Привет, Mithril SSR!"),
        m("p", "Это пример серверного рендеринга.")
    ])
};

const html = m.render({}, m(App));
console.log(html);

В результате html будет содержать:

<div><h1>Привет, Mithril SSR!</h1><p>Это пример серверного рендеринга.</p></div>

Параметр {} в m.render() передаёт объект DOM для монтирования, но при SSR он может быть пустым, так как результат нужен как строка.


Интеграция с сервером Node.js

Для отдачи страниц с SSR часто используется Express:

const express = require('express');
const m = require('mithril');

const App = {
    view: () => m("div", [
        m("h1", "Mithril + SSR"),
        m("p", "Страница рендерится на сервере.")
    ])
};

const server = express();

server.get('/', (req, res) => {
    const html = m.render({}, m(App));
    res.send(`
        <!DOCTYPE html>
        <html lang="ru">
        <head>
            <meta charset="UTF-8">
            <title>Mithril SSR</title>
        </head>
        <body>
            ${html}
            <script src="/bundle.js"></script>
        </body>
        </html>
    `);
});

server.listen(3000, () => console.log('Сервер запущен на http://localhost:3000'));

В этом примере компонент App рендерится на сервере, а клиентский JavaScript (например, собранный через Webpack) подключается для дальнейшей реактивности.


Подготовка компонентов к SSR

Некоторые компоненты могут использовать клиентские API, недоступные на сервере, например window, document или таймеры. Для SSR необходимо условно проверять наличие этих объектов:

const TimerComponent = {
    oninit: vnode => {
        if (typeof window !== "undefined") {
            vnode.state.time = 0;
            vnode.state.interval = setInterval(() => {
                vnode.state.time++;
                m.redraw();
            }, 1000);
        }
    },
    onremove: vnode => {
        if (vnode.state.interval) clearInterval(vnode.state.interval);
    },
    view: vnode => m("div", `Время: ${vnode.state.time || 0}`)
};

Использование typeof window !== "undefined" позволяет безопасно рендерить компонент на сервере.


Гидратация клиентской части

После отдачи HTML с сервера клиент должен взять на себя управление DOM. В Mithril это делается с помощью m.mount() или m.startComputation()/m.endComputation(). Основной подход — монтировать тот же компонент на тот же корневой элемент:

const m = require('mithril');
const App = require('./App');

document.addEventListener("DOMContentLoaded", () => {
    m.mount(document.body, App);
});

При этом Mithril автоматически применяет diff и делает DOM реактивным, без полного перерисовывания страницы.


Оптимизация SSR

  1. Избегать тяжелых вычислений на сервере. В идеале серверный рендеринг должен возвращать HTML быстро, а динамическая логика выполняться на клиенте.
  2. Разделять код на серверный и клиентский. Можно использовать условные импорты или динамический require.
  3. Использовать кеширование HTML. Для статических страниц результат m.render() можно кэшировать и отдавать повторно.
  4. Минимизировать зависимость от DOM. Любой код, обращающийся к window или document, должен быть изолирован через проверки.

Совместимость с сборщиками

SSR-окружение легко интегрируется с Webpack или Vite:

  • На сервере Webpack-бандл обычно собирается с таргетом node.
  • На клиенте создается отдельный бандл, подключаемый через <script>.
  • Babel может использоваться для поддержки современных возможностей JavaScript и JSX.

Пример конфигурации Webpack для серверного бандла:

module.exports = {
    target: 'node',
    entry: './server.js',
    output: {
        filename: 'server.bundle.js',
        path: __dirname + '/dist'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: 'babel-loader'
            }
        ]
    }
};

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


Работа с маршрутами

Mithril имеет встроенный роутер, который может использоваться и на сервере:

const m = require('mithril');

const Home = { view: () => m("h1", "Главная") };
const About = { view: () => m("h1", "О сайте") };

const routes = {
    "/": Home,
    "/about": About
};

function renderRoute(url) {
    const component = routes[url] || { view: () => m("h1", "404") };
    return m.render({}, m(component));
}

При SSR маршруты выбираются на основе запроса и рендерятся соответствующие компоненты, что обеспечивает корректную отдачу HTML для каждой страницы.


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