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:
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:
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
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
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:
assets/bundle.js
— app puro do lado do client.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.
- O
app
do React e os componentes que residem emsrc/components
. - Os arquivos do Redux, em
src/redux/
. assets/ & media/
: contém os arquivos estáticos, comostyle.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.
- Passe
initialState
paraconfigureStore()
.configureStore()
retorna uma nova instância da store. Mantenha-a na variávelstore
. - 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ávelcontent
armazena o HTML. - Obtenha o state da store do Redux chamando
getState()
nastore
. Mantenha-o na variávelpreloadedState
. - Retorne o
content
e opreloadedState
. 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:
- Substitua
render()
porhydrate()
.hydrate()
é o mesmo querender()
, mas usado para 'hidratar' os elementos renderizados peloReactDOMServer
. Ele garante que o conteúdo seja o mesmo no servidor e no client. - Leia o state do objeto window global
window.__STATE__
. Armazene-o em uma variável e exclua owindow.__STATE__
. - 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
/
: por padrão, a página inicial renderizada pelo servidor./client
: Exemplo puro renderizado do lado do client./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.
Obrigado pela leitura.
Se você leu este artigo e o achou útil, Siga o autor no Twitter e no Webflow.