O que é renderização do lado do servidor e eu preciso disso?

Olá, Habr!



No novo ano, vamos começar nossa conversa com você com um artigo inicial sobre renderização do lado do servidor. Se você estiver interessado, uma publicação mais recente sobre Nuxt.js e mais trabalhos de publicação nessa direção são possíveis.



Com o advento de bibliotecas e estruturas JavaScript modernas, que se destinam principalmente à criação de páginas da web interativas e aplicativos de página única, todo o processo de mostrar páginas ao usuário mudou muito.



Antes do advento de aplicativos inteiramente gerados por JS no navegador, o HTML era servido ao cliente em resposta a uma chamada HTTP. Isso pode ser feito retornando um arquivo HTML estático com o conteúdo ou processando a resposta usando uma linguagem do lado do servidor (PHP, Python ou Java) e de uma forma mais dinâmica.



Esta solução permite criar sites responsivos que rodam muito mais rápido do que os sites padrão de solicitação-resposta, pois elimina o tempo gasto pela solicitação "no caminho".



Uma resposta típica de um servidor a uma solicitação a um site escrito em React seria mais ou menos assim:



<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="/favicon.ico">
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/app.js"></script>
  </body>
</html>
      
      





Ao selecionar esta resposta, nosso navegador também selecionará o "pacote" que app.js



contém nosso aplicativo e, após um ou dois segundos, renderizará a página inteira.



Neste ponto, você já pode usar o inspetor HTML integrado do navegador para visualizar todo o HTML renderizado. No entanto, olhando para o código-fonte, não veremos nada além do HTML acima.



Por que isso é um problema?



Embora esse comportamento não seja um problema para a maioria de nossos usuários ou durante o desenvolvimento de um aplicativo, pode se tornar indesejável se:



  • -, ,
  • , ,


Se, do ponto de vista demográfico, o seu público-alvo pertence a um desses grupos, trabalhar com o site será inconveniente - em particular, os usuários terão que esperar muito tempo, olhando para a placa “Carregando ...” (ou pior, para uma tela em branco).



“Ok, mas demograficamente, meu público-alvo definitivamente não é um desses grupos, então devo me preocupar?”



Há duas outras coisas a serem consideradas ao trabalhar com um aplicativo renderizado pelo cliente: mecanismos de pesquisa e presença na mídia social .



Hoje, de todos os motores de busca, apenas o Google tem alguma capacidade de exibir um site e levar em consideração seu JS antes de exibir uma página. Além disso, embora o Google consiga exibir a página de índice do seu site, sabe-se que pode haver problemas para navegar em sites que possuem um roteador.



Isso significa que será muito difícil para o seu site chegar ao topo dos resultados de qualquer mecanismo de pesquisa que não seja o Google.



O mesmo problema é visto nas redes sociais, por exemplo, no Facebook - se um link para o seu site for compartilhado, nem o nome nem a imagem de visualização serão exibidos corretamente.



Como resolver este problema



Existem várias maneiras de resolver isso.



R - Tente manter todas as páginas principais do seu site estáticas



Quando é criado um site de plataforma, onde o usuário terá que fazer login com seu nome de usuário, e sem fazer login no sistema, o conteúdo não é fornecido ao visitante, você pode tentar deixar páginas públicas estáticas (escritas em HTML) de seu site, em particular, o índice, "sobre nós", "contatos "E não use JS ao exibi-los .



Como seu conteúdo é limitado por requisitos de login, ele não será indexado por mecanismos de pesquisa e não pode ser compartilhado nas redes sociais.



B - Gere partes de seu aplicativo como páginas HTML durante a construção



Você pode adicionar bibliotecas como react-snapshot ao seu projeto ; eles são usados ​​para gerar cópias HTML das páginas de seu aplicativo e armazená-las em um diretório dedicado. Este diretório é então implantado junto com o pacote JS. Assim, o HTML será veiculado a partir do servidor junto com a resposta, e seu site também será visto por aqueles usuários que têm o JavaScript desabilitado, bem como pelos motores de busca, etc.



Como regra, configurar o react-snapshot não é difícil: basta adicionar a biblioteca ao seu projeto e alterar o script de construção da seguinte maneira:



"build": "webpack && react-snapshot --build-dir static"





A desvantagem desta solução é a seguinte: todo o conteúdo que queremos gerar deve estar disponível em tempo de construção - não podemos acessar nenhuma API para obtê-lo, também não podemos pré-gerar conteúdo que dependa dos dados fornecidos pelo usuário (por exemplo, de um URL).



C - Crie um aplicativo JS que usa renderização de servidor



Um dos maiores pontos de venda da geração atual de aplicativos JS é que eles podem ser executados no cliente (navegador) e no servidor. Isso possibilita a geração de HTML para páginas mais dinâmicas, cujo conteúdo ainda não é conhecido no momento da construção. Essas aplicações são freqüentemente chamadas de "isomórficas" ou "universais".



As duas soluções de renderização do lado do servidor mais populares para React são:





Crie sua própria implementação de SSR



Importante: se você for tentar criar sua própria implementação SSR para aplicativos React, precisará fornecer um back-end de nó para seu servidor. Você não será capaz de implantar esta solução em um host estático como faria com as páginas do github.



A primeira coisa que precisamos fazer é criar um aplicativo, como qualquer outro aplicativo React.



Vamos criar um ponto de entrada:



// index.js
import React from 'react';
import { render } from 'react-dom';
import App from './App.js';render(<App />, document.getElementById('root'));
      
      







