Saltar al contenido
Framework Ecosystems LabsUn contrato, muchos ecosistemas, la misma prueba.

Clase 095 — Islas#

⬅️ Clase 094 · 📚 Parte 7 · 🎓 Clases · 096 ➡️ Parte 7 — Renderizado y full-stack · Nivel 🔴 avanzado · Pista fullstack (Renderizado y full-stack) ✅ Clase construida — 3 implementaciones verificadas contra contrato.json.

🎯 Objetivo#

La clase 094 midió lo que cuesta hidratar una pantalla. Esta contesta la pregunta obvia que sigue: ¿y si solo hidratamos el trozo que lo necesita?

Eso es la arquitectura de islas. Y la clase la mide con dos páginas en lugar de una, porque el número que importa no es lo que pesa una página con islas: es cuánto sube al añadirlas.

📚 Resultados de aprendizaje#

Al terminar podrás:

🧩 La situación#

Una página de artículo. Cuatro párrafos de texto que no reaccionan a nada, un botón de «me gusta» y un filtro sobre una lista de tres tareas.

Es a propósito la proporción de una página real: casi todo muerto, dos zonas vivas. Y cada implementación sirve además la misma página sin las dos zonas vivas, en /sin-islas, para poder restar.

🧮 El contrato#

# Petición Qué comprueba
1 GET / el artículo entero llega renderizado por el servidor
2 GET / y las dos islas también, con data-cuenta="0" y la lista pintada
3 GET / con el <script que las va a revivir
4 GET /sin-islas el mismo artículo, sin ningún data-isla=
5 GET /islas.json las dos islas declaradas y las_islas_cuestan: true
6 GET /islas.json la medición es real: bytes y diferencia distintos de cero

El caso 2 es el que separa las islas de la carga en el cliente: las islas se renderizan en el servidor igual que todo lo demás. Que el botón diga 0 en el HTML significa que no hay hueco vacío esperando a que llegue JavaScript.

Y el caso 5 dice algo que las presentaciones de islas suelen callar:

        "json_contiene": {
          "islas": ["contador", "filtro"],
          "cuantas_islas": 2,
          "las_islas_cuestan": true
        }

Las islas no son gratis. Son más baratas que hidratarlo todo, que es otra cosa.

<!-- generado: fichas -->

📖 Las palabras que esta clase define#

Si alguna de estas no te dice nada todavía, esta es la clase donde se aprende. Las definiciones viven en el glosario, que reúne las del programa entero.

Palabra Qué significa
Isla Un trozo interactivo dentro de una página que por lo demás es HTML estático. Solo se hidrata la isla, así que el JavaScript que llega es proporcional a lo que de verdad se mueve.

🧰 Las piezas de esta clase, una por una#

Antes del código: qué es cada framework, qué versión se está usando y qué hace falta para ejecutarlo. Todo lo de esta sección sale de los archivos reales del repositorio —el catálogo, la receta de arranque y el manifiesto de dependencias de cada ecosistema—, así que no puede quedarse desactualizado sin que la validación lo detecte.

Framework Qué es Desde Licencia Quién lo mantiene
Astro web-metaframework de JavaScript/TypeScript (TypeScript) 2021 MIT proyecto independiente
Next.js react-metaframework de JavaScript/TypeScript (TypeScript) 2016 MIT Vercel
Nuxt vue-metaframework de JavaScript/TypeScript (TypeScript) 2016 MIT proyecto independiente

🔧 Astro#

Arquitectura de islas: por omisión no envía JavaScript y cada componente interactivo se declara explícitamente. Permite mezclar React, Vue y Svelte en la misma página, lo que lo hace un banco de pruebas ideal para comparar.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec astro build

Arrancarla suelta, sin el verificador:

PORT=3000 node ./dist/server/entry.mjs

Qué hay dentro de su directorio:

Archivo Qué es
astro.config.mjs código JavaScript (módulo ES)
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
src/components/Articulo.astro archivo del proyecto
src/components/Contador.jsx componente en JSX
src/components/Filtro.jsx componente en JSX

🔧 Next.js#

