Скажу сразу: Alpine.js — это реактивность, но в мини-формате. Отлично работает на статике и в простых SPA. Но как только начинаешь говорить «серверный рендеринг», появляется легкая морока.

Почему? Alpine изначально живёт в браузере. На сервере он не считает состояния, не рендерит события, не ставит x-data и x-on сам. Сервер должен просто отдать правильный HTML, а Alpine потом оживит его. Иначе будешь сидеть и ломать голову, почему интерактивность не работает.

Шаг 1: Установка Alpine.js

Если npm в проекте — ставим командой:

npm install alpinejs

Да, банально. Если используешь CDN — просто вставь <script src="https://unpkg.com/alpinejs" defer></script> в HTML. Главное, чтобы Alpine загружался после DOM.

Шаг 2: Подготовка серверного HTML

Сервер должен отдавать HTML с нужными атрибутами:

  • x-data — состояние компонента
  • x-bind — бинды
  • x-on — события

Без них Alpine просто не знает, что делать.

Пример:

<div x-data="{ count: 0 }">
    <button x-on:click="count++">+</button>
    <span x-text="count"></span>
</div>

На сервере это просто статический HTML. Alpine оживляет его на клиенте.

Шаг 3: Инициализация на клиенте

После того как сервер отдал HTML, нужно стартануть Alpine:

<script src="/path/to/alpine.js" defer></script>
<script>
    document.addEventListener("DOMContentLoaded", () => { Alpine.start(); });
</script>

defer важен, иначе Alpine попытается работать до того, как DOM загрузился — и будут ошибки.

Пример на Node.js + Express

Предположим, есть простой сервер:

const express = require('express');
const app = express();
const fs = require('fs');
const path = require('path');

app.get('/', (req, res) => {
  fs.readFile(path.join(__dirname, 'index.html'), 'utf8', (err, data) => {
    if (err) return res.status(500).send('Server Error');

    const alpineScript = '<script src="/path/to/alpine.js" defer></script>';
    const alpineInit = '<script>document.addEventListener("DOMContentLoaded", () => { Alpine.start(); });</script>';
    const htmlWithAlpine = data.replace('</body>', `${alpineScript}${alpineInit}</body>`);

    res.send(htmlWithAlpine);
  });
});

app.listen(3000, () => console.log('Server running on port 3000'));

Суть: сервер читает HTML, вставляет скрипт Alpine.js и инициализацию, отдает браузеру. Всё. Никаких магических SSR-фич Alpine на сервере нет.

Лайфхаки и подводные камни

  • Состояние на сервере не считает Alpine. Всё, что нужно для интерактивности — в HTML или потом через JS.
  • Динамические данные лучше вставлять через шаблонизатор (Blade, EJS, Pug) или через JSON, который Alpine подхватывает.
  • События до загрузки DOM ломают всё. Всегда defer и DOMContentLoaded.
  • SSR в Alpine — это условное SSR. Ты отдаёшь HTML с начальными значениями, а Alpine делает интерактивность уже в браузере. Настоящий SSR как в Vue/Nuxt или React/Next.js Alpine не умеет.

Итог

Alpine.js с серверным рендерингом — это всегда компромисс. Ты не получаешь полноценного SSR, но можешь отдавать HTML, который Alpine потом оживит на клиенте.

Если хочешь настоящего SSR с реактивностью — придётся смотреть на Vue, React или Svelte. Alpine — это лёгкая, простая и удобная штука для небольших проектов или для добавления интерактивности к статике.