5 dicas simples para deixar seu site mais rápido
Tags: webdev, performance, tutorial, braziliandevs
Published: 2022-10-16 22:24:11

## Introdução

Otimizar performance é parecido com economizar dinheiro.
Não adianta você economizar centavos apagando as luzes da casa, enquanto usa ar condicionado horas por dia.

Assim como economizar dinheiro, para otimizar performance é importante ter métricas para atacar a principal causa do problema, senão você vai estar jogando energia fora com algo que talvez não traga nenhum benefício perceptível.

A métrica também é importante para confirmar que nossa alteração realmente melhorou a performance.

No browser, você pode usar as diversas ferramentas que o DevTools oferece, como Performance, Performance Monitor, Memory, Network, etc, e também a extensão LightHouse.

![Ferramenta Performance do DevTools](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jaxzbq6f3dniv6n7mcqp.png)

Você também pode usar outras ferramentas como Elastic APM e similares para monitorar tanto o front-end quanto o back-end.

![Elastic APM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/893ycdh1xc3n35yryesz.png)

Nesse artigo eu não vou me basear em métricas, porque na realidade vou dar sugestões de coisas que você pode experimentar para melhorar performance.

A parte de medir e validar se houve melhoria de performance fica contigo.

## 1 - Use debounce e throttle

Debounce e throttle são funções para atrasar ou limitar quantas vezes um processamento é realizado.
O JavaScript não tem nativamente essas funções, porém você pode implementar ou usar uma biblioteca para isso.

Nesse exemplo aqui eu botei um event listener no evento de `scroll`.
Eu fiz scroll por poucos segundos e esse listener foi disparado **240 vezes**.

![window.onscroll = () => console.log('scroll')](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ch971ytnp716rzj2lixw.png)

Se eu tiver alguma função que faz algum processamento pesado no meu event listener, eu vou travar a aba do navegador disparando esse processamento dezenas de vezes.

### Debounce

O `debounce` atrasa a execução de uma função por um tempo determinado pelo usuário.
Por exemplo:

```jsx
window.onscroll = debounce(() => console.log('scrolled'), 1000)
```

O `console.log` só vai ser executado 1 segundo depois do **último** evento de scroll.

### Throttle

O `throttle` executa a chamada para a função num intervalo definido pelo usuário.

```jsx
window.onscroll = throttle(() => console.log('scrolled'), 1000)
```

Nesse caso, se o usuário ficar fazendo scroll, **a cada segundo** vai ter um `console.log`.

## 2 - Coloque só o crítico no `head`

Se você coloca todos os seus JS e CSS no `head` do seu index.html, você está fazendo com que sua página fique **em branco** até que **todos** esses arquivos carreguem.

Esse é o momento que vários usuários **desistem de usar seu site**!

```html
<!DOCTYPE html>
<html lang="pt">
  <title>Meu site lento</title>
  <link rel="stylesheet" href="estilo_critico.css">
  <link rel="stylesheet" href="estilo_nao_critico1.css">
  <link rel="stylesheet" href="estilo_nao_critico2.css">
  <script src="script_nao_critico1.js"></script>
  <script src="script_nao_critico2.js"></script>
</head>
<body>
  <!-- só vai aparecer depois que todos os arquvios forem baixados e processados -->
  <h1>Bem vindo!</h1>
</body>
</html>
```

Uma regra simples é a seguinte:

> só coloque no `head` o que você **realmente** precisa que seja carregado e processado antes do browser mostrar algo na tela.
> 

Só coloque estilos no `head` que você considere críticos, exemplo: fontes, layout básico, cores.

Só coloque scripts no `head` se você realmente precisa que eles façam algo antes da página ser renderizada.

Se você não precisa que um estilo ou script execute antes da página ser renderizada, coloque no fim do `body`.

Resultado do html anterior:

```html
<!DOCTYPE html>
<html lang="pt">
  <title>Meu site otimizado</title>
  <link rel="stylesheet" href="estilo_critico.css">
</head>
<body>
  <!-- vai aparecer assim que o estilo_crítico.css carregar -->
  <h1>Bem vindo!</h1>
  
  <link rel="stylesheet" href="estilo_nao_critico1.css">
  <link rel="stylesheet" href="estilo_nao_critico2.css">
  <script src="script_nao_critico1.js"></script>
  <script src="script_nao_critico2.js"></script>
</body>
</html>
```