Convirtió el renderizado en servidor en la opción por omisión del ecosistema React. Su acoplamiento con una plataforma concreta es la dimensión que el módulo 11 obliga a puntuar.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec next build

Arrancarla suelta, sin el verificador:

PORT=3000 pnpm exec next start -p 3000

Qué hay dentro de su directorio:

Archivo Qué es
app/Articulo.jsx componente en JSX
app/Contador.jsx componente en JSX
app/Filtro.jsx componente en JSX
app/datos.js código JavaScript
app/islas.json/route.js código JavaScript
app/layout.js código JavaScript
app/medicion.js código JavaScript
app/page.js código JavaScript

🔧 Nuxt#

El equivalente de Next.js sobre Vue, con un motor de servidor propio reutilizable fuera del framework.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec nuxt build

Arrancarla suelta, sin el verificador:

PORT=3000 node .output/server/index.mjs

Qué hay dentro de su directorio:

Archivo Qué es
components/Articulo.server.vue archivo del proyecto
components/Contador.vue archivo del proyecto
components/Filtro.vue archivo del proyecto
datos.ts código TypeScript
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
medicion.js código JavaScript
nuxt.config.ts código TypeScript
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión

Si alguna cadena de herramientas no está en tu máquina, node scripts/doctor.mjs dice cuál falta y con qué comando se instala. No hace falta tenerlas todas: el verificador ejecuta lo que encuentra y declara lo que omitió.

<!-- fin generado: fichas -->

🌐 Las implementaciones — el código a la vista#

El medidor, idéntico en las tres#

astro/src/medicion.js — y la razón de que haga falta pedir dos páginas:

 * Para saberlo hacen falta dos páginas y no una. `/` lleva el artículo y las dos
 * islas; `/sin-islas` lleva el mismo artículo y nada más. La diferencia entre las
 * dos es el precio de las islas, y ese número no se puede sacar mirando una sola
 * página.

La medición que más dice de las tres:

/** Cuántas veces aparece una aguja dentro de los guiones en línea, no en el
 *  marcado. Es la diferencia entre «este texto se ve» y «este texto viaja». */
