Alura > Cursos de Front-end > Cursos de React > Conteúdos de React > Primeiras aulas do curso React: otimização avançada de performance

React: otimização avançada de performance

Entendendo métricas de performance em aplicações Web - Apresentação

Apresentando o instrutor e o curso

Olá, estudante da Alura! Meu nome é Pedro Celestino de Mello, sou instrutor na Alura.

Audiodescrição: Pedro é um homem branco, com cabelos escuros e barba escura. Ele veste roupas pretas e está em um ambiente iluminado por luzes azuis e rosas.

Seja bem-vindo a mais um curso de React na nossa plataforma da Alura. Este curso é destinado a você, estudante, que já se perguntou como melhorar ainda mais a performance da sua aplicação. Ou para quem está trabalhando em uma aplicação no dia a dia e percebe alguns problemas, como um scroll fantasma, onde a tela dá um pulo e muda de posição, fontes que demoram para carregar, ou reclamações de usuários sobre o carrinho de compras travando. Este curso é para você.

Introduzindo a aplicação Zoop Store e objetivos do curso

Atualmente, estou exibindo na tela a Zoop Store, que é a aplicação com a qual trabalharemos durante este curso. No início, a pontuação de performance é de 57 pontos, e nosso objetivo é alcançar 99 pontos.

Neste curso, vamos reforçar técnicas de memoização e aprender sobre ferramentas de monitoramento da aplicação, para extrair dados e informações em tempo real sobre a performance. Fique conosco, estudante, e vamos juntos melhorar suas habilidades em React.

Explorando ferramentas e padrões de otimização

O Google Lighthouse será uma das ferramentas que vamos reforçar aqui nos nossos conhecimentos. Também trabalharemos com alguns patterns (padrões), os famosos pattern compositions (composições de padrões). Vamos explorar os conhecidos custom hooks (ganchos personalizados) e outros padrões que discutiremos ao longo das aulas.

Além disso, abordaremos a questão da otimização de imagens, incluindo o tamanho que utilizaremos para nossas imagens. Vamos construir um componente de otimização de imagens do zero para nossa aplicação, utilizar o Lazy Loading (carregamento preguiçoso) e estender esse conceito para carregar componentes e páginas sob demanda, otimizando assim o bundle final da nossa aplicação.

Avançando para tópicos avançados e requisitos do curso

Se tudo isso não for suficiente, finalizaremos com um tópico avançado: trabalhar com as threads (linhas de execução) dos navegadores, ou seja, os web workers (trabalhadores da web), utilizando APIs nativas do próprio navegador para abstrair algumas lógicas e melhorar a performance para o usuário.

Para acompanhar este nível, será necessário já ter um conhecimento mais avançado sobre o funcionamento do React e dos hooks. Utilizaremos bastante o Tailwind para a parte de estilização, então é importante que esses conceitos já estejam bem internalizados. Também abordaremos o gerenciamento global em aplicações React, utilizando o useContext e, posteriormente, introduziremos o Zustand para melhorar a performance do nosso gerenciamento global.

Convidando os estudantes a participar

Vamos colocar a mão no código agora e entender de uma vez por todas quais são os gargalos e como resolver o problema de performance em aplicações React. Vamos juntos, pois estou aguardando vocês, estudantes.

Entendendo métricas de performance em aplicações Web - Aprendendo sobre métricas de performance

Discutindo a importância da performance em aplicações

O tema de performance é constantemente discutido, especialmente quando tratamos de produtos como aplicações e websites em ambiente de produção, ou seja, projetos reais. Provavelmente, são projetos com os quais trabalhamos no dia a dia, e é um assunto que não podemos negligenciar. Antes de abordarmos os fundamentos, é importante mencionar que muitos tópicos que revisaremos já foram vistos em trilhas anteriores, mas agora os exploraremos de maneira mais aprofundada, em um nível mais avançado e arquitetural. Vamos entender como a performance funciona e o que podemos fazer, além do que já aprendemos, para melhorar ainda mais a performance das nossas aplicações, com foco no monitoramento.

Este curso é importante porque a performance tem um impacto financeiro comprovado nas aplicações. Apresentaremos exemplos reais de sites que aplicaram métricas de performance e melhoraram seus resultados. Em média, cada segundo de atraso no carregamento visual resulta em uma redução de 7% na conversão de vendas. Para empresas multimilionárias ou bilionárias, 7% representa uma quantia significativa. Além disso, 100 milissegundos de melhoria equivalem a um aumento de 1% nas vendas. Portanto, focar na performance da aplicação pode aumentar o funil de conversão.