E o aplicativo-componente (App):



// App.js
import React from 'react';const App = () => {
  return (
    <div>
      Welcome to SSR powered React application!
    </div>
  );
}
      
      





E também um "wrapper" para carregar nosso aplicativo:



// index.html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="root"></div>
    <script src="/bundle.js"></script>
  </body>
</html>
      
      





Como você pode ver, o aplicativo é bastante simples. Neste artigo, não passaremos por todas as etapas necessárias para gerar o assembly webpack + babel correto.

Se você iniciar o aplicativo em seu estado atual, uma mensagem de boas-vindas aparecerá na tela. Olhando o código-fonte, você verá o conteúdo do arquivo index.html



, mas a mensagem de boas-vindas não estará lá. Para resolver este problema, vamos adicionar a renderização do servidor. Primeiro, vamos adicionar 3 pacotes:



yarn add express pug babel-node --save-dev
      
      





Express é um servidor da web poderoso para node, pug é um mecanismo de modelagem que pode ser usado com express e babel-node é um wrapper para node que fornece transpilação instantânea.



Primeiro, vamos copiar nosso arquivo index.html



e salvá-lo como index.pug



:



// index.pug
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="root">!{app}</div>
    <script src="bundle.js"></script>
  </body>
</html>
      
      





Como você pode ver, o arquivo não mudou muito, exceto pelo que agora está inserido no HTML !{app}



. Esta é uma variável pug



que mais tarde será substituída pelo HTML real.



Vamos criar nosso servidor:



// server.jsimport React from 'react';
import { renderToString } from 'react-dom/server';
import express from 'express';
import path from 'path';import App from './src/App';const app = express();
app.set('view engine', 'pug');
app.use('/', express.static(path.join(__dirname, 'dist')));app.get('*', (req, res) => {
  const html = renderToString(
    <App />
  );  res.render(path.join(__dirname, 'src/index.pug'), {
    app: html
  });
});app.listen(3000, () => console.log('listening on port 3000'));
      
      





Vamos analisar este arquivo em ordem.



import { renderToString } from 'react-dom/server';
      
      





A biblioteca react-dom contém uma exportação nomeada separada renderToString



que funciona como a que conhecemos render



, mas não renderiza o DOM, mas o HTML como uma string.



const app = express();
app.set('view engine', 'pug');
app.use('/', express.static(path.join(__dirname, 'dist')));
      
      







Criamos uma nova instância de servidor expresso e informamos que usaremos um mecanismo de modelagem pug



. Nesse caso, poderíamos continuar com a leitura normal do arquivo e executar uma operação "localizar e substituir", mas essa abordagem é realmente ineficaz e pode causar problemas devido ao acesso múltiplo ao sistema de arquivos ou problemas com o cache.



Na última linha, dizemos ao express para procurar um arquivo em um diretório dist



e, se a solicitação (por exemplo /bundle.js



) corresponder a um arquivo presente nesse diretório, retorne-o.



app.get('*', (req, res) => {
});
      
      







Agora pedimos ao express para adicionar um manipulador a cada URL não correspondente - incluindo nosso arquivo inexistente index.html



(como você se lembra, nós o renomeamos para index.pug



, e ele não está no diretório dist



).



const html = renderToString(
  <App />
);
      
      





Com a ajuda renderToString



, exibimos nosso aplicativo. O código se parece exatamente com o ponto de entrada, mas essa correspondência é opcional.



res.render(path.join(__dirname, 'src/index.pug'), {
  app: html
});
      
      





Agora que renderizamos o HTML, pedimos ao express para renderizar o arquivo em resposta index.pug



e substituir a variável app



pelo HTML que recebemos.



app.listen(3000, () => console.log('listening on port 3000'));
      
      





Por fim, garantimos que o servidor seja inicializado e configurado para que ele escute na porta 3000.

Agora, só precisamos adicionar o script necessário para package.json



:



"scripts": {
  "server": "babel-node server.js"
}
      
      





Agora, ao chamar yarn run server



, devemos receber a confirmação de que o servidor está realmente funcionando. Vá para localhost : 3000 no navegador , onde, novamente, devemos ver nosso aplicativo. Se olharmos para o código-fonte nesta fase, vemos:



<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="root">div data-reactroot="">Welcome to SSR powered React application!</div></div>
    <script src="bundle.js"></script>
  </body>
</html>
      
      





Se tudo estiver assim, significa que a renderização do servidor está funcionando conforme o esperado e você pode começar a estender seu aplicativo!



Por que ainda precisamos de bundle.js?



No caso de um aplicativo extremamente simples, que é considerado aqui, não é necessário incluir bundle.js - sem este arquivo, nosso aplicativo ainda funcionará. Mas, no caso de um aplicativo real, você ainda precisará incluir esse arquivo.



Isso permitirá que navegadores que podem lidar com JavaScript assumam o trabalho e interajam com sua página já no lado do cliente, e aqueles que não sabem como analisar JS irão para a página com o HTML necessário que o servidor retornou.



Coisas para lembrar



Apesar do fato de que a renderização do servidor parece bastante simples, ao desenvolver aplicativos, você precisa prestar atenção a alguns tópicos que à primeira vista não são muito óbvios:



  • , , . , , HTML, this.state



    ,
  • componentDidMount



    — , , . , , . , ( res.render



    ) , . -
  • react (. @reach/router react-router) , URL, . !



All Articles