Разработка CSR (Client-Side Rendering) для веб-приложения
Client-Side Rendering — рендер интерфейса полностью в браузере. Сервер отдаёт минимальный HTML-скелет и JS-бандл, браузер выполняет код и строит DOM. Это архитектура SPA (Single Page Application): навигация без перезагрузки страницы, богатые интерактивные интерфейсы, состояние живёт в памяти клиента.
CSR — правильный выбор для закрытых приложений: дашборды, кабинеты, инструменты, платформы. Там, где SEO не нужен, зато нужен отзывчивый интерфейс с локальным состоянием.
Когда CSR лучше SSR
| Критерий | CSR | SSR |
|---|---|---|
| SEO для публичного контента | Плохо | Хорошо |
| Первая загрузка | Медленнее | Быстрее |
| Последующие переходы | Мгновенно | Каждый раз запрос |
| Сложное состояние на клиенте | Естественно | Усложняется |
| Серверные ресурсы | Браузер делает работу | Нужен сервер для рендера |
| Оффлайн-режим (PWA) | Возможен | Сложно |
Архитектура React SPA с Vite
src/
api/ # HTTP-клиенты и типы
components/ # Переиспользуемые компоненты
features/ # Feature-модули (auth, dashboard, reports)
auth/
components/
hooks/
store.ts
hooks/ # Глобальные хуки
lib/ # Утилиты, конфигурации
pages/ # Страницы = маршруты
router/ # Настройка TanStack Router
stores/ # Zustand stores
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
export default defineConfig({
plugins: [TanStackRouterVite(), react()],
resolve: { alias: { '@': '/src' } },
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['@tanstack/react-router'],
query: ['@tanstack/react-query'],
},
},
},
},
});
Роутинг с TanStack Router
// src/router/routes.ts
import { createRootRoute, createRoute, createRouter } from '@tanstack/react-router';
import { RootLayout } from '@/components/layouts/RootLayout';
const rootRoute = createRootRoute({ component: RootLayout });
const dashboardRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/dashboard',
component: lazy(() => import('@/pages/Dashboard')),
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' });
}
},
});
export const router = createRouter({
routeTree: rootRoute.addChildren([dashboardRoute, ...]),
context: { auth: undefined! },
});
TanStack Router — типобезопасный роутинг: параметры маршрутов, search-параметры и context типизированы на уровне TypeScript.
Управление серверным состоянием
TanStack Query для всех асинхронных данных:
// features/products/hooks/useProducts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { productsApi } from '@/api/products';
export function useProducts(filters: ProductFilters) {
return useQuery({
queryKey: ['products', filters],
queryFn: () => productsApi.getList(filters),
staleTime: 5 * 60 * 1000, // 5 минут до повторного запроса
placeholderData: (prev) => prev, // Не убирать старые данные при смене фильтров
});
}
export function useUpdateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: productsApi.update,
onSuccess: (updated) => {
// Обновить конкретный элемент в кэше без рефетча
queryClient.setQueryData(
['products', updated.id],
updated
);
// Инвалидировать список
queryClient.invalidateQueries({ queryKey: ['products'] });
},
});
}
Оптимизация бандла
CSR-приложения страдают от большого initial bundle. Стратегии:
Code splitting по маршрутам:
const DashboardPage = lazy(() => import('@/pages/Dashboard'));
const ReportsPage = lazy(() => import('@/pages/Reports'));
Анализ бандла:
npx vite-bundle-analyzer
Динамический импорт тяжёлых библиотек:
async function exportToExcel(data: Row[]) {
// xlsx загружается только при клике на "Экспорт"
const { utils, writeFile } = await import('xlsx');
const ws = utils.json_to_sheet(data);
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, 'Данные');
writeFile(wb, 'export.xlsx');
}
PWA и оффлайн-режим
// vite.config.ts — Vite PWA plugin
import { VitePWA } from 'vite-plugin-pwa';
VitePWA({
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com\/products/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'api-products',
expiration: { maxAgeSeconds: 24 * 60 * 60 },
},
},
],
},
})
Сроки реализации
- Неделя 1: Vite + TypeScript + Router, аутентификация (JWT/OAuth), структура проекта
- Неделя 2–3: основные страницы, TanStack Query для API, формы (React Hook Form + Zod)
- Неделя 4: оптимизация бандла, code splitting, тестирование (Vitest + Testing Library)
Для закрытых SPA-приложений с десятками маршрутов и сложной логикой CSR остаётся наиболее продуктивной архитектурой — никаких ограничений SSR, полный контроль над состоянием.