Exemplos de sucesso e a importância da performance em dispositivos móveis

Sites lentos têm uma relação direta com a perda de usuários. Quando um site demora para carregar e apresenta uma performance ruim, 53% dos usuários tendem a abandoná-lo. Exemplos de sucesso incluem o Tokopedia, um e-commerce da Indonésia, que melhorou a velocidade de carregamento e obteve 35% mais cliques e 8% mais conversões. A Vodafone na Itália otimizou o carregamento de seu site, resultando em um aumento de 8% nas vendas. A Walmart no Canadá, ao melhorar 100 milissegundos no carregamento, obteve 2% a mais em conversões e 98% em pedidos mobile.

Hoje, o usuário não está apenas no computador, mas também em tablets e celulares. Investir em performance não se limita mais ao desktop; é crucial considerar a visualização em dispositivos móveis, que tendem a ser mais lentos devido a questões de hardware. Devemos otimizar o código para melhorar o carregamento em dispositivos móveis.

Introduzindo os principais indicadores de performance

Os três principais indicadores de performance que utilizaremos são o LCP (Largest Contentful Paint), que mede o carregamento da página, o INP (Interaction to Next Paint), que avalia a responsividade em diferentes dispositivos, e o CLS (Cumulative Layout Shift), que verifica a estabilidade visual da página. Sites que atendem a esses critérios de performance têm 24% menos abandono de página.

Ao medir esses indicadores, normalmente o fazemos por página, pois algumas páginas são mais críticas em termos de performance do que outras. Devemos focar nas partes mais críticas do site para realizar medições e implementações. Muitas técnicas que utilizaremos já foram vistas anteriormente, mas agora as aplicaremos de forma mais avançada, entendendo o que realmente é necessário.

Detalhando os indicadores de performance

Destrinchando os indicadores, o LCP, por exemplo, mede o tempo até a imagem principal aparecer, semelhante ao tempo que um prato leva para chegar à mesa em um restaurante. A meta é que esse tempo seja inferior a 2.5 segundos, embora algumas aplicações modernas já trabalhem com um limite de dois segundos. A Google, que fornece as ferramentas para medições, estabelece que o indicador de carregamento deve ser de até 2.5 segundos.

O impacto que esse indicador tem na nossa aplicação é que ele representa a primeira impressão do usuário com o nosso site. Se o usuário acessa o site e percebe que está demorando para carregar, que o scroll está lento e que os elementos não estão carregando rapidamente, isso já causa uma impressão negativa em relação ao funcionamento da aplicação.

Explorando a responsividade e estabilidade visual

Nosso indicador de responsividade, o INP, mede o tempo entre clicar e a tela responder. Podemos fazer uma analogia com apertar o botão do elevador e ele acender. Nossa meta é que esse tempo seja inferior a 200 milissegundos, ou seja, 0,2 segundos, o que representa uma resposta quase instantânea.

Tratamos a questão da responsividade em relação a dispositivos móveis e tablets, além de computadores pessoais e laptops, porque o hardware dos celulares tende a ser mais lento. Às vezes, há um atraso ao clicar ou adicionar algo ao carrinho. Existe a parte inevitável do hardware e a parte do software, que podemos otimizar para oferecer um feedback visual melhor ao usuário quanto à responsividade da aplicação. O impacto é que, ao adicionar um produto ao carrinho, a resposta deve ser instantânea, indicando que o produto foi adicionado com sucesso.

Nosso último, mas não menos importante, indicador dessa tríade de performance é a estabilidade, ou CLS. Isso ocorre quando a página "pula" enquanto carrega. Imagine navegar em um site de marketplace e, ao clicar em "comprar", a tela pula, resultando em um clique errado e na adição do produto errado ao carrinho. A estabilidade visa manter a aplicação estável, sem alterações de layout, para evitar problemas ao interagir com a aplicação. O objetivo é que o CLS seja inferior a 0,1, garantindo estabilidade e evitando frustrações do usuário.

Recapitulando ferramentas e definindo metas de performance

Entre os três indicadores, melhorar o CLS já traz uma melhoria significativa na conversão de vendas do site. Focar na estabilidade pode trazer resultados positivos, mas é importante não negligenciar os outros indicadores.

