O que é Server Side Rendering e como usar na prática
Server Side Rendering ou SSR é o processo de pegar todos os Javascript e todos os CSS de um site que, geralmente é carregado no browser (client-side), e renderizá-los como estático do lado do servidor.
Com isso podemos obter um site com um tempo de carregamento reduzido e totalmente indexável por SEO’s. Mas para entender um pouco desse tempo de carregamento, temos que entender os princípios do carregamento que é realizado no seu browser para ver o real benefício de se usar algum framework SSR.
Quando uma página é carregada ela necessita de input (que chamamos de assets). Estes são os primeiros conteúdos a serem entregues para o browser para que, a partir dali, ele possa realizar o seu trabalho e renderizar a página o mais rápido para o usuário.
Para continuarmos a abordagem, vamos entender como o browser recebe e faz o uso disso no nosso site.
Inicialmente quando acessamos um conteúdo na web, temos uma referência no documento HTML que nos indica o que será carregado e também será a primeira coisa que o browser vai receber, esse documento contém todas as referências necessárias para os seguintes assets, como imagens, CSS e javascripts.
O browser sabe que a partir disso ele precisa ir a algum lugar para localizar e baixar esses assets, enquanto ele vai construindo o documento. Então, mesmo que o browser contenha toda a estrutura HTML, ele não poderá renderizar algo amigável até que o CSS correspondente, que contém toda a sua estilização, seja também carregado.
Uma vez feito, o seu browser poderá ter algum conteúdo relevante para entregar ao usuário. Após esse processo finalizado, o browser irá baixar todos os arquivos javascripts e é aí que costuma estar a maioria dos problemas.
Os arquivos poderão ser grandes e com o uso de uma internet ruim o tempo de carregamento poderá ser grande. Desta maneira, a experiência do seu usuário não será a ideal, o que pode piorar se a primeira renderização depender de algum arquivo javascript.
O grande diferencial de usar SSR é que podemos entregar, quase que imediatamente, um conteúdo significante para o usuário.
Isto acontece porque o HTML e seus principais assets são carregados em um mesmo arquivo e entregue ao client.
Portanto, eliminamos algumas etapas no processo de download de assets e o browser fica encarregado somente de carregar os componentes criados e seus fluxos que também ficam em um arquivo minificado. Ainda como boa prática, recomendo fortemente a utilização de algum CDN para realizarmos o cache desses arquivos.
Benefícios ao se usar SSR
- Carregamento mais rápido na renderização inicial
- Por termos toda a estrutura pronta, temos uma página HTML totalmente indexável. Nesse caso ótimo para SEO e Crawlers.
Diferenças entre CSR e SSR
Com dois infográficos bem simples irei compilar todas as ideias passadas anteriormente para poder explicar melhor ainda as diferenças entre oClient Side Rendering e o Server Side Rendering.
SSR na Prática
Com toda a evolução de Javascript, hoje temos uma série de frameworks que abstraem a complexidade da criação de estrutura e até de desenvolvimento e nesse caso eu apresentarei um pouco do framework NextJS.
Benefícios ao se utilizar NextJS
- Server Rendered por Default
- Divisão automática de código para carregamentos de páginas mais rápidos
- Criação de rotas simples
- Ambiente baseado em Webpack com suporte a HMR (Hot Module Replacement)
- Capaz de integrar com Node.js HTTP server
- Configurações customizavéis de Babel e Webpack
Instalação via NPM
npm install — save next react react-dom
Instalação Via Yarn
yarn add next react react-dom
Começando com NextJS
Criação do package.json com o seguinte conteúdo.
{
"name": "**Nome do Projeto**",
"version": "0.0.1",
"scripts": {
"dev": "next"
},
"dependencies": {
"next": "7.0.1",
"react": "16.4.2",
"react-dom": "16.4.2"
}
}
O NextJS trabalha com uma estrutura básica para criação de pasta com roteamento simples.
Isso significa que toda página deverá ser criada dentro de um diretório chamado pages
e qualquer página criada dentro desse diretório já será acessado pelo nome colocado, devido ao roteamento baseado em estrutura de arquivo.
Então, se eu criar um arquivos dentro do pages com o nome posts ela estará acessível em /posts
.
Com poucas linhas de código já temos algo funcionando e ainda contamos com o HMR. Tudo que fizer no código será enviado automaticamente para o browser sem termos que dar refresh para visualizar as alterações.
Mas sabemos que isso não é o bastante para criar um sistema robusto e que conte com busca de dados em uma API e coisas do tipo. Para tal o NextJS já proporciona uma estrutura inicial que será utilizada tanto Server Side quanto Client Side, a função getInitialProps.
import React from 'react'
import axios from 'axios'
export default class extends React.Component {
static async getInitialProps ({ req }) {
const res = await axios.get('https://api.github.com/repos/zeit/next.js')
return { stars: res.data.stargazers_count }
}
render() {
const { stars } = this.props
return (
<div>
Next stars: <strong>{stars}</strong>
</div>
)
}
}
O método getInitialProps recebe o contexto do objeto com as seguintes propriedades:
- pathname — nome do caminho da URL
- query — queryString da url
- asPath — String do caminho atual
- req — Objeto HTTP da chamada (servidor apenas)
- res — Objeto HTTP de resposta (servidor apenas)
- Err — Objeto de error se houve alguma falha durante a renderização
Vale ressaltar que o getInitialProps será sempre o primeiro método a ser acessado e ele será sempre disparado no javascript que fica dentro do pages. As demais chamadas poderão ser realizadas dentro de funções e de preferência após o componente carregar.
Com o NextJS ainda é possível injetar dados no <head> da páginas. Este caso é muito utilizado, caso você queira setar algum meta tag somente depois que o conteúdo for carregado, por dependência de alguma chamada em uma API, o acesso poderá ser feito da seguinte maneira:
import React from 'react'
import Head from 'next/head'
class HtmlHead extends React.Component {
render () {
const { title, url, description } = this.props
return (
<Head>
<title>{titleTuned}</title>
<meta name="description" content={description} />
<meta name="abstract" content={description} />
<meta property="og:title" content={title} />
<meta property="og:url" content={url} />
</Head>
)
}
}
export default HtmlHead
Ainda é possível manipular o conteúdo a ser carregado no documento adicionando um wrapper. Por padrão, o NextJS permite configurar qualquer propriedade entre o <html> e o <body>, essas tags estão fora do escopo das páginas. Mas como faço se eu quiser customizar essas tags?
Nesse caso, isso é possível criando um arquivo dentro do pages chamado _document.js, permitindo carregar estilos, scripts e meta tags. Veja no exemplo abaixo:
import Document, { Head, Main, NextScript } from "next/document";
export default class MyDocument extends Document {
render() {
return (
<html>
<Head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="/_next/static/style.css" />
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
Como dito anteriormente, o NextJS permite o uso de servidores NodeJS, embora o comportamento padrão do NextJS é de servir o que estiver armazenado dentro de pages como sua própria rota.
No entanto, à medida que um aplicação cresce, talvez seja necessário ajustar isso ou adicionar uma lógica personalizada do lado do servidor. Felizmente, o NextJS expõe todo o servidor ExpressJS para nós.
Para realizar tais mudanças é necessário realizar alguns ajustes no nosso código. Primeiro abra o package.json e troque o conteúdo de “dev” para node server.js.
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
Agora, iremos criar um arquivo chamado server.js na raiz do projeto. Veja a seguir um template sugerido na documentação do NextJS:
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(3000, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
Com isso temos nosso próprio servidor, que nos possibilita gerenciar as rotas e dizer para o NextJS qual página renderizar quando houver uma chamada no nosso servidor. O responsável por fazer essa renderização é o app.render().
Nele podemos passar a referência de qual página renderizar usando a referência de rotas app.render(req, res, ‘/posts’, query).
Acredito que com esse guia, você terá o conhecimento, mesmo que embrionário sobre os pontos positivos de se utilizar SSR, e como começar a construir sua próxima aplicação utilizando-se de um framework SSR.
Peace!