Case study: advanced SSR with React
Делитесь опытом
Beware: React
Isomorphic app

Rendr
advanced?
and what is simple?
ReactDOMServer.renderToString(element)
React Router
import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from './routes'
serve((req, res) => {
// Note that req.url here should be the full URL path from
// the original request, including the query string.
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
// You can also check renderProps.components or renderProps.routes for
// your "not found" component or route respectively, and send a 404 as
// below, if you're using a catch-all route.
res.status(200).send(renderToString(<RouterContext {...renderProps} />))
} else {
res.status(404).send('Not found')
}
})
})babel-node
Redux
function renderFullPage(html, preloadedState) {
return `
<!doctype html>
<html>
<head>
<title>Redux Universal Example</title>
</head>
<body>
<div id="root">${html}</div>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
</script>
<script src="/static/bundle.js"></script>
</body>
</html>
`
}CSS Modules
import React, { Component } from 'react';
import btn from './styles.css';
const Button = ({ text }) =>
<button className={ btn.red }>{ this.props.text }</button>;
export default Button;Image imports
import logo from './logo.png';
const Logo = () =>
<img href={ logo } alt="Logo" />;
export default Logo;Let's build server with webpack?
Сложна
- Лоадеры
- Плагины
- Переменные окружения
Как подружить клиентский и серверный билд?
dev и prod версии
dev
- Hot reload всего и вся
- Сорс мапы
- Щепотку плагинов (eslint / unused files)
prod
- Просто сбилдить (держать в голове план деплоя)
- Сжать всё что можно
- ExtractTextPlugin для стилей
- Проанализировать размер и сгенерить отчет (опционально)
client config
import { client } from 'universal-webpack/config'
import settings from './universal-webpack-settings'
import configuration from './webpack.config'
export default client(configuration, settings)
server config
import { server } from 'universal-webpack/config'
import settings from './universal-webpack-settings'
import configuration from './webpack.config'
export default server(configuration, settings)
universal-webpack-settings.json
{
"server":
{
"input": "./source/server.js",
"output": "./build/server/server.js"
}
}
Угадайка
Сколько у нас webpack конфигов?
Что делает universal-webpack для клиентского билда?
- Добавляет плагин, который генерит json со списком чанков
- Автоматически добавляет ExtractTextPlugin на клиенте в проде
- Умеет чистить и создавать папку для билда
Что делает universal-webpack для серверного билда?
- Добавляет context
- target: 'node'
- output.libraryTarget: 'commonjs2'
- Убирает хеши из путей
- Сорсмапы
- externals
- Меняет поведение style-loader
- Меняет поведение file-loader
- Убирает HotModuleReplacementPlugin и CommonsChunkPlugin
- Перед запуском сервера ждет, пока соберутся оба бандла
Подводные камни
Придется мигрировать на второй webpack
dev mode

Это еще не все :(
- React Hot Loader 3
- Удобная подгрузка данных перед рендером
- Программные редиректы, которые будет работать и на сервере, и на клиенте
- Проставлять title и meta в зависимости от страницы
react-isomorphic-render
react-isomorphic-render.js
export default {
// React-router v3 routes
routes: require('./src/client/routes'),
// Redux reducers
// (they will be combined into the
// root reducer via `combineReducers()`)
reducer: require('./src/client/redux/reducers')
}
Много чего умеет
- Удобная конфигурация (и общая, и серверная)
- Свой подход для работы с асинхронными экшн криэйторами
- Небольшая универсальная утилита для работы с http
- @preload HOC
- title и meta via react-helmet
- Определение локали (l11n)
- Экшн криэйторы для редиректов
- Статистика - сколько времени ушло на разные этапы рендера
- HMR
- WebSocket
- Генерация статического сайта
npm scripts

С чем еще столкнулись?
- Работу с DOM'ом стоит перенести в componentDidMount
- Не все будет работать без DOM'a (dompurify, mxd-data-layer)
- При помощи webpackConfig.resolve.alias можно заменить какую-нибудь зависимость на пустой мок
- Может понадобиться мок браузерных частей (navigator)
- Нужен универсальный http клиент (superagent)
Альтернативы
Можно не так больно?
А надо ли оно вообще?
- SEO
- User perceived performance
- Complexity
- Ресурсозатратно и не всегда быстро
Что можно еще улучшить?
- Умно кешировать результат renderToString
- Отдавать стрим в процессе рендера (React 16)
- Больше деталей по ссылке и ссылкам внутри
Делитесь опытом
Case study: andvanced SSR with React
By Pavel Trehubau
Case study: andvanced SSR with React
- 577