20. Server-Side Rendering
🖥️ Server-Side Rendering в Solid.js
Заголовок раздела «🖥️ Server-Side Rendering в Solid.js»Яша, SSR в Solid — это одна из его сильнейших сторон. Solid умеет не просто отдавать HTML со сервера, а делать это с потоковой передачей (streaming)! HTML начинает приходить клиенту сразу, пока данные ещё загружаются. Плюс — гидратация работает практически без оверхеда. Разберём всё! 🚀
🏗️ Как работает SSR
Заголовок раздела «🏗️ Как работает SSR»Без SSR (SPA):1. Браузер получает пустой HTML2. Загружает JS бандл3. Выполняет JS → рендерит HTML4. Загружает данные5. Отображает контент❌ Медленный первый байт контента (FCP)
С SSR:1. Сервер рендерит HTML сразу с данными2. Браузер получает готовый HTML → пользователь видит контент3. Загружается JS бандл4. Гидратация → приложение становится интерактивным✅ Быстрый FCP, хорошо для SEO📦 renderToString
Заголовок раздела «📦 renderToString»Самый простой способ — синхронный рендер в строку:
import { renderToString } from 'solid-js/web';import App from './App';
// На сервере (Node.js):const html = renderToString(() => <App />);
// Полный HTMLconst fullPage = `<!DOCTYPE html><html> <head> <title>My App</title> </head> <body> <div id="app">${html}</div> <script src="/bundle.js"></script> </body></html>`;
// Отправляем клиентуresponse.send(fullPage);⚠️
renderToStringждёт завершения всехcreateResourceперед рендером. Если данные грузятся долго — клиент ждёт весь HTML целиком.
🌊 renderToStream (Streaming SSR)
Заголовок раздела «🌊 renderToStream (Streaming SSR)»Вот где Solid блистает — потоковый рендер:
import { renderToStream } from 'solid-js/web';
app.get('/', (req, res) => { res.setHeader('Content-Type', 'text/html'); res.setHeader('Transfer-Encoding', 'chunked');
const stream = renderToStream(() => <App />);
// Сначала отправляем shell (заголовок) res.write(` <!DOCTYPE html> <html> <head><title>App</title></head> <body><div id="app"> `);
// Streaming: каждый Suspense boundary стримится отдельно! stream.pipe(res);
stream.on('end', () => { res.write(`</div><script src="/bundle.js"></script></body></html>`); res.end(); });});💧 Как работает Streaming
Заголовок раздела «💧 Как работает Streaming»// С Suspense — каждый boundary стримится отдельноfunction App() { return ( <div> {/* Этот блок отправится сразу */} <Header />
{/* Этот блок придёт когда загрузятся пользователи */} <Suspense fallback={<UserSkeleton />}> <UserList /> </Suspense>
{/* Этот блок придёт когда загрузятся продукты */} <Suspense fallback={<ProductSkeleton />}> <ProductList /> </Suspense> </div> );}
// Порядок получения HTML клиентом:// 1. <Header /> — сразу// 2. <UserSkeleton /> — сразу (пока данные грузятся)// 3. Реальный <UserList /> — когда пришли данные// 4. <ProductSkeleton /> → <ProductList /> — независимо!🔄 Гидратация
Заголовок раздела «🔄 Гидратация»// entry-server.tsx — серверimport { renderToString } from 'solid-js/web';import App from './App';
export async function render() { return renderToString(() => <App />);}
// entry-client.tsx — клиент (гидратация)import { hydrate } from 'solid-js/web'; // не render!import App from './App';
// hydrate сопоставляет серверный HTML с компонентами// Минимальный JS overhead — только привязка обработчиков!hydrate(() => <App />, document.getElementById('app')!);⚡ Гидратация в Solid невероятно быстрая! Solid не перерисовывает DOM — он только привязывает реактивность к уже существующим DOM-узлам. В React виртуальный DOM заново обходится весь. Solid — только нужные точки.
🔍 isServer — определение среды
Заголовок раздела «🔍 isServer — определение среды»import { isServer } from 'solid-js/web';
function Component() { // Условный код в зависимости от среды if (isServer) { // Выполняется только на сервере console.log('Это сервер'); }
// createEffect не выполняется на сервере! createEffect(() => { if (isServer) return; // Лишняя защита, но ясно показывает намерение window.analytics.track('component-view'); });
return <div>Компонент</div>;}
// Серверный код в утилитахexport function getBaseUrl() { if (isServer) { return process.env.BASE_URL || 'http://localhost:3000'; } return window.location.origin;}🔒 Серверный код — только на сервере
Заголовок раздела «🔒 Серверный код — только на сервере»// Это НЕ попадёт в браузер если использовать "use server"import { query } from '@solidjs/router';
const getSecretData = query(async () => { 'use server'; // Директива — этот код только на сервере const apiKey = process.env.PRIVATE_API_KEY; // Безопасно! return fetch('https://private-api.com/data', { headers: { Authorization: `Bearer ${apiKey}` } }).then(r => r.json());}, 'secret');
// Защищённый компонентfunction SecretPanel() { const data = createAsync(() => getSecretData()); return ( <Suspense fallback={<p>Загрузка...</p>}> <pre>{JSON.stringify(data(), null, 2)}</pre> </Suspense> );}🏝️ Island Architecture (экспериментально)
Заголовок раздела «🏝️ Island Architecture (экспериментально)»// SolidStart Islands — только нужные компоненты гидрируются// Остальная страница — чистый HTML, никакого JS!
export default defineConfig({ islands: true });
// components/Counter.tsx — "остров" интерактивности// Этот компонент получит свой JS bundleexport default function Counter() { const [count, setCount] = createSignal(0); return <button onClick={() => setCount(c => c + 1)}>{count()}</button>;}
// src/routes/index.tsximport Counter from '~/components/Counter';
export default function Home() { return ( <main> {/* Статичный HTML — никакого JS */} <h1>Заголовок</h1> <p>Статичный текст...</p>
{/* "Остров" — получит JS для интерактивности */} <Counter /> </main> );}