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
  • Ресурсозатратно и не всегда быстро

Что можно еще улучшить?

Делитесь опытом

Case study: andvanced SSR with React

By Pavel Trehubau

Case study: andvanced SSR with React

  • 577