\n \n \n `);\n});\n\napp.listen(3000, () => console.log('Server running on port 3000'));\n```\n\nЭтот подход дает полный контроль над рендерингом и позволяет легко интегрировать с Express middleware.\n\n## 2. Hydration - оживление HTML на клиенте\n\n```javascript\n// client.js - код на клиенте\nimport React from 'react';\nimport { hydrateRoot } from 'react-dom/client';\nimport App from './App.jsx';\n\n// Hydration \"оживляет\" статический HTML\nconst root = hydrateRoot(document.getElementById('app'), );\n```\n\nHydration критично для SSR - сервер отправляет HTML, клиент добавляет интерактивность без пересоздания DOM.\n\n## 3. Обработка асинхронных данных на сервере\n\n```javascript\n// server.js с загрузкой данных\nimport express from 'express';\nimport { renderToString } from 'react-dom/server';\n\napp.get('/users/:id', async (req, res) => {\n try {\n // Загружаем данные на сервере\n const response = await fetch(`https://api.example.com/users/${req.params.id}`);\n const userData = await response.json();\n \n // Передаем данные компоненту\n const appString = renderToString(\n \n );\n \n // Отправляем с начальными данными\n res.send(`\n \n \n \n
${appString}
\n \n \n \n \n `);\n } catch (error) {\n res.status(500).send('Error loading user');\n }\n});\n```\n\nНачальное состояние передается в глобальную переменную для гидрации клиента.\n\n## 4. Vite SSR + Express\n\n```javascript\n// server.js с Vite\nimport express from 'express';\nimport { createServer as createViteServer } from 'vite';\nimport { renderToString } from 'react-dom/server';\n\nconst app = express();\nconst vite = await createViteServer({\n server: { middlewareMode: true },\n appType: 'ssr'\n});\n\napp.use(vite.middlewares);\n\napp.get('*', async (req, res) => {\n try {\n const { App } = await vite.ssrLoadModule('/src/App.jsx');\n const html = renderToString();\n \n const template = fs.readFileSync('index.html', 'utf-8');\n const finalHtml = template.replace('', html);\n \n res.status(200).set({ 'Content-Type': 'text/html' }).end(finalHtml);\n } catch (e) {\n vite.ssrFixStacktrace(e);\n res.status(500).end(e.stack);\n }\n});\n\napp.listen(3000);\n```\n\nVite предоставляет встроенную поддержку SSR с горячей перезагрузкой для разработки.\n\n## 5. Streaming для больших приложений\n\n```javascript\n// server.js с потоковым рендерингом\nimport { renderToPipeableStream } from 'react-dom/server';\n\napp.get('/', (req, res) => {\n const { pipe } = renderToPipeableStream(\n ,\n {\n onShellReady() {\n res.setHeader('Content-type', 'text/html');\n pipe(res);\n },\n onShellError(error) {\n res.statusCode = 500;\n res.send('

Loading...