Recapitulando, já vimos essas ferramentas anteriormente, mas hoje temos o Google Lighthouse, o Chrome DevTools Performance, o React DevTools Profiler (específico para React) e o Web Vitals Library, mantido pelo Google. O Web Vitals mede as três métricas principais (CLS, INP e estabilidade) em usuários reais, enquanto o Lighthouse faz simulações. Essa diferença é um diferencial importante, pois permite observar as métricas em uso real, mantendo a performance da aplicação sempre atualizada.

Antes de partir para o código, definimos algumas metas. O primeiro passo é rodar o Lighthouse três vezes para verificar variações nos valores. Se houver variações, calculamos a mediana e anotamos o score inicial. Em seguida, analisamos o tamanho do site, identificando partes que ocupam espaço desnecessário e podem causar carregamento demorado. Essa análise é feita após obter resultados confiáveis do Lighthouse.

Iniciando a implementação e análise de performance

O terceiro passo é definir metas SMART. Por exemplo, em vez de apenas querer deixar a aplicação mais rápida, definimos que o indicador de LCP deve passar de 3,1 segundos para 2 segundos. Com métricas definidas, podemos mensurar melhorias durante a implementação.

Vamos agora para o código, analisar a estrutura do projeto e começar a implementação do Web Vitals, rodando o Lighthouse três vezes e observando o comportamento do Web Vitals. Vamos juntos nessa sequência.

Entendendo métricas de performance em aplicações Web - Conhecendo o projeto Zoop Store

Apresentando o projeto Zoop Store

Vamos conhecer o projeto da Zoop Store. Provavelmente, já o vimos durante as trilhas de React na Alura. No entanto, a Zoop Store evoluiu, está mais moderna e precisa de nossa ajuda para melhorar a performance. Vamos entender primeiro como está a estrutura do projeto.

Na raiz do projeto, estamos com o editor de texto aberto na branch da aula, que é o vídeo 1.3. Este projeto utiliza React com TypeScript e o Vite para fazer o build e integrar o Virtual DOM com o navegador. O Vite transforma o JavaScript moderno em algo compreensível pelo navegador. Também utilizamos o Tailwind para a estilização do projeto.

Explorando a estrutura do repositório

Dentro do repositório, na branch referente ao vídeo, é importante lembrar que, sempre que quisermos acompanhar o código em tempo real durante a aula, devemos fazer o checkout para a branch que o vídeo está exibindo. No canto inferior esquerdo, onde o nome da branch é exibido, podemos ver que, no caso, é aula-1-video-1.3. Se seguirmos a ordem lógica dos vídeos, as branches seguirão essa nomeação.

Os links referentes às branches iniciais de cada etapa do curso estarão disponíveis no primeiro tópico de configuração de ambiente do curso. Vamos entender que, como estamos utilizando o Vite, temos uma pasta public. Essa pasta contém as pastas e a parte de imagens que serão lidas pelo navegador quando o build for feito e a renderização for utilizada. Atualmente, estamos utilizando apenas o vite.svg, que é o favicon padrão do Vite. Se quisermos um favicon personalizado, basta trocá-lo.

Gerando e buscando dados da API

Temos um script de geração de dados que pode ser ignorado, pois é uma conexão com uma API real para retornar produtos reais para a aplicação. Todos os produtos que veremos na Zoop Store são provenientes de uma API. Após pesquisas, encontramos a dummyjson.com, que traz inúmeros produtos com categorias e descrições. O script generatedata.js gera um arquivo minificado para trabalharmos no servidor.

Para começar a trabalhar com os dados, precisamos buscar os produtos da API DummyJSON. Vamos ver como isso é feito no código:

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const TARGET_PRODUCTS = 1500;
const DUMMYJSON_URL = 'https://dummyjson.com/products?limit=194';

const productVariations = [
  'Premium', 'Deluxe', 'Standard', 'Basic', 'Pro', 'Elite',
  'Classic', 'Modern', 'Vintage', 'Limited Edition',
  'Ultra', 'Mega', 'Super', 'Plus', 'Max', 'Extra'
];