> Obs.: no caso de scripts, você até pode deixá-los no `head`, desde que use [defer](https://www.w3schools.com/tags/att_script_defer.asp)
> 

Saiba mais:

[Critical rendering path - Web performance | MDN](https://developer.mozilla.org/en-US/docs/Web/Performance/Critical_rendering_path)

## 3 - Coloque o crítico inline

Fazer uma requisição HTTP é algo que pode ser bem lento, principalmente em redes móveis, porque o tempo da requisição chegar no servidor e voltar é muito grande.

Dessa forma, no primeiro carregamento da sua página você deve buscar fornecer o crítico num único arquivo HTML.

```html
<html lang="pt">
  <title>Meu site rapidão</title>
</head>
<body>
  <style>
    /* meu estilo crítico */
    body {
      color: red;
    }
  </style>
  <!-- vai aparecer mais rápido pois não vai fazer uma req. pro estilo crítico -->
  <h1>Bem vindo!</h1>
  
  <link rel="stylesheet" href="estilo_nao_critico1.css">
  <link rel="stylesheet" href="estilo_nao_critico2.css">
  <script src="script_nao_critico1.js"></script>
  <script src="script_nao_critico2.js"></script>
</body>
</html>
```

Um site que faz isso bem é o G1:
![G1 parcialmente carregado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k0qlpiqxa17tsbv3zw7c.png)

Mesmo desativando o cache e simulando um 3G lento, em **menos de 2 segundos** o texto da notícia e o layout básico da página foi carregado, o que para um usuário é bem aceitável. 

Já o Github é outra história: 

O site carrega **8 estilos** no `head`.

![First Contentful Paint do Github](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4oqruvnpizgcvn1k4m7.png)

O navegador levou **19 segundos** para mostrar algo na tela! Muitos usuários desistiriam no caminho. 

Também é preciso ter um cuidado especial com single page applications, pois elas são todas renderizadas no cliente, e muitas vezes acabam fazendo muitas requisições.
Você pode experimentar reduzir o número de requisições usando GraphQL ou outras técnicas como server side rendering.

Saiba mais:

[Inlining critical CSS](https://imkev.dev/inlining-critical-css)

## 4 - Use cache de requisições HTTP

Caching é uma ótima estratégia para diminuir tempo de carregamento de páginas e entregar uma experiência instantânea para o usuário.

### 4.1 - `Cache-Control` e `max-age`

O `cache-control` é um header de resposta HTTP que, entre outras coisas, diz para o navegador por quanto tempo ele pode salvar o resultado de uma requisição para usar no futuro.

Esse tempo é definido pelo `max-age`, que é um valor sempre em segundos.

Exemplo: cache de dois minutos
```
Cache-Control: max-age=120
```

Se eu fizer umas requisições com o mesmo path, exemplo `/img/1234.png`, a primeira vai trazer do back-end e a salvar em cache por 2 minutos.

Se na segunda vez ainda não tiver passado 2 os minutos, o resultado da requisição anterior vai ser reutilizado, sem nem precisar falar com o back-end.

Isso traz um benefício de performance enorme, porque trazer do disco pode ser milhares de vezes mais rápido do que trazer do servidor.

### 4.2 - ETag

ETag é um outro header de resposta que também serve como validação de cache.
O ETag é pensado para ser um código curto identificador daquela resposta.

Em resumo:
- respostas idênticas devem ter o mesmo ETag
- respostas distintas devem ter ETags diferentes

O motivo disso é que quando o navegador fizer uma requisição que responda com um ETag, ele vai salvar a resposta no disco e nas próximas vezes ele vai enviar o ETag para o servidor, falando "Se o body mudar, me retorna o novo body. Se não, me retorne um status 304 (Not Modified) sem body.".

ETags podem ser implementadas no lado do servidor fazendo hash do body da resposta.

Se for uma requisição para um arquivo, pode ser baseada no momento de última modificação.

Se for uma requisição que consulta dados do banco, também pode usar a última vez que um dado foi alterado...
Use sua imaginação.

### 4.3 - Combinando `max-age` com `ETag`

Quem disse que você precisa escolher um ou outro?
Você pode perfeitamente pôr um cache de 5 minutos numa imagem, e também usar uma ETag.

Quando o cache expirar, o navegador vai fazer uma requisição checando se a ETag mudou, e se não, vai manter o dado que ele já tem em cache por mais 5 minutos.

Saiba mais:

[Cache-Control - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)

[ETag - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag)

## 5 - Mostre menos coisa na tela de uma vez

Mais elementos na tela significa mais coisa em memória, mais elementos para recalcular o estilo e layout, mais event listeners, mais trabalho para a GPU, etc…

Se tiver muitas imagens, coloque lazy loading, porque carregar muitas imagens em paralelo vão dar uma sensação de maior lentidão e pode até travar o seu site.

![Lazy loading de imagens](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/auzljrxxgatw591ipkt3.png)

Use paginação e/ou infinite scroll para controlar a quantidade de conteúdo na tela.

![Paginação](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p6rtxcmbt2uz9237zhq5.png)

![Infinite scroll](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x69vhwqoyu0xhfbwprox.png)

Dessa forma o usuário não precisa carregar megabytes de dados de uma vez só para ver o início do conteúdo.

## Conclusão

Como o título avisou, foram dicas bem simples, porém que eu considero bem úteis e eficazes no desafio de entregar boa performance.

Eu tenho vontade de falar sobre outros assuntos mais desafiadores, como memory leaks, repaint and reflow, e estou lendo o livro [High Performance Browser Networking do Ilya Grigorik](https://www.amazon.com.br/High-Performance-Browser-Networking-Developer/dp/1449344763/ref=sr_1_1?qid=1665932719&refinements=p_27%3AIlya+Grigorik&s=books&sr=1-1&ufe=app_do%3Aamzn1.fos.6a09f7ec-d911-4889-ad70-de8dd83c8a74) para transformar numa série de artigos também.

Por hoje é só. Aguardo seu feedback!