Blog.

Code Spliting com NextJS

Cover Image for Code Spliting com NextJS
Alexandro castro
Alexandro castro

O Problema em desenvolvimento

O Problema 1

Em desenvolvimento, quando se importa um componente de um index (por exemplo no diretório components/index2.ts) com outras exportações, o webpack gera o bundle com todos os componentes exportados no index.

No nosso screens/Home_1.tsx, temos um exemplo, onde só está sendo usado o componente Modal e em development, o bundle gerado é o seguinte:

O Dialog pesa 1mb, e o Modal uns 3kb, mas o bundle gerado tem 1.03mb, pois o webpack gera o bundle com todos os componentes exportados no index.

Imagina uma página com um Design System inteiro importado, vai ficar super pesado e lento.

O Problema 2

Se você fizer o uso do Dialog, ao invés do Modal, no NextJS, o browser vai tentar renderizar os 1mb do Dialog via Server Side Render, e na hidratação, irá carregar de novo os 1mb do Dialog.

O Problema em produção

Introdução

Em produção, quando se importa um componente de um index com outras exportações, o webpack gera o bundle apenas com o componente importado.

Porém o Problema 2 do ambiente de desenvolvimento, continua em produção.

A Solução do Problema 1

Este problema vai te atrapalhar mais em desenvolvimento, pois em produção, o webpack gera o bundle apenas com o componente importado.

Então, se você importar apenas o componente que você vai usar, o bundle gerado será apenas com o componente que você importou.

por exemplo, ao invés de importar:

import { Modal } from '@/components'

Seria melhor importar:

import { Modal } from '@/components/Modal'

Porém, se você está criando package local, ou alguma pasta global, onde você não quer ficar importando todos os componentes, você pode usar o import dynamic do NextJS, citado na solução do Problema 2.

ou você pode utilizar o React Lazy, que também vai resolver o problema.

O Resultado vai ser esse:

Repara também que o App foi carregado mais rápido e depois foi feito o carregamento do Dialog, que é o componente que foi importado.

A Solução do Problema 2

Existe uma solução bem simples que resolve o problema 2, que é o uso do dynamic import do NextJS.

O dynamic import, faz com que o componente seja carregado apenas no client side, e não no server side.

Isso então vai evitar que o Dialog seja renderizado no server side, e na hidratação, o Dialog não será carregado de novo.

você pode encontrar a implementação no arquivo screens/Home_1.tsx

O Resultado vai ser esse:

Fallback

O fallback é uma propriedade do dynamic import, que pode ser usada para renderizar um componente de loading enquanto o componente é carregado.

Já que o dynamic não usa mais o você pode usar a propriedade loading, e renderizar via server side render, um componente de loading ou um skeleton.

Exemplo:

import dynamic from 'next/dynamic'

const Modal = dynamic(() => import('@/components/Dialog'), {
  loading: () => <div>Carregando...</div>
})

Disclaimer

O then usado no dynamic em screens/Home_1.tsx é apenas por que:

1 - Todos imports de arquivos são assincronos 2 - O @/components exporta componentes individuais, ao invés de export default { ... }

Como está sendo usado:

const Dialog = dynamic(() => import('@/components').then((components) => components.Dialog()), {
  ssr: false,
  loading: () => <div>loading...</div>
})

Se por acaso você usar com export default { ... }

export default {
    Modal: () => import('./Modal'),
    Dialog: () => import('./Dialog'),
}

Exemplo em components/index3.ts

Você pode usar o dynamic assim:

const Dialog = dynamic(() => import('@/components').then((components) => components.default.Dialog()), {
  ssr: false,
  loading: () => <div>loading...</div>
})

Observação: Mudou de 1mb para 500kb por que? Após passar o minified code, o Dialog ficou com 500kb.


Por que? Todo export default é retornado como default, então você precisa acessar o default para acessar o componente.

Se você fizer um console, vai ver algo assim:

{
    __esModule: true,
    default: {
        Modal: () => import('./Modal'),
        Dialog: () => import('./Dialog'),
    }
}

e caso você manter os export const, você terá:

{
    __esModule: true,
    default: {
        Modal: () => import('./Modal'),
        Dialog: () => import('./Dialog'),
    },
    Modal: () => import('./Modal'),
    Dialog: () => import('./Dialog'),
}

Conclusão

O problema 1, pode ser resolvido apenas importando o componente que você vai usar, ou usando o dynamic import.

O problema 2, pode ser resolvido apenas usando o dynamic import.

Sugiro também que veja uma solução melhor que vá ter menos problemas, que é o uso do babel-plugin-transform-imports, que vai fazer o uso do dynamic import automaticamente.

https://www.npmjs.com/package/babel-plugin-transform-imports

Referências

https://nextjs.org/docs/advanced-features/dynamic-import

https://www.freecodecamp.org/news/code-splitting-in-react-loadable-components/

https://webpack.js.org/guides/code-splitting/

https://developer.mozilla.org/en-US/docs/Glossary/Code_splitting

https://nextjs.org/learn/foundations/how-nextjs-works/code-splitting

https://nextjs.org/learn/foundations/how-nextjs-works/client-and-server