Divisão de código com importações dinâmicas no Next.js

Como acelerar seu app Next.js com divisão de código e estratégias de carregamento inteligente.

O que você aprenderá?

Nesta postagem, explicamos diferentes tipos de divisão de código e como usar importações dinâmicas para acelerar os apps Next.js.

Divisão de código com base em rotas e em componentes

Por padrão, o Next.js divide seu JavaScript em partes separadas para cada rota. Quando os usuários carregam seu aplicativo, o Next.js envia apenas o código necessário para a rota inicial. Quando os usuários navegam pelo aplicativo, eles buscam os blocos associados às outras rotas. A divisão de código com base em rotas minimiza a quantidade de scripts que precisam ser analisados e compilados de uma vez, o que resulta em tempos de carregamento de página mais rápidos.

Embora a divisão de código com base em rota seja um bom padrão, é possível otimizar ainda mais o processo de carregamento com a divisão de código no nível do componente. Se você tem componentes grandes no app, é recomendável dividi-los em partes separadas. Dessa forma, qualquer componente grande que não seja crítico ou que só seja renderizado em determinadas interações do usuário (como clicar em um botão) poderá ser carregado lentamente.

O Next.js é compatível com o import() dinâmico, que permite importar módulos JavaScript (incluindo componentes do React) dinamicamente e carregar cada importação como um bloco separado. Isso proporciona divisão de código no nível do componente e permite controlar o carregamento de recursos para que os usuários façam o download apenas do código de que precisam para a parte do site que estão visualizando. No Next.js, esses componentes são renderizados pelo servidor (SSR, na sigla em inglês) por padrão.

Importações dinâmicas em ação

Esta postagem inclui várias versões de um app de exemplo que consiste em uma página simples com um botão. Ao clicar no botão, você vê um cachorrinho fofo. Ao avançar cada versão do app, você verá como as importações dinâmicas são diferentes das importações estáticas e como trabalhar com elas.

Na primeira versão do app, o filhote mora em components/Puppy.js. Para exibir o filhote na página, o app importa o componente Puppy em index.js com uma instrução de importação estática:

import Puppy from "../components/Puppy";

Para conferir como o Next.js agrupa o app, inspecione o rastreamento de rede no DevTools:

  1. Para visualizar o site, pressione Ver app. Em seguida, pressione Tela cheia modo tela cheia.

  2. Pressione "Control + Shift + J" (ou "Command + Option + J" no Mac) para abrir o DevTools.

  3. Clique na guia Rede.

  4. Marque a caixa de seleção Desativar cache.

  5. Recarregue a página.

Quando você carrega a página, todo o código necessário, incluindo o componente Puppy.js, é empacotado em index.js:

Guia "Rede" do DevTools mostrando seis arquivos JavaScript: index.js, app.js, webpack.js, main.js, 0.js e o arquivo dll (biblioteca de link dinâmico).

Quando você pressionar o botão Clique em mim, apenas a solicitação de JPEG do filhote será adicionada à guia Rede:

Guia "Network" do DevTools após o clique do botão, mostrando os mesmos seis arquivos JavaScript e uma imagem.

A desvantagem dessa abordagem é que, mesmo que os usuários não cliquem no botão para ver o filhote, eles precisarão carregar o componente Puppy, porque ele está incluído em index.js. Neste pequeno exemplo, isso não é grande coisa, mas em aplicativos do mundo real, é uma grande melhoria carregar componentes grandes somente quando necessário.

Agora, confira uma segunda versão do aplicativo, em que a importação estática é substituída por uma importação dinâmica. O Next.js inclui next/dynamic, o que possibilita o uso de importações dinâmicas para qualquer componente no Next:

import Puppy from "../components/Puppy";
import dynamic from "next/dynamic";

// ...

const Puppy = dynamic(import("../components/Puppy"));

Siga as etapas do primeiro exemplo para inspecionar o rastro da rede.

Quando você carregar o app pela primeira vez, apenas o index.js será transferido por download. Desta vez, ele é 0,5 KB menor (caiu de 37,9 KB para 37,4 KB) porque não inclui o código do componente Puppy:

Rede do DevTools mostrando os mesmos seis arquivos JavaScript, mas o index.js agora tem 0,5 KB menor.

O componente Puppy agora está em um bloco separado, 1.js, que é carregado somente quando você pressiona o botão:

Guia "Rede" do DevTools após o clique no botão, mostrando o arquivo 1.js adicional e a imagem adicionada ao fim da lista de arquivos.

Em aplicativos reais, os componentes geralmente são muito maiores, e o carregamento lento pode reduzir o payload JavaScript inicial em centenas de kilobytes.

Importações dinâmicas com indicador de carregamento personalizado

Quando você faz o carregamento lento de recursos, é recomendável fornecer um indicador de carregamento em caso de atrasos. No Next.js, você pode fazer isso fornecendo um argumento adicional para a função dynamic():

const Puppy = dynamic(() => import("../components/Puppy"), {
  loading: () => <p>Loading...</p>
});

Para conferir o indicador de carregamento em ação, simule uma conexão de rede lenta no DevTools:

  1. Para visualizar o site, pressione Ver app. Em seguida, pressione Tela cheia modo tela cheia.

  2. Pressione "Control + Shift + J" (ou "Command + Option + J" no Mac) para abrir o DevTools.

  3. Clique na guia Rede.

  4. Marque a caixa de seleção Desativar cache.

  5. Na lista suspensa Limitação, selecione 3G rápido.

  6. Pressione o botão Clique aqui.

Agora, quando você clicar no botão, levará um tempo para carregar o componente, e o app exibirá a mensagem "Carregando..." enquanto isso.

Uma tela escura com o texto

Importações dinâmicas sem SSR

Se você precisar renderizar um componente somente no lado do cliente (por exemplo, um widget de chat), defina a opção ssr como false:

const Puppy = dynamic(() => import("../components/Puppy"), {
  ssr: false,
});

Conclusão

Compatível com importações dinâmicas, o Next.js oferece a divisão de código no nível do componente, o que pode minimizar os payloads do JavaScript e melhorar o tempo de carregamento do aplicativo. Todos os componentes são renderizados do lado do servidor por padrão, e essa opção pode ser desativada sempre que necessário.