Skip to content

Instantly share code, notes, and snippets.

@imevro
Last active March 22, 2020 19:22
Show Gist options
  • Save imevro/d29ab267c2c18d16b37377ca6c30232b to your computer and use it in GitHub Desktop.
Save imevro/d29ab267c2c18d16b37377ca6c30232b to your computer and use it in GitHub Desktop.
Minimally working version of SSR (react, react-router, react-helmet, styled-components; implying use of react-scripts without eject)
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="$helmet-placeholder$">
<meta name="$sc-placeholder$">
<style>
.ssr-placeholder {
opacity: 0;
}
</style>
</head>
<body>
<noscript>
Почему ты залез на курс по фронтэнду с выключенным джаваскриптом?
</noscript>
<div id="root">
<span class="ssr-placeholder">{ssrData}</span>
</div>
</body>
</html>
const debug = require('debug')('erodionov:server');
const express = require('express');
const path = require('path');
const handleRenderReactApp = require('./reactApp');
const server = express();
const router = express.Router();
const { HOST = '127.0.0.1', PORT = 80 } = process.env;
// статикой раздаём `build`
server.use(express.static(path.resolve(__dirname, '..', 'build')));
server.use(router);
// на любой запрос вызываем функцию handleRenderReactApp
router.get('*', handleRenderReactApp);
server.listen(PORT, HOST, () => debug(`app started at ${HOST}:${PORT}`));
// because we export as ESM, not CJS
// eslint-disable-next-line import/first
import ReactApp from '../src/App';
const path = require('path');
const fs = require('fs');
const React = require('react');
const { StaticRouter } = require('react-router-dom');
const ReactDOMServer = require('react-dom/server');
const { Helmet } = require('react-helmet');
const { ServerStyleSheet } = require('styled-components');
// берём build/index.html и сохраняем весь контент в indexFileContent
const indexFile = path.resolve(__dirname, '..', 'build', 'index.html');
const indexFileContent = fs.readFileSync(indexFile, { encoding: 'utf8' });
// плейсхолдер куда будем рендерить данные
const ssrPlaceholder = '<span class="ssr-placeholder">{ssrData}</span>';
// плейсхолдер для react-helmet
const helmetPlaceholder = '<meta name="$helmet-placeholder$">';
// плейсхолдер для styled-components
const scPlaceholder = '<meta name="$sc-placeholder$">';
module.exports = (req, res) => {
// https://www.styled-components.com/docs/advanced#server-side-rendering
const sheet = new ServerStyleSheet();
// https://reacttraining.com/react-router/web/guides/server-rendering
const context = {};
const App = ReactDOMServer.renderToString(
sheet.collectStyles(
<StaticRouter location={req.url} context={context}>
<ReactApp />
</StaticRouter>,
),
);
// https://github.com/nfl/react-helmet#server-usage
const helmet = Helmet.renderStatic();
const styleTags = sheet.getStyleTags();
// редиректим с http-кодом 301
if (context.url) {
res.redirect(301, context.url);
}
const helmetData = `
${helmet.title.toString()}${helmet.meta.toString()}${helmet.link.toString()}
`;
// вставляем данные
const content = indexFileContent
.replace(helmetPlaceholder, helmetData)
.replace(scPlaceholder, styleTags)
.replace(ssrPlaceholder, App);
res.send(content);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment