Server-Side Rendering (SSR) позволяет рендерить React-приложение на сервере, что улучшает SEO, ускоряет первый рендер и повышает доступность контента для пользователей. TanStack Router предоставляет мощный способ организации маршрутизации в приложении, совместимый с SSR, за счёт своей декларативной модели маршрутов и асинхронной загрузки данных.
В основе SSR с TanStack Router лежат следующие принципы:
loader, которые выполняются до рендеринга компонента.Маршруты в TanStack Router строятся с использованием объекта
Route, где ключевые поля для SSR:
path — путь маршрута.component — React-компонент, который рендерится для
маршрута.loader — асинхронная функция для предварительной
загрузки данных на сервере.Пример определения маршрутов с loader:
import { Route } from '@tanstack/router';
import HomePage from './pages/HomePage';
import UserPage from './pages/UserPage';
const rootRoute = new Route({
path: '/',
component: HomePage,
loader: async () => {
const response = await fetch('https://api.example.com/home');
return response.json();
},
});
const userRoute = new Route({
path: '/user/:id',
component: UserPage,
loader: async ({ params }) => {
const response = await fetch(`https://api.example.com/user/${params.id}`);
return response.json();
},
});
export const routes = [rootRoute, userRoute];
Для SSR TanStack Router требует выполнения нескольких ключевых шагов на сервере:
matchRoutes или аналогичной
функции, чтобы определить активные маршруты для текущего
URL.loader функций перед
рендерингом.ReactDOMServer.renderToString.Пример использования с Express:
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { Router, RouterProvider } from '@tanstack/router';
import { routes } from './routes';
const app = express();
app.get('*', async (req, res) => {
const router = new Router({ routes });
// Определяем активные маршруты
const matches = router.matchRoutes(req.path);
// Загружаем данные для активных маршрутов
const dataPromises = matches.map(match => match.loader?.({ params: match.params }));
const data = await Promise.all(dataPromises);
// Генерируем HTML
const html = ReactDOMServer.renderToString(
<RouterProvider router={router} initialData={data} />
);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>SSR с TanStack Router</title>
</head>
<body>
<div id="root">${html}</div>
<script>
window.__INITIAL_DATA__ = ${JSON.stringify(data)};
</script>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
app.listen(3000);
После того как сервер отправил HTML с предварительно загруженными данными, клиент должен использовать эти данные для гидрации маршрутов и компонентов:
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import { RouterProvider, Router } from '@tanstack/router';
import { routes } from './routes';
const router = new Router({ routes, initialData: window.__INITIAL_DATA__ });
hydrateRoot(document.getElementById('root'), (
<RouterProvider router={router} />
));
Особенности клиентской гидрации:
initialData синхронизирует состояние между сервером и
клиентом.loader, если данные
уже были загружены на сервере.Для крупных приложений важно предусмотреть:
loader через
конструкции try/catch и возврат fallback-данных.React.lazy) для маршрутов с большим размером,
интегрированная с Suspense и SSR через
@loadable/component или аналог.Пример обработки ошибок в loader:
const userRoute = new Route({
path: '/user/:id',
component: UserPage,
loader: async ({ params }) => {
try {
const response = await fetch(`https://api.example.com/user/${params.id}`);
if (!response.ok) throw new Error('Failed to fetch user data');
return response.json();
} catch (error) {
return { error: error.message };
}
},
});
TanStack Router поддерживает вложенные маршруты, что особенно важно для SSR:
loader функции выполняются от корня к
потомкам.Пример вложенного маршрута с SSR:
const dashboardRoute = new Route({
path: '/dashboard',
component: DashboardLayout,
loader: async () => fetch('/api/dashboard').then(r => r.json()),
children: [
new Route({
path: 'stats',
component: StatsPage,
loader: async () => fetch('/api/stats').then(r => r.json()),
}),
],
});
В этом примере данные для DashboardLayout загружаются
перед StatsPage, а гидрация на клиенте сохраняет и
родительские, и дочерние данные.
Этот подход к SSR с TanStack Router обеспечивает строгую синхронизацию данных между сервером и клиентом, эффективное управление асинхронными запросами и удобное использование вложенной маршрутизации в больших React-приложениях.