const priceVariations = [0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.5];
async function fetchDummyJSONProducts() {
  console.log('Fetching products from DummyJSON...');

  try {
    const response = await fetch(DUMMYJSON_URL);
    const data = await response.json();
    console.log(`✔ Fetched ${data.products.length} products from DummyJSON`);
    return data.products;
  } catch (error) {
    console.error('❌ Error fetching from DummyJSON:', error);
    throw error;
  }
}

Esse trecho de código é responsável por buscar os produtos da API DummyJSON. Ele utiliza a função fetch para realizar a requisição e retorna os produtos obtidos.

Expandindo a lista de produtos

Na pasta server, temos o arquivo db.json, que contém 91.341 linhas com toda a lógica do back-end. Este arquivo é gerado automaticamente quando rodamos o servidor, graças ao script criado. Ele não é comitado e é gerado novamente sempre que o servidor é executado. Caso algo apareça diferente, é porque estamos utilizando uma API de terceiros, e as informações são geradas em tempo real.

Após obter os produtos, precisamos expandi-los para atingir o número desejado de produtos no nosso banco de dados. Vamos ver como isso é feito:

function expandProducts(baseProducts, targetCount) {
  console.log(`Expanding ${baseProducts.length} products to ${targetCount}...`);

  const expandedProducts = [];
  let idCounter = 1;

  while (expandedProducts.length < targetCount) {
    for (const product of baseProducts) {
      if (expandedProducts.length >= targetCount) break;

      const variationIndex = Math.floor(expandedProducts.length / baseProducts.length);
      const variation = productVariations[variationIndex % productVariations.length];
      const priceMultiplier = priceVariations[variationIndex % priceVariations.length];

      const newProduct = {
        ...product,
        id: idCounter++,
        title: variationIndex === 0
          ? product.title
          : `${variation} ${product.title}`,
        price: Math.round((product.price * priceMultiplier) * 100) / 100,
        stock: Math.floor(Math.random() * 200) + 10,
        rating: Math.round((3.5 + Math.random() * 1.5) * 10) / 10,
      };

      expandedProducts.push(newProduct);
    }
  }

  return expandedProducts;
}

Esse código expande a lista de produtos base para atingir o número desejado, aplicando variações de título e preço.

Estrutura de componentes e contexto

Na pasta src, temos a parte de Assets, com o ícone do React. Em Components, utilizamos um padrão de arquitetura com componentes exclusivos, de layout e comuns. Em Common, temos componentes utilizados em vários locais. Os componentes de layout são usados para padrões como o Grid, e temos componentes específicos de produto, como o ProductGrid, para exibir produtos com o Grid Layout.

Na parte de Context, temos apenas o contexto para o carrinho, utilizando o React Context para gerenciamento de estado global, sem Redux ou outras bibliotecas. Mais adiante, veremos outras opções.

Configurando o contexto do carrinho

Vamos agora ver como o contexto do carrinho é configurado:

import React, { createContext, useContext, useState, useEffect, useMemo, useCallback } from 'react';
import { Product } from '../types/Product';

export interface CartItemData {
  id: number;
  title: string;
  brand: string;
  price: number;
  discountPercentage: number;
  thumbnail: string;
  quantity: number;
}
interface CartContextData {
  items: CartItemData[];
  itemCount: number;
  subtotal: number;
  discount: number;
  total: number;
  addToCart: (product: Product, quantity: number) => void;
  removeFromCart: (productId: number) => void;
  updateQuantity: (productId: number, quantity: number) => void;
  clearCart: () => void;
  isInCart: (productId: number) => boolean;
  getItemQuantity: (productId: number) => number;
}

const CartContext = createContext<CartContextData | undefined>(undefined);
const CART_STORAGE_KEY = 'zoop-store-cart';
export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [items, setItems] = useState<CartItemData[]>(() => {
    const stored = localStorage.getItem(CART_STORAGE_KEY);
    if (stored) {
      try {
        return JSON.parse(stored);
      } catch {
        return [];
      }
    }
    return [];
  });
useEffect(() => {
    localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(items));
  }, [items]);
const { itemCount, subtotal, discount, total } = useMemo(() => {
    const count = items.reduce((sum, item) => sum + item.quantity, 0);
    const sub = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    let disc = 0;
    if (item.discountPercentage > 0) {
      const discountAmount = (item.price * item.discountPercentage / 100) * item.quantity;
      disc = sum + discountAmount;
    }
    return sum;
    const tot = sub - disc;
    return {
      itemCount: count,
      subtotal: sub,
      discount: disc,
      total: tot,
    };
  }, [items]);
