División de código con importaciones dinámicas en Next.js

Cómo acelerar tu app de Next.js con la división de código y estrategias de carga inteligente

¿Qué aprenderás?

En esta publicación, se explican los diferentes tipos de división de código y cómo usar las importaciones dinámicas para acelerar tus apps de Next.js.

División de código basada en rutas y componentes

De forma predeterminada, Next.js divide tu código JavaScript en fragmentos separados para cada ruta. Cuando los usuarios cargan tu aplicación, Next.js solo envía el código necesario para la ruta inicial. Cuando los usuarios navegan por la aplicación, recuperan los fragmentos asociados con las otras rutas. La división de código basada en rutas minimiza la cantidad de secuencia de comandos que se debe analizar y compilar a la vez, lo que genera tiempos de carga de página más rápidos.

Si bien la división de código basada en rutas es una opción predeterminada adecuada, puedes optimizar aún más el proceso de carga con la división de código a nivel del componente. Si tienes componentes grandes en tu app, es una buena idea dividirlos en fragmentos separados. De esta manera, se puede cargar de forma diferida cualquier componente grande que no sea fundamental o que solo se renderice en ciertas interacciones del usuario (como hacer clic en un botón).

Next.js admite import() dinámico, que te permite importar módulos de JavaScript (incluidos los componentes de React) de forma dinámica y cargar cada importación como un fragmento independiente. Esto te brinda el fraccionamiento de código a nivel del componente y te permite controlar la carga de recursos para que los usuarios solo descarguen el código que necesitan para la parte del sitio que están viendo. En Next.js, estos componentes se renderizan del servidor (SSR) de forma predeterminada.

Importaciones dinámicas en acción

Esta publicación incluye varias versiones de una app de ejemplo que consta de una página simple con un botón. Cuando haces clic en el botón, ves un lindo cachorro. A medida que avanzas por cada versión de la app, verás cómo las importaciones dinámicas se diferencian de las importaciones estáticas y cómo trabajar con ellas.

En la primera versión de la app, el cachorro vive en components/Puppy.js. Para mostrar el cachorro en la página, la app importa el componente Puppy en index.js con una sentencia de importación estática:

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

Para ver cómo Next.js agrupa la app, inspecciona el registro de red en DevTools:

  1. Para obtener una vista previa del sitio, presiona Ver app. Luego, presiona Pantalla completa pantalla completa.

  2. Presiona "Control + Mayúsculas + J" (o "Comando + Opción + J" en Mac) para abrir DevTools.

  3. Haga clic en la pestaña Red.

  4. Selecciona la casilla de verificación Inhabilitar caché.

  5. Vuelve a cargar la página.

Cuando cargas la página, todo el código necesario, incluido el componente Puppy.js, se agrupa en index.js:

Pestaña Red de DevTools que muestra seis archivos JavaScript: index.js, app.js, webpack.js, main.js, 0.js y el archivo dll (biblioteca de vínculos dinámicos).

Cuando presionas el botón Click me, solo se agrega la solicitud del JPEG del cachorro a la pestaña Red:

Pestaña Red de DevTools después de hacer clic en el botón, que muestra los mismos seis archivos JavaScript y una imagen.

La desventaja de este enfoque es que, incluso si los usuarios no hacen clic en el botón para ver al cachorro, deben cargar el componente Puppy porque se incluye en index.js. En este pequeño ejemplo, no es un gran problema, pero en las aplicaciones del mundo real, a menudo es una gran mejora cargar componentes grandes solo cuando es necesario.

Ahora, observa una segunda versión de la app, en la que la importación estática se reemplaza por una importación dinámica. Next.js incluye next/dynamic, lo que hace posible usar importaciones dinámicas para cualquier componente en Next:

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

// ...

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

Sigue los pasos del primer ejemplo para inspeccionar el registro de red.

Cuando cargas la app por primera vez, solo se descarga index.js. Esta vez, es 0.5 KB más pequeño (pasó de 37.9 KB a 37.4 KB) porque no incluye el código del componente Puppy:

Red de DevTools que muestra los mismos seis archivos JavaScript, excepto que index.js ahora es 0.5 KB más pequeño.

El componente Puppy ahora está en un fragmento independiente, 1.js, que se carga solo cuando presionas el botón:

Pestaña Red de DevTools después de hacer clic en el botón, que muestra el archivo 1.js adicional y la imagen agregada a la parte inferior de la lista de archivos.

En las aplicaciones del mundo real, los componentes suelen ser mucho más grandes, y la carga diferida puede reducir tu carga útil inicial de JavaScript en cientos de kilobytes.

Importaciones dinámicas con indicador de carga personalizado

Cuando cargas recursos de forma diferida, es recomendable proporcionar un indicador de carga en caso de que haya demoras. En Next.js, puedes hacerlo proporcionando un argumento adicional a la función dynamic():

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

Para ver el indicador de carga en acción, simula una conexión de red lenta en DevTools:

  1. Para obtener una vista previa del sitio, presiona Ver app. Luego, presiona Pantalla completa pantalla completa.

  2. Presiona "Control + Mayúsculas + J" (o "Comando + Opción + J" en Mac) para abrir DevTools.

  3. Haga clic en la pestaña Red.

  4. Selecciona la casilla de verificación Inhabilitar caché.

  5. En la lista desplegable Restricción, selecciona 3G rápido.

  6. Presiona el botón Haz clic en mí.

Ahora, cuando haces clic en el botón, el componente tarda un poco en cargarse y, mientras tanto, la app muestra el mensaje "Cargando…".

Una pantalla oscura con el texto

Importaciones dinámicas sin SSR

Si necesitas renderizar un componente solo en el lado del cliente (por ejemplo, un widget de chat), puedes configurar la opción ssr en false:

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

Conclusión

Con la compatibilidad con importaciones dinámicas, Next.js te brinda la división de código a nivel de componentes, que puede minimizar tus cargas útiles de JavaScript y mejorar el tiempo de carga de la aplicación. Todos los componentes se renderizan del servidor de forma predeterminada, y puedes inhabilitar esta opción cuando sea necesario.