Изоморфные приложения (или универсальные) — это приложения, способные рендериться как на стороне сервера, так и на стороне клиента. В контексте Riot.js такой подход позволяет ускорить загрузку страницы, улучшить SEO и обеспечить плавный переход между страницами без полной перезагрузки.
1. Разделение логики: Изоморфное приложение строится вокруг общей бизнес-логики, которая может выполняться как на сервере, так и на клиенте. Это требует строгого разделения слоёв:
2. Серверный рендеринг (SSR) в Riot.js: Riot.js
поддерживает SSR через функцию render из пакета
riot. Пример рендеринга компонента на сервере:
import { render } from 'riot';
import App from './components/App.riot';
function renderPage(data) {
const html = render(App, { data });
return `
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Изоморфное приложение на Riot.js</title>
</head>
<body>
<div id="app">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`;
}
Здесь компонент App получает данные на сервере и
возвращает HTML, который сразу отображается в браузере.
После серверного рендеринга необходимо “гидратировать” компоненты на клиенте — привязать их к уже существующему DOM, чтобы события и реактивные обновления заработали:
import { mount } from 'riot';
import App from './components/App.riot';
const data = window.__INITIAL_DATA__;
mount(App, { data }, '#app');
window.__INITIAL_DATA__ — это объект с данными,
переданный с сервера, что позволяет избежать повторного запроса
данных.
Изоморфные приложения часто используют маршрутизацию. В Riot.js популярны следующие подходы:
riot-router или сторонних библиотек. Компоненты
подгружаются динамически без перезагрузки страницы.Пример маршрутизации на Express:
import express from 'express';
import { render } from 'riot';
import App from './components/App.riot';
const app = express();
app.get('*', async (req, res) => {
const data = await fetchData(req.url);
const html = render(App, { data });
res.send(`
<!DOCTYPE html>
<html lang="ru">
<head><title>Riot SSR</title></head>
<body>
<div id="app">${html}</div>
<script>window.__INITIAL_DATA__ = ${JSON.stringify(data)}</script>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
app.listen(3000);
В изоморфных приложениях состояние компонентов должно быть изолированным и сериализуемым. Для этого часто используют паттерн store:
// store.js
export default function createStore(initialState = {}) {
let state = initialState;
const listeners = new Set();
return {
getState: () => state,
setState: (newState) => {
state = { ...state, ...newState };
listeners.forEach(fn => fn(state));
},
subscribe: (fn) => listeners.add(fn),
unsubscribe: (fn) => listeners.delete(fn)
};
}
На сервере создаётся отдельный store для каждого запроса, чтобы избежать совместного состояния между пользователями. На клиенте store инициализируется данными с сервера.
Для уменьшения размера бандла и ускорения загрузки применяются техники code splitting и lazy loading компонентов:
import { mount } from 'riot';
async function loadComponent() {
const module = await import('./components/LazyComponent.riot');
mount(module.default, {}, '#lazy');
}
document.querySelector('#load-btn').addEventListener('click', loadComponent);
window и document напрямую на
сервере.Изоморфный подход в Riot.js позволяет создавать быстрые, SEO-дружелюбные приложения с плавным пользовательским интерфейсом, минимизируя задержку между сервером и клиентом. Правильная архитектура и соблюдение правил SSR обеспечивает масштабируемость и поддерживаемость проекта.