Artigo original: How to implement server-side rendering in your React app in three simple steps

Isto é o que vamos criar para este tutorial: um card de React como o que vemos aqui:

1_wk04sWGQkw36_XLFvPACrA-1

Neste artigo, usaremos renderização do lado do servidor para produzir uma resposta em HTML quando um usuário ou um crawler chegar ao URL da página. Trataremos das solicitações posteriores do lado do client.

Por que precisamos disso?

Vou orientar você pela resposta.

Qual é a diferença entre a renderização do lado do client e do lado do servidor?

Na renderização do lado do client, seu navegador baixa uma página mínima de HTML. Ele renderiza o JavaScript e preenche o conteúdo da página.

Na renderização do lado do servidor, por outro lado, a renderização dos componentes do React é servidor. O resultado é o conteúdo em HTML.

Você pode combinar ambos os tipos para criar um app isomórfico.

Desvantagens de se renderizar o React do lado do servidor

  • A renderização do lado do servidor pode melhorar o desempenho se a sua aplicação for pequena, mas pode piorá-la se ele for grande demais.
  • Ela aumenta o tempo de resposta (que pode ficar pior se o servidor estiver ocupado).
  • Ela aumenta o tempo de resposta, o que significa que a página leva mais tempo para carregar.
  • Ela aumenta a complexidade da aplicação.

Quando você deve usar a renderização do lado do servidor?

Apesar dessas consequências da renderização do lado do servidor, existem algumas situações em que você pode e deve usá-la.

1. SEO

Todo site da web quer aparecer nas buscas. Corrija-me se eu estiver errado.

Infelizmente, os crawlers dos mecanismos de busca não entendem/renderizam ainda o JavaScript.

Isso quer dizer que eles veem uma página em branco, não importando a utilidade do seu site.

Já há quem diga que o crawler do Google agora renderiza o JavaScript.

Para testar isso, fiz o deploy de um app no Heroku. Isto foi o que eu vi no console de busca do Google:

1_KgOtUd6XBbeZvR1FDBGcXA
O crawler do Google não renderiza o React

Uma página em branco.

Essa foi a maior razão para eu explorar a renderização do lado do servidor, especialmente quando ela é uma conteúdo importante (texto explicativo em inglês) como uma página inicial, um blog e assim por diante.

Para verificar se o Google renderiza o seu site, visite:

Search Console Dashboard > Crawl > Fetch as Google. Insira o URL da página ou deixe-o em branco para ver a página inicial.

Selecione FETCH AND RENDER. Quando estiver concluído, clique para ver o resultado.

2. Melhorar o desempenho

Na renderização do lado do servidor, o desempenho da aplicação depende dos recursos do servidor e da velocidade da rede do usuário. Isso a torna muito útil para sites com muito conteúdo.

Por exemplo, digamos que você tenha um telefone celular mediano com baixa velocidade de internet. Você tenta acessar um site que baixa 4MB de dados antes de você poder ver qualquer coisa.

Você conseguiria ver qualquer coisa na tela em um intervalo de 2 a 4 segundos?

Você visitaria esse site novamente?

Acho que não.

Outra grande melhoria está no Primeiro tempo de interação do usuário. Essa é a diferença entre o momento em que o usuário acessa o URL até aquele onde ele vê o conteúdo.

Aqui temos a comparação. Eu a testei em um Mac para desenvolvimento.

React renderizado no servidor

1_kYMHoa7OemCHA_KBzJ1w-w
Relatório de desempenho da renderização pelo servidor (Chrome)

O primeiro tempo de interação é de 300ms. O "Hydrate" termina a 400ms. O evento de carregamento sai, aproximadamente, em 500ms. Você pode ver isso verificando a imagem abaixo.

React renderizado no navegador do client

1_wquRCRboPDi7Ix2HAxvCAA
Relatório de desempenho no lado do client (Chrome)

O primeiro tempo de interação é a 400ms. O evento de carregamento sai aos 470ms.

Os resultados falam por si mesmos. Há uma diferença de 100ms no primeiro tempo de interação do usuário para um app tão pequeno.

Como funciona? — (4 passos simples)

  • Crie uma Store do Redux em cada solicitação.
  • Faça o dispatch, como opção, de algumas ações.
  • Obtenha o state da store e realize a renderização do lado do servidor.
  • Envie o state obtido no passo anterior junto com a resposta.

Usaremos o state passado na resposta para criar o state inicial do lado do client.

Antes de começar, faça a clonagem/baixe o exemplo completo do Github e use-o como referência.

Começando a configuração do nosso app

Primeiro, abra seu editor e seu shell favoritos. Crie uma pasta para sua aplicação. Vamos começar.

npm init --yes

Preencha os detalhes. Após a criação do package.json, copie nele as dependências e os scripts abaixo.

Instale todas as dependências executando o seguinte comando:

npm install

Você precisa configurar o Babel e o Webpack para que seu script de build funcione.

O Babel transforma o código em ES6 e em React em código do Node e compreensível pelo navegador.

Crie um arquivo .babelrc e coloque nele a linha abaixo.

{
  "presets": ["@babel/env", "@babel/react"]
}

O webpack empacota nosso app e suas dependências em um único arquivo. Crie outro arquivo webpack.config.js com o código a seguir:

const path = require('path');module.exports = {
    entry: {
        client: './src/client.js',
        bundle: './src/bundle.js'
    },
    output: {
        path: path.resolve(__dirname, 'assets'),
        filename: "[name].js"
    },
    module: {
        rules: [
            { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
        ]
    }
}

O processo de build tem como resultado dois arquivo:

  1. assets/bundle.js — app puro do lado do client.
  2. assets/client.js — companheiro do lado do client para a renderização do lado do servidor.

A pasta src/ contém o código fonte. Os arquivos compilados pelo Babel vão em views/. O diretório views será criado automaticamente se já não existir.

Por que precisamos compilar os arquivos de origem?

A razão é a diferença de sintaxe entre o ES6 e o JavaScript comum. Embora estejamos escrevendo em React e Redux, estamos usando bastante imports e exports em todos os arquivos.

Infelizmente, eles não funcionam no Node. É a hora em que o Babel nos ajuda. O script abaixo informa o Babel que ele deve compilar todos os arquivos no diretório src e colocar o resultado em views.

"babel": "babel src -d views",

Agora, o Node pode executá-los.

Copiar os arquivos estáticos e pré-programados

Se você já clonou o repositório, copie de lá. Do contrário, baixe o arquivo ssr-static.zip do Dropbox. Extraia-o e mantenha essas três pastas dentro do diretório do seu app. Isso é o que eles contêm.

  1. O app do React e os componentes que residem em src/components.
  2. Os arquivos do Redux, em src/redux/.
  3. assets/ & media/: contém os arquivos estáticos, como style.css e as imagens.

No lado do servidor

Crie dois arquivos, chamados server.js e template.js, na pasta src/.

1. src/server.js

É aqui que a magia acontece. Este é o código que você procurava.

import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import configureStore from './redux/configureStore';
import App from './components/app';

module.exports = function render(initialState) {
  // Modele o state inicial  
  const store = configureStore(initialState);
  let content = renderToString(<Provider store={store} ><App /></Provider>);
  const preloadedState = store.getState();
  return {
    content,
    preloadedState
  };
};

Em vez de renderizar nossa aplicação, precisamos envolvê-la em uma função e exportá-la. A função aceita o state inicial da aplicação.

É assim que funciona.

  1. Passe initialState para configureStore(). configureStore() retorna uma nova instância da store. Mantenha-a na variável store.
  2. Chame o método renderToString(), fornecendo nosso app como entrada. Ele renderiza nossa aplicação no servidor e retorna o HTML produzido. Agora, a variável content armazena o HTML.
  3. Obtenha o state da store do Redux chamando getState() na store. Mantenha-o na variável preloadedState.
  4. Retorne o content e o preloadedState. Nós os passaremos ao nosso template para obter a página de HTML final.

2. src/template.js

template.js exporta uma função. Ele recebe title, state e content como entradas. Depois, ele as injeta no template e retorna o documento final em HTML.

Para passar o state, o template inclui state a window.__STATE__ dentro da tag <script>.

Agora, você pode ler o state no lado do client acessando window.__STATE__.

Também incluímos a aplicação equivalente no lado do client, assets/client.js, em outra tag script.

Se você solicitar a versão pura do client, ela só coloca assets/bundle.js na tag script.

O lado do client

O lado do client é bastante simplificado.

1. src/bundle.js

É assim que você escreve o wrap Provider em React e Redux. É o nosso app do lado do client. Não há segredos aqui.

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './redux/configureStore';
import App from './components/app';

const store = configureStore();
render(
  <Provider store={store} > <App /> </Provider>,
  document.querySelector('#app')
);

2. src/client.js

Parece familiar? Sim, não há nada de especial aqui, exceto window.__STATE__. Tudo o que precisamos é obter o state inicial de window.__STATE__ e passá-lo para nossa função configureStore() como o state inicial.

Vamos dar uma olhada no nosso arquivo do client:

import React from 'react';
import { hydrate } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './redux/configureStore';
import App from './components/app';

const state = window.__STATE__;
delete window.__STATE__;
const store = configureStore(state);
hydrate(
  <Provider store={store} > <App /> </Provider>,
  document.querySelector('#app')
);

Vamos conferir as alterações:

  1. Substitua render() por hydrate(). hydrate() é o mesmo que render(), mas usado para 'hidratar' os elementos renderizados pelo ReactDOMServer. Ele garante que o conteúdo seja o mesmo no servidor e no client.
  2. Leia o state do objeto window global window.__STATE__. Armazene-o em uma variável e exclua o window.__STATE__.
  3. Crie uma store com state como initialState.

Tudo resolvido.

Juntando tudo

Index.js

Este é o ponto de entrada da nossa aplicação. Ele trata das solicitações e dos templates.

Ele também declara a variável initialState. Eu a modelei com os dados do arquivo assets/data.json. Passaremos isso para nossa função ssr().

Observação: ao fazer referência a um arquivo que esteja dentro de src/ a partir de um arquivo fora de src/ , use o require() normal e substitua src/ por views/. Você sabe o motivo (a compilação do Babel).

Roteamento

  1. /: por padrão, a página inicial renderizada pelo servidor.
  2. /client: Exemplo puro renderizado do lado do client.
  3. /exit: Botão de parada do servidor. Disponível apenas em desenvolvimento.

Fazendo o build e executando

É hora de fazer o build e de executar nossa aplicação. Podemos fazer isso com uma única linha de código.

npm run build && npm run start

Agora, a aplicação estará sendo executadas em http://localhost:3000.

Pronto para se tornar um profissional em React?

Comecei uma nova série para deixar as suas habilidades em React as melhores possíveis e agora mesmo.

1_TEecv1nLg253xmyGgddhOw
Link para se inscrever abaixo

Obrigado pela leitura.

Se você leu este artigo e o achou útil, Siga o autor no Twitter e no Webflow.