export function vecesDentroDeGuiones(html, aguja) {

Y la resta:

  return {
    bytes_del_html: bytesConIslas,
    bytes_sin_islas: bytesSinIslas,
    las_islas_cuestan: bytesConIslas > bytesSinIslas,
    lo_que_cuestan_dos_islas: bytesConIslas - bytesSinIslas,

El contenido también es el mismo en las tres — astro/src/datos.js:

 * Esa proporción es el argumento entero de la arquitectura de islas: en una
 * página normal, la mayor parte del marcado no necesita JavaScript, y hidratarla
 * toda es pagar por algo que nadie va a usar.
 */
export const ARTICULO = [

Astro · el documento no es una aplicación#

astro/src/pages/index.astro — dos directivas, dos islas:

    <Articulo />
    <Contador client:load />
    <Filtro client:load tareas={TAREAS} />

Y una escala de compromisos que los otros dos no tienen:

// `client:load` revive la isla en cuanto carga la página. Hay más: `client:idle`
// espera a que el navegador esté ocioso, `client:visible` espera a que la isla
// entre en pantalla, `client:media` la condiciona a una consulta de medios. Esa
// escala de compromisos no existe en los otros dos frameworks de la clase: allí
// la aplicación arranca entera y no hay grados.

Por qué su página sin islas llega con cero <script>Articulo.astro:

// Este componente no tiene forma de llegar al navegador: no hay directiva que
// se le pueda poner. Las plantillas `.astro` se ejecutan en el servidor y
// producen texto; no existe una versión suya en JavaScript de cliente.

Y la regla que hay que llevarse, en la segunda isla — Filtro.jsx:

 * Recibe las tareas como propiedad, así que **las tareas sí viajan**: van
 * serializadas en el atributo `props` de su `<astro-island>`. El artículo no,
 * porque no es propiedad de ninguna isla.

Y la configuración, que no enciende nada — astro.config.mjs:

 * Es una diferencia de fondo con los otros dos de esta clase: en Next y en Nuxt
 * hay una aplicación que se arranca y las islas son partes de ella; aquí no hay
 * aplicación, hay un documento con trozos vivos.

Next.js · fronteras de cliente, que no es lo mismo#

nextjs/app/Articulo.jsx — el matiz que la medición hace visible:

 * La diferencia está en lo que sí viaja: **su resultado**. Next manda la
 * descripción del árbol renderizado —la carga RSC— dentro del documento, y ahí
 * está el texto del artículo otra vez. `/islas.json` lo cuenta, y en Astro ese
 * mismo número sale cero.

Y una consecuencia práctica que se paga al leer código ajeno — app/page.js:

 * Fíjate en que aquí no hay ninguna directiva: la frontera está dentro de cada
 * componente, no en el sitio donde se usa. Es la decisión de diseño opuesta a la
 * de Astro, y tiene una consecuencia práctica: para saber si algo va al
 * navegador hay que abrir el archivo del componente, no la página.

Y por qué su suelo no baja de seis mil bytes — app/sin-islas/page.js:

 * Aun así llega con `<script>`: Next arranca su tiempo de ejecución en toda
 * página del App Router para poder navegar sin recargar. La diferencia con `/`
 * es real y se mide, pero no baja a cero como en Astro.

Nuxt · la misma idea, del revés#

nuxt/nuxt.config.ts — y es la explicación más útil de la clase:

 * En Astro el documento es estático y las islas son los trozos vivos. En Nuxt la
 * aplicación está viva entera y una isla es un componente que se queda en el
 * servidor: un archivo `.server.vue`, cuyo HTML llega renderizado y cuyo código
 * no viaja.
 * `componentIslands` sigue bajo `experimental`, y eso también es un dato de la
 * comparación: en Astro esto no es una opción, es el producto.
 */
export default defineNuxtConfig({
  compatibilityDate: "2025-01-01",
  experimental: { componentIslands: true },
});

Con la declaración en el nombre del archivo — components/Articulo.server.vue:

// El sufijo `.server` es toda la declaración. Este componente se renderiza en el
// servidor y su código no se incluye en el paquete del navegador.

Y el resumen de los tres, escrito en la página — pages/index.vue:

// Aquí no hay directiva ninguna: la decisión está en el nombre del archivo del
// componente. Tres frameworks, tres sitios distintos donde mirar para saber qué
// llega al navegador: la etiqueta (Astro), la primera línea del componente
// (Next) y el nombre del archivo (Nuxt).

🔬 Comparación#

Medido por el mismo archivo, en la misma máquina, en la misma ejecución:

/ con islas /sin-islas lo que cuestan las 2 islas guiones externos (con / sin) en línea (con / sin) el artículo dentro de un guion
Astro 5769 B 1164 B +4605 B 0 / 0 3592 / 0 0
Next.js 6727 B 6599 B +128 B 6 / 5 4536 / 4725 1
Nuxt 2741 B 2514 B +227 B 1 / 1 553 / 562 0

Y los tres mecanismos, que producen efectos parecidos y no son la misma cosa:

Qué es una isla aquí Dónde se declara ¿Hay grados?
Astro un componente de otro framework incrustado en un documento en la etiqueta, al usarlo ✅ cuatro
Next.js una frontera dentro del árbol de componentes de servidor en la primera línea del componente
Nuxt al revés: un componente que no viaja, con sufijo .server en el nombre del archivo

Cinco lecturas, y ninguna es «Astro gana»:

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 095

Para verlo tú, con cualquiera arrancada, la comparación de una línea:

curl -s http://127.0.0.1:4100/sin-islas | grep -c "<script"

En Astro sale 0. En los otros dos, no.

🧪 Reto de transferencia#

  1. Calcula tu proporción. En tu página más visitada, mide qué porcentaje del marcado necesita responder a algo. Si es menos de un tercio, este modelo tiene algo que ofrecerte.
  2. Encuentra tus islas. Escribe la lista de zonas que de verdad necesitan JavaScript. Suele salir más corta de lo esperado: cabecera, buscador, y poco más.
  3. Mide el suelo de tu framework. Sirve una página sin nada interactivo y pesa lo que llega. Ese número es el precio de entrada, y no baja por muy bien que escribas el resto.

🔗 Enlaces#

Fuentes#