Olá, estudante da Alura! Tudo bem? Eu sou Pedro Mello, instrutor front-end e quero te dar as boas-vindas a mais um curso de React na plataforma da Alura.
Audiodescrição: Pedro se identifica como um homem branco. Tem olhos castanhos, cabelo e barba escuros. Usa piercing no septo e camiseta preta. Ao fundo, ambiente iluminado pela luz azul.
Para este curso, vamos trabalhar em um projeto muito interessante, o projeto da ByteBooks, uma editora de livros. Talvez seja um projeto que já conheçam de algum lugar.
Para esse projeto, vamos explorar a memorização de componentes e transformar o projeto que, no momento, é estático em um projeto server-side rendering (SSR ou renderização do lado do servidor). Vamos fazer esse processo de transformação manualmente, partindo do zero.
Além disso, entender os pontos de melhoria de desempenho com o nosso grande aliado, o Google Lighthouse.
No momento, ele mostra uma pontuação baixa para a aplicação, principalmente referente ao tempo da primeira e última aparição de conteúdo na tela.
E temos novidades! Nosso projeto agora, além da listagem e do filtro, tem a página de detalhes do livro. Também conseguimos adicionar e remover quantidade, passar para a sacola, finalizar um pedido.
É um projeto que vai agregar muito para você - tanto em projetos pessoais quanto na sua carreira. Todos os conteúdos que vamos abordar, desde memorização até transformação de um projeto em server-side rendering, podem ser utilizados em qualquer projeto que você tenha.
É necessário que você já saiba o funcionamento dos Hooks e saiba mexer no React com TypeScript.
O ideal é que você já tenha um conhecimento prévio sobre o Google Lighthouse, pois vamos usá-lo durante este curso para avaliar métricas para conferir se as melhorias que fizemos realmente surtiram efeito.
Esperamos você no nosso primeiro vídeo!
Para esse curso, temos uma proposta diferente para o projeto ByteBooks.
Nosso projeto construído com React e Vite sofreu algumas modificações, o que afetou as notas apresentadas na aba do Google Lighthouse. A performance diminuiu consideravelmente com a inclusão de novas rotas e funcionalidades na aplicação.
Atualmente, temos uma nova área de sacola na aplicação e uma página de detalhes do livro, onde podemos aumentar a quantidade de itens para compra.
Essas novas funcionalidades que fizeram com que, infelizmente, essa performance tivesse uma redução em seu resultado final.
Se pensamos num contexto de mercado de trabalho, isso é muito comum. Podemos estar trabalhando num projeto e outro time pode pegar esse projeto para trabalhar e fazer algumas modificações que não seguem completamente as boas práticas de performance que já aprendemos.
Nesse ponto, é preciso estudar alternativas para melhorar essa performance, não só com os mecanismos que já conhecemos, mas também com outras ferramentas e formas de fazer renderização da aplicação.
Para entender o contexto de como o Vite funciona para renderizar uma aplicação, quando iniciamos um projeto com o Vite e não passamos nenhuma flag sobre uma renderização específica ou alguma configuração extra, ele, por padrão, vai gerar um site que une o melhor do mundo de uma aplicação estática (como o Gatsby ou outros frameworks) e também traz o dinamismo de single-page applications (SPA ou aplicativo de página única).
Se estivéssemos iniciando um projeto com o Vue, React ou Angular no modelo sem ser de renderização pelo lado do servidor, o Vite vai unir o melhor do estático e o melhor do single-page application.
A single-page application, que é o caso do nosso projeto ByteBooks, funciona muito do lado do cliente, ou seja, ela depende 100% da máquina que a pessoa usuária está utilizando.
Portanto, se o computador da pessoa usuária está com algum problema, isso vai impactar na performance final dessa aplicação.
Mas, se utilizamos um mecanismo de renderização pelo lado do servidor, tiramos toda essa responsabilidade de renderização do lado do cliente.
Assim, ao invés de primeiro gerar o bundle, baixar o JavaScript no computador da pessoa usuária e depois renderizadar, passamos a ter um servidor que vai cuidar dessa geração de código.
Desse modo, ele vai gerar um index.html
. Ou seja, o que vamos estar vendo não é mais um JavaScript, internamente vai ter o nosso JavaScript funcionando, mas ele nos retorna um HTML com o site funcionando normalmente, podendo utilizar os hooks do React sem nenhum problema.
Ao tirar essa responsabilidade da pessoa usuária e trazer para o servidor, vamos aumentar muito a performance da aplicação.
Vamos deixar o link da documentação do Vite para você, explicando sobre a renderização do lado da pessoa usuária e também explicando um pouco mais sobre como funciona a renderização do lado do servidor.
Para começar a fazer a alteração do projeto, vamos explorar o código do ByteBooks. Se você fez a configuração inicial do seu projeto, seu código vai estar igual ao nosso nesse exato momento.
Primeiro, precisamos criar um arquivo de um servidor que vai fazer essa transformação do código JavaScript em um index.html
. Na raiz do projeto, vamos criar um arquivo chamado server.js
.
server.js
:
//server.js
import fs from 'node:fs/promises';
import express from 'express';
// Constants
const isProduction = process.env.NODE_ENV === 'production';
const port = process.env.PORT || 5173;
const base = process.env.BASE || '/';
// Cached production assets
const templateHtml = isProduction ? await fs.readFile('./dist/client/index.html', 'utf-8') : '';
const ssrManifest = isProduction
? await fs.readFile('./dist/client/ssr-manifest.json', 'utf-8')
: undefined;
// Create http server
const app = express();
// Add Vite or respective production middlewares
let vite;
if (!isProduction) {
const { createServer } = await import('vite');
vite = await createServer({
server: { middlewareMode: true },
appType: 'custom',
base,
});
app.use(vite.middlewares);
} else {
const compression = (await import('compression')).default;
const sirv = (await import('sirv')).default;
app.use(compression());
app.use(base, sirv('./dist/client', { extensions: [] }));
}
// Serve HTML
app.use('*', async (req, res) => {
try {
const url = req.originalUrl.replace(base, '');
let template;
let render;
if (!isProduction) {
// Always read fresh template in development
template = await fs.readFile('./index.html', 'utf-8');
template = await vite.transformIndexHtml(url, template);
render = (await vite.ssrLoadModule('/src/entry-server.tsx')).render;
} else {
template = templateHtml;
render = (await import('./dist/server/entry-server.js')).render;
}
const rendered = await render(url, ssrManifest);
const html = template.replace(`<!--app-html-->`, rendered.html ?? '');
res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
} catch (e) {
vite?.ssrFixStacktrace(e);
console.log(e.stack);
res.status(500).end(e.stack);
}
});
// Start http server
app.listen(port, () => {
console.log(`Server started at http://localhost:${port}`);
});
Nesse arquivo, vamos colar um código para criar um servidor baseado no Express, no servidor Node. Também instanciamos o Vite, verificamos se é ambiente produtivo e aplicamos os middlewares do Vite para atuar na geração da build para o index.html
.
E nessa no bloco do app.use()
, entre a linha 37 e 63, é onde toda a mágica está acontecendo.
Dentro do if
, o Vite lê o arquivo index.html
que está na raiz do nosso projeto, faz a transformação, carrega o módulo de server-side-handling a partir de um arquivo que vamos criar, chamado entry-server.tsx
, e depois faz o render fazendo um replace()
no index.html
com a tag <!--app-html-->
, que vamos fazer agora também.
Vamos salvar o arquivo de servidor. Você não precisa se preocupar com os erros do process
marcado pelo Lint.
Agora, vamos abrir o index.html
, na linha 10, entre a tag de abertura e fechamento da div
, vamos adicionar o comentário <!--app-html-->
que também está no server.js
.
index.html
:
<div id="root"><!--app-html--></div>
Nosso script atualmente está carregando através do arquivo main
, ou seja, o arquivo que gera o ReactDOM dentro do index.html
. Nele, temos o provider do Redux, o gerenciador de rotas. Nesse momento, ele está utilizando esse arquivo como principal.
main.tsx
:
ReactDOM.createRoot(document.getElementById('root')!).render(
<Provider store={store}>
<Header />
<Router>
<Routes />
</Router>
<Footer />
</Provider>
);
Em server.js
, reparem que o nosso entry point para a aplicação não vai ser mais o main
, vamos ter um para o servidor e vamos ter um para o lado do cliente também. Só que essa configuração vamos fazer junto na nossa próxima aula.
Esperamos você lá para terminar de configurar a nossa aplicação para rodar a renderização do lado do servidor.
Agora vamos finalizar a configuração do projeto para rodar no server-side rendering. Para isso, precisamos instalar algumas dependências no projeto.
Nas dependencies
do arquivo package.json
, adicionamos uma vírgula e colamos as dependências de compression
, express
e siv
, que vão funcionar conjuntamente com nosso arquivo server.js
.
package.json
:
"dependencies": {
"@fontsource/poppins": "^5.0.8",
"@reduxjs/toolkit": "^1.9.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.11.0",
"react-redux": "^8.1.3",
"react-router-dom": "^6.16.0",
"sleep-promise": "^9.1.0",
"compression": "^1.7.4",
"express": "^4.18.2",
"sirv": "^2.0.3"
},
Além disso, como estamos em um projeto de TypeScript, precisamos instalar algumas dependências de @types
para o TypeScript poder reconhecer essas alterações que estamos fazendo.
Ainda no package.json
, dentro de devDependencies
, adicionamos uma vírgula e colamos as dependências do @types/express
, @types/node
, @vitejs/plugin-react
, cross-env
, typescript
e vite
.
Note que algumas dependências estão conflitando, pois estão com versões mais atualizadas do que as que já estavam presentes. Por isso, removemos as duplicadas e deixamos as mais atualizadas que inserimos.
Até 2022, o Vite não tinha suporte nativo para server-side rendering. A partir da versão 5.0, ele passa a ter esse suporte sem precisar de algum outro framework ou uma outra biblioteca para resolver isso.
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"autoprefixer": "^10.4.16",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.30",
"tailwindcss": "^3.3.3",
"@types/express": "^4.17.21",
"@types/node": "^20.9.0",
"@vitejs/plugin-react": "^4.2.0",
"cross-env": "^7.0.3",
"typescript": "^5.2.2",
"vite": "^5.0.0"
}
Agora, não temos mais nenhuma dependência duplicada no projeto.
Atenção! O Vite não funciona mais com a versão 18 do Node. É importante que esteja na versão 18 ou superior. Atualmente, a versão 20 é a versão long-term service (LTS), mantida por pelo menos mais 2 ou 3 anos.
Estamos utilizando a versão 18.18.0
, que foi a versão que iniciamos esse projeto. Se você estiver na versão 20 também, o projeto também vai funcionar normalmente. Mas, se você quiser seguir a versão que utilizamos, basta executar o seguinte comando:
nvm use 18.18.0
Agora, vamos executar npm install
, já que fizemos essas alterações no package.json
.
npm i
Com todas as dependências finalmente instaladas, ainda temos mais algumas configurações para fazer no projeto.
Atualmente, nosso arquivo de entrada está sendo o main.tsx
. Agora, como vamos trabalhar com a renderização do lado de servidor, precisamos de dois arquivos. Um que vai ser um entry-client
, que será a entrada das informações do lado do cliente, e um entry-server
, que será o arquivo responsável por gerar as informações do lado do servidor.
Dentro de "src" no projeto da ByteBooks, clicamos com o botão direito para criar um novo arquivo chamado entry-client.tsx
. Colamos um código que já estava pronto, ajustamos a importação do arquivo de rotas.
entry-client.tsx
:
import './index.css';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom';
import { Routes } from './routes';
ReactDOM.hydrateRoot(
document.getElementById('root') as HTMLElement,
<Router>
<Routes />
</Router>
);
Podemos salvar o arquivo de entrada para o lado do cliente.
Agora, criamos o arquivo de entrada do lado do servidor, que será chamado entry-server.tsx
e também estará no diretório "src".
entry-server.tsx
:
import ReactDOMServer from 'react-dom/server';
import { StaticRouter as Router } from 'react-router-dom';
import { Routes } from './routes';
export function render(url: string) {
const html = ReactDOMServer.renderToString(
<Router location={url}>
<Routes />
</Router>
);
return { html };
}
O código é bem semelhante com o lado do cliente, com a diferença que o nosso render
está recebendo uma url
como parâmetro e está passando para o nosso arquivo de rotas.
O nosso arquivo de rotas do lado do servidor está importando o StaticRouter
, ou seja, do lado do servidor, as nossas rotas vão ser tratadas como rotas estáticas.
Enquanto na nossa entrada do cliente, a função tem algumas informações de hidratação, que vamos lidar em uma aula futura. Mas, o Router
está vindo do BrowserRouter
.
Ou seja, a forma que uma aplicação lida com as rotas do lado do cliente e do lado do servidor é diferente. Por isso, precisamos fazer essa diferenciação na hora de importar o Router
para as nossas rotas.
Com a entrada do servidor e a entrada do lado do cliente finalizadas, vamos passar o Provider
do Redux, o Header
e Footer
para o arquivo de rotas.
Para isso, colocamos a tag de abertura do Provider
e o Header
antes do Switch
e a tag de fechamento do Provider
e o Footer
depois do Switch
, importando o que for necessário.
index.tsx
:
import { Provider } from 'react-redux';
import { store } from '../store/store';
import Header from '../components/Header';
import Footer from '../components/Footer';
export const Routes = () => (
<Provider store={store}>
<Header />
<Switch>
<Route exact path='/' component={Catalog} />
<Route path='/book' component={BookDetail} />
</Switch>
<Footer />
</Provider>
);
Agora, não precisamos mais do arquivo main.tsx
. Por quê? Quando rodarmos o nosso servidor, a partir de agora, o canal de entrada vai ser o entry-client
ou o entry-server
. Por isso, o main
não vai ser mais utilizado a partir de agora.
Para finalizar essa configuração do arquivo HTML, precisamos acessar o index.html
e trocar a referência de onde ele está pegando o script.
Ao invés de pegar do main
, vamos fazer com que ele pegue do entry-client
, que será responsável por exibir a entrada pelo lado do cliente após a compilação do lado do servidor.
index.html
:
<script type="module" src="/src/entry-client.tsx"></script>
Com todos os arquivos configurados e todas as dependências instaladas, vamos rodar npm run dev
para abrir o nosso projeto.
npm run dev
No navegador, o projeto agora está rodando. A navegação de rotas, a aba de sacola, o retorno para o início e o filtro continuam funcionando normalmente.
Finalizamos a transformação do projeto que estava em um gerador estático, numa versão antiga do Vite, para agora um modelo SSR, sendo uma renderização do lado do servidor.
Na próxima aula, vamos adentrar em alguns outros conceitos do funcionamento do server-side rendering e também discutir sobre os componentes do lado do servidor.
O curso React: utilizando SSR para otimizar a performance da aplicação possui 119 minutos de vídeos, em um total de 38 atividades. Gostou? Conheça nossos outros cursos de React em Front-end, ou leia nossos artigos de Front-end.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.