');\n },\n onError(error) {\n console.error('Rendering error:', error);\n }\n }\n );\n});\n```\n\nПотоковый рендеринг отправляет HTML по частям, улучшая Time to First Byte.\n\n## 6. Обработка стилей при SSR\n\n```javascript\n// server.js с CSS-in-JS\nimport { StyleSheetManager } from 'styled-components';\nimport { ServerStyleSheet } from 'styled-components';\n\napp.get('/', (req, res) => {\n const sheet = new ServerStyleSheet();\n \n try {\n const html = renderToString(\n sheet.collectStyles()\n );\n \n const styleTags = sheet.getStyleTags();\n \n res.send(`\n \n \n \n ${styleTags}\n \n \n
${html}
\n \n \n \n `);\n } finally {\n sheet.seal();\n }\n});\n```\n\nCSS должен быть включен в HTML на сервере чтобы избежать flash of unstyled content.\n\n## 7. Next.js как альтернатива (рекомендуется)\n\n```javascript\n// app/page.tsx - Next.js App Router\nexport const metadata = {\n title: 'My App'\n};\n\nexport default function Page() {\n return
Server-rendered content
;\n}\n\n// next.config.js\nconst nextConfig = {\n output: 'standalone' // Для Dokku deployment\n};\n\nexport default nextConfig;\n```\n\nNext.js абстрагирует всю сложность SSR и предоставляет удобный API для разработчиков.\n\n## 8. Сравнение подходов\n\n```javascript\n// CUSTOM EXPRESS + REACT\nПреимущества:\n- Полный контроль\n- Минимальные зависимости\n- Просто для простых приложений\n\nНедостатки:\n- Много boilerplate кода\n- Нужно настраивать routing, гидрацию, стили\n- Сложно масштабировать\n\n// VITE SSR\nПреимущества:\n- Быстрая разработка\n- Встроенная поддержка SSR\n- Hot reload\n\nНедостатки:\n- Меньше examples в сообществе\n- Нужно писать свой сервер\n\n// NEXT.JS\nПреимущества:\n- Все настроено из коробки\n- Встроенные оптимизации\n- Большое сообщество\n- Image, font optimization\n\nНедостатки:\n- Менее гибкий\n- Vendor lock-in\n```\n\n## 9. Проблемы и решения\n\n```javascript\n// ПРОБЛЕМА: Hydration mismatch\n// На сервере и клиенте разный HTML\n\n// РЕШЕНИЕ: Использовать одинаковый код и данные\napp.get('/:id', async (req, res) => {\n const data = await fetchData(req.params.id);\n \n // Передать точно такие же данные клиенту\n const html = renderToString();\n \n res.send(`\n \n
${html}
\n `);\n});\n\n// На клиенте\nconst data = window.__DATA__;\nconst root = hydrateRoot(document.getElementById('app'), \n \n);\n\n// ПРОБЛЕМА: Медленный рендеринг\n// РЕШЕНИЕ: Использовать streaming\nrenderToPipeableStream(app, options);\n\n// ПРОБЛЕМА: Context API не работает на сервере\n// РЕШЕНИЕ: Использовать другой способ передачи состояния\nconst html = renderToString();\n```\n\n## 10. Production deployment\n\n```javascript\n// package.json\n{\n \"scripts\": {\n \"dev\": \"node server.js\",\n \"build\": \"vite build\",\n \"start\": \"NODE_ENV=production node server.js\"\n }\n}\n\n// Докерфайл для Dokku\nFROM node:18\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --production\nCOPY . .\nRUN npm run build\nEXPOSE 3000\nCMD [\"npm\", \"start\"]\n```\n\n## Рекомендация\n\n**Для новых проектов используй Next.js** - это избавляет от необходимости настраивать SSR вручную. Если нужна максимальная гибкость или специфические требования, используй Express + React с Vite для разработки.\n\nCustom SSR реализация имеет смысл только если ты четко понимаешь зачем она нужна и готов поддерживать boilerplate код. Для большинства случаев Next.js это правильный выбор.","dateCreated":"2026-04-03T17:56:19.248801","upvoteCount":0,"author":{"@type":"Person","name":"claude-haiku-4.5"}}}}
← Назад к вопросам

Как реализовать серверный рендеринг без Nuxt.js?

2.3 Middle🔥 161 комментариев
#Vue.js#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Серверный рендеринг (SSR) без Nuxt.js

Общеу думку что SSR доступен только с фреймворками вроде Nuxt.js неправда. В JavaScript существует несколько подходов к реализации серверного рендеринга: от Express + React до Next.js. Каждый подход имеет свои компромиссы между сложностью и контролем.

1. Express + React SSR

// server.js - Node.js сервер
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './App.jsx';

const app = express();

app.get('/', (req, res) => {
  // Рендеринг компонента на сервере
  const appString = renderToString(<App />);
  
  // Отправка HTML
  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>SSR App</title>
      </head>
      <body>
        <div id="app">${appString}</div>
        <script src="/client.js"></script>
      </body>
    </html>
  `);
});

app.listen(3000, () => console.log('Server running on port 3000'));

Этот подход дает полный контроль над рендерингом и позволяет легко интегрировать с Express middleware.

2. Hydration - оживление HTML на клиенте

// client.js - код на клиенте
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import App from './App.jsx';

// Hydration "оживляет" статический HTML
const root = hydrateRoot(document.getElementById('app'), <App />);

Hydration критично для SSR - сервер отправляет HTML, клиент добавляет интерактивность без пересоздания DOM.

3. Обработка асинхронных данных на сервере

// server.js с загрузкой данных
import express from 'express';
import { renderToString } from 'react-dom/server';

app.get('/users/:id', async (req, res) => {
  try {
    // Загружаем данные на сервере
    const response = await fetch(`https://api.example.com/users/${req.params.id}`);
    const userData = await response.json();
    
    // Передаем данные компоненту
    const appString = renderToString(
      <UserProfile user={userData} />
    );
    
    // Отправляем с начальными данными
    res.send(`
      <!DOCTYPE html>
      <html>
        <body>
          <div id="app">${appString}</div>
          <script>
            window.__INITIAL_STATE__ = ${JSON.stringify(userData)};
          </script>
          <script src="/client.js"></script>
        </body>
      </html>
    `);
  } catch (error) {
    res.status(500).send('Error loading user');
  }
});

Начальное состояние передается в глобальную переменную для гидрации клиента.

4. Vite SSR + Express

// server.js с Vite
import express from 'express';
import { createServer as createViteServer } from 'vite';
import { renderToString } from 'react-dom/server';

const app = express();
const vite = await createViteServer({
  server: { middlewareMode: true },
  appType: 'ssr'
});

app.use(vite.middlewares);

app.get('*', async (req, res) => {
  try {
    const { App } = await vite.ssrLoadModule('/src/App.jsx');
    const html = renderToString(<App />);
    
    const template = fs.readFileSync('index.html', 'utf-8');
    const finalHtml = template.replace('<!--app-html-->', html);
    
    res.status(200).set({ 'Content-Type': 'text/html' }).end(finalHtml);
  } catch (e) {
    vite.ssrFixStacktrace(e);
    res.status(500).end(e.stack);
  }
});

app.listen(3000);

Vite предоставляет встроенную поддержку SSR с горячей перезагрузкой для разработки.

5. Streaming для больших приложений

// server.js с потоковым рендерингом
import { renderToPipeableStream } from 'react-dom/server';

app.get('/', (req, res) => {
  const { pipe } = renderToPipeableStream(
    <App />,
    {
      onShellReady() {
        res.setHeader('Content-type', 'text/html');
        pipe(res);
      },
      onShellError(error) {
        res.statusCode = 500;
        res.send('<!doctype html><p>Loading...</p>');
      },
      onError(error) {
        console.error('Rendering error:', error);
      }
    }
  );
});

Потоковый рендеринг отправляет HTML по частям, улучшая Time to First Byte.

6. Обработка стилей при SSR

// server.js с CSS-in-JS
import { StyleSheetManager } from 'styled-components';
import { ServerStyleSheet } from 'styled-components';

app.get('/', (req, res) => {
  const sheet = new ServerStyleSheet();
  
  try {
    const html = renderToString(
      sheet.collectStyles(<App />)
    );
    
    const styleTags = sheet.getStyleTags();
    
    res.send(`
      <!DOCTYPE html>
      <html>
        <head>
          ${styleTags}
        </head>
        <body>
          <div id="app">${html}</div>
          <script src="/client.js"></script>
        </body>
      </html>
    `);
  } finally {
    sheet.seal();
  }
});

CSS должен быть включен в HTML на сервере чтобы избежать flash of unstyled content.

7. Next.js как альтернатива (рекомендуется)

// app/page.tsx - Next.js App Router
export const metadata = {
  title: 'My App'
};

export default function Page() {
  return <div>Server-rendered content</div>;
}

// next.config.js
const nextConfig = {
  output: 'standalone' // Для Dokku deployment
};

export default nextConfig;

Next.js абстрагирует всю сложность SSR и предоставляет удобный API для разработчиков.

8. Сравнение подходов

// CUSTOM EXPRESS + REACT
Преимущества:
- Полный контроль
- Минимальные зависимости
- Просто для простых приложений

Недостатки:
- Много boilerplate кода
- Нужно настраивать routing, гидрацию, стили
- Сложно масштабировать

// VITE SSR
Преимущества:
- Быстрая разработка
- Встроенная поддержка SSR
- Hot reload

Недостатки:
- Меньше examples в сообществе
- Нужно писать свой сервер

// NEXT.JS
Преимущества:
- Все настроено из коробки
- Встроенные оптимизации
- Большое сообщество
- Image, font optimization

Недостатки:
- Менее гибкий
- Vendor lock-in

9. Проблемы и решения

// ПРОБЛЕМА: Hydration mismatch
// На сервере и клиенте разный HTML

// РЕШЕНИЕ: Использовать одинаковый код и данные
app.get('/:id', async (req, res) => {
  const data = await fetchData(req.params.id);
  
  // Передать точно такие же данные клиенту
  const html = renderToString(<Component data={data} />);
  
  res.send(`
    <script>window.__DATA__ = ${JSON.stringify(data)}</script>
    <div id="app">${html}</div>
  `);
});

// На клиенте
const data = window.__DATA__;
const root = hydrateRoot(document.getElementById('app'), 
  <Component data={data} />
);

// ПРОБЛЕМА: Медленный рендеринг
// РЕШЕНИЕ: Использовать streaming
renderToPipeableStream(app, options);

// ПРОБЛЕМА: Context API не работает на сервере
// РЕШЕНИЕ: Использовать другой способ передачи состояния
const html = renderToString(<App initialState={state} />);

10. Production deployment

// package.json
{
  "scripts": {
    "dev": "node server.js",
    "build": "vite build",
    "start": "NODE_ENV=production node server.js"
  }
}

// Докерфайл для Dokku
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

Рекомендация

Для новых проектов используй Next.js - это избавляет от необходимости настраивать SSR вручную. Если нужна максимальная гибкость или специфические требования, используй Express + React с Vite для разработки.

Custom SSR реализация имеет смысл только если ты четко понимаешь зачем она нужна и готов поддерживать boilerplate код. Для большинства случаев Next.js это правильный выбор.

Как реализовать серверный рендеринг без Nuxt.js? | PrepBro