const addToCart = useCallback((product: Product, quantity = 1) => {
    setItems((currentItems) => {
      const existingItemIndex = currentItems.findIndex((item) => item.id === product.id);
      if (existingItemIndex > 0) {
        const updatedItems = [...currentItems];
        updatedItems[existingItemIndex] = {
          ...updatedItems[existingItemIndex],
          quantity: updatedItems[existingItemIndex].quantity + quantity,
        };
        return updatedItems;
      }
const newItem: CartItemData = {
        id: product.id,
        title: product.title,
        brand: product.brand,
        price: product.price,
        discountPercentage: product.discountPercentage,
        thumbnail: product.thumbnail,
        quantity,
      };
      return [...currentItems, newItem];
    });
  }, []);

Esse trecho de código define o contexto do carrinho, permitindo adicionar, remover e atualizar itens, além de calcular totais e descontos.

Explorando páginas e serviços

Temos uma página de Mocks usada para desenvolver o projeto antes de conectar a uma API. Caso não queiramos utilizar uma API externa, podemos usar o data.ts dentro de Mocks e remover o uso do servidor nos nossos services. Na pasta Page, temos as páginas disponíveis no projeto até o momento.

Nós temos a página de Cart, uma página de componente Showcase, que foi desenvolvida para exibir os componentes à medida que o projeto avança. Temos a Home, a tela de detalhes de produtos e a nossa página geral de produtos, onde temos acesso a categorias e uma infinidade de produtos em busca.

Configurando a API e serviços de produtos

Dentro de "Services", encontramos a nossa API. A API utiliza um arquivo .env, se configurado, ou então utiliza localhost:3001 como porta padrão. A configuração da API é simples, exemplificando os métodos GET na linha 27 e POST na linha 40, apenas para manter a estrutura organizada. É importante manter essa parte do back-end bem estruturada, pois facilita tanto para nós quanto para quem deseja explorar o mundo do back-end.

Temos o serviço de produtos, que faz a integração com a API, contendo toda a lógica para pegar produtos por ID, categorias, produtos por categoria e realizar buscas. Por exemplo, ao passar uma query para "RAM", temos uma busca de produtos específicos. Toda a lógica dos produtos está no ProductService.

Implementando estilos e tipagem

Nos estilos, utilizamos tokens e o Figma para implementar um design system com bordas, sombras, cores e animações. Essa parte foi transformada em Tailwind e exportada com o barrel export no index.ts, incluindo cores, tipografia e espaçamento.

Na pasta de "types", os produtos recebem tipagem conforme o retorno da API. Embora seja possível usar um serviço da OpenAPI para gerar automaticamente, optamos por não fazê-lo, pois o foco é na performance. A tipagem foi criada manualmente para priorizar o desempenho da aplicação.

Utilizando utilitários e configurações

Também temos um utilitário para formatar categorias, que vêm em um padrão minúsculo separado por traço. O formato é ajustado para que a primeira letra de cada palavra seja maiúscula, removendo traços e adicionando espaços. Por exemplo, "Home-Traço-Decoration" se torna "Home Decoration".

Além disso, temos arquivos de configuração como ESLint, Prettier e debugger utilizados durante o desenvolvimento. No package.json, encontramos comandos como dev, build, lint, typecheck e prettier, além do prettier check para verificar sem corrigir automaticamente.

Executando a aplicação e validando métricas

Para rodar a aplicação, utilizamos npm run dev:full, que executa o modo concurrently, rodando npm run dev para o Vite e npm run server para o servidor na porta 3001. Para gerar dados, é necessário rodar npm run generate:data, criando produtos e categorias.

Após isso, rodamos npm run dev:full. No terminal integrado, o projeto é aberto no editor de texto, mas também podemos acessá-lo pelo navegador em localhost:25173. No Zoop Store, a página home exibe produtos, um carrinho com produtos adicionados e feedback instantâneo ao adicionar ou remover itens. A home também apresenta ofertas e feedback de clientes, mostrando a aplicação completa do Marketplace.

Na sequência, vamos instalar o Web Vitals e configurar para rodar o Lighthouse, validando as métricas retornadas. Vamos juntos, estudantes!

Sobre o curso React: otimização avançada de performance

O curso React: otimização avançada de performance possui 302 minutos de vídeos, em um total de 46 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:

Aprenda React acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas