Server-Side Rendering (SSR) в Next.js часто преподносится как «просто добавь getServerSideProps
», но реальные проекты сталкиваются с граблями, которые не видны в документации. Рассмотрим, как превратить базовую реализацию SSR в оптимальное решение, готовое для продакшена.
Проблема: Наивная реализация SSR
Типичный пример:
export async function getServerSideProps(context) {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } };
}
Этот код нарушает три ключевых принципа production-решений:
- Нет обработки ошибок
- Отсутствие контроля времени выполнения
- Игнорирование кэширования
1. Обработка ошибок за пределами try/catch
Для SSR критически важно гарантировать рендеринг хотя бы базовой структуры страницы при сбоях API. Решение — двухуровневая обработка:
async function fetchDataWithFallback(url, fallback = {}) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`${res.status}: ${res.statusText}`);
clearTimeout(timeoutId);
return await res.json();
} catch (error) {
console.error(`Data fetch failed: ${error.message}`);
return fallback;
}
}
Использование в getServerSideProps
:
export async function getServerSideProps() {
const [mainData, secondaryData] = await Promise.all([
fetchDataWithFallback(API_ENDPOINTS.main, DEFAULT_MAIN_DATA),
fetchDataWithFallback(API_ENDPOINTS.secondary, {}),
]);
return {
props: {
mainData,
secondary: secondaryData?.results ?? [],
}
};
}
2. Контроль времени выполнения
При использовании SSR с TTV (Time-To-Visual) больше 2 секунд вы рискуете получить penalization в SEO. Для мониторинга добавьте кастомные метрики:
// pages/_app.js
export function reportWebVitals(metric) {
if (metric.name === 'Next.js-ssr') {
analytics.track('SSR_DURATION', {
duration: metric.value,
path: window.location.pathname,
});
}
}
Оптимизируйте тяжелые операции:
- Выносите не критичные для первого рендера вычисления в клиентские эффекты
- Используйте потоковую передачу данных с
react-server-dom-webpack
- Для сложных преобразований данных применяйте Web Workers в Node.js:
// server/worker.js
const { Worker } = require('worker_threads');
async function processDataInWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('./data-processor.js', {
workerData: data
});
worker.on('message', resolve);
worker.on('error', reject);
});
}
3. Стратегии кэширования
Используйте многоуровневое кэширование для уменьшения нагрузки на сервер:
Уровень 1: In-memory cache (Node.js process)
const LRU = require('lru-cache');
const ssrCache = new LRU({
max: 100,
maxAge: 1000 * 60 * 5, // 5 минут
});
export async function getCachedData(key, fetchFn) {
if (ssrCache.has(key)) {
return ssrCache.get(key);
}
const data = await fetchFn();
ssrCache.set(key, data);
return data;
}
Уровень 2: Redis для кластера серверов
const redis = require('redis');
const client = redis.createClient();
async function getOrSetCache(key, fetchFn, ttl = 300) {
const cached = await client.get(key);
if (cached) return JSON.parse(cached);
const freshData = await fetchFn();
await client.setex(key, ttl, JSON.stringify(freshData));
return freshData;
}
Уровень 3: CDN Cache-Control
export async function getServerSideProps({ res }) {
res.setHeader(
'Cache-Control',
'public, s-maxage=60, stale-while-revalidate=300'
);
// ...data fetching
}
4. Сессионные данные и авторизация
Ошибка: Передача чувствительных данных через props приводит к утечкам в клиентский код. Решение — разделение данных:
// server/api/auth.js
export function getServerSideUser(context) {
const session = getSession(context.req);
return session?.user || null;
}
// pages/profile.js
export async function getServerSideProps(context) {
const user = getServerSideUser(context);
return {
props: {
// Публичные данные
profile: await fetchPublicProfile(user?.id),
// Приватные данные передаются через контекст SSR
__secure: {
billingInfo: user ? await fetchBillingInfo(user.id) : null
}
}
};
}
// components/SecureDataBridge.js
import { useEffect } from 'react';
import { useSecureData } from '../context/secure-data';
export default function SecureDataBridge({ __secure }) {
const { setSecureData } = useSecureData();
useEffect(() => {
setSecureData(__secure);
}, []);
return null;
}
Production Checklist
Перед запуском SSR в продакшене убедитесь в:
- Наличии circuit breakers для внешних API
- Реализации gracefull degradation при частичных сбоях данных
- Настройке мониторинга TTV и TTI
- Использовании изолированных контейнеров для обработки тяжелых задач
- Тестировании поведения при 5XX ошибках бэкенда
SSR в Next.js перестает быть «волшебной таблеткой» в сложных сценариях, но сознательное проектирование потоков данных и обработки ошибок делает его надежным фундаментом для высоконагруженных приложений. Главное правило: рассматривайте SSR не как фичу фреймворка, а как отдельный сервис со своими SLA и требованиями к инфраструктуре.