§ NOTA TÉCNICA · ARQUITECTURA

Astro 5 + islas React para tools cliente puro

tools.ago.cl es una suite de 15 herramientas web que se ejecutan en el navegador del usuario, sin servidor. Para esa arquitectura evaluamos Next.js, Remix, SvelteKit y Astro 5. Elegimos Astro con islas React. Esta nota documenta el razonamiento, la configuración real, un ejemplo concreto de página con isla hidratada y las limitaciones honestas que aparecieron en producción.

El contexto: qué problema resolvimos

Cada tool de aGo lab tiene tres partes muy distintas en términos de carga y consumo. Primero, el contenido estático para SEO y citación por modelos: descripción de la tool, FAQ, JSON-LD, link cruzado a ago.cl/servicios. Segundo, el editor real: un componente React con sliders, canvas, drag and drop, lectura de archivos. Tercero, el routing y el shell común: header, footer, navegación, theme toggle.

En un framework React all-in tradicional, las tres partes pagan el mismo costo de bundle y de hidratación. La descripción de la tool, que es HTML puro sin interactividad, viaja en el cliente como JSX y se monta como árbol React aunque nadie la vaya a tocar. Eso infla el tiempo a interactivo (TTI) y empeora Lighthouse en mobile.

Queríamos algo donde el HTML estático sea HTML estático y el editor sea React solo donde aporta. Astro 5 ofrece exactamente eso con sus islas. La pregunta era si era la elección correcta para nuestro caso o si mejor pagar el costo a cambio de un framework con más ecosistema.

Opciones evaluadas

Opción A · Next.js 15 con app router

El estándar React 2026. Server components, streaming, optimizaciones.

Pros: ecosistema enorme, equipo React lo conoce, soporte Vercel directo.

Contras: overhead React por defecto, hosting típicamente Vercel o configuración compleja para estático puro, server components reñidos con cliente puro real.

Opción B · Remix / React Router v7

Foco en mutaciones, nested routing, progressive enhancement.

Pros: filosofía web fundamentals sólida, buena DX.

Contras: diseñado para apps con servidor; para estático puro se siente forzado. Algunas APIs asumen loaders server-side que no aplicarían.

Opción C · SvelteKit estático

Svelte 5 más SvelteKit con adapter-static.

Pros: bundles más chicos, sintaxis cómoda, performance superior en algunas métricas.

Contras: el equipo es React-first. Cambiar mental model para 15 tools tiene costo, y muchas libs específicas (PDF, color) tienen el componente React más maduro.

Opción D · Astro 5 + islas React

HTML estático por defecto, React solo en componentes marcados.

Pros: cero JS donde no se necesita, multi-framework si se quiere, deploy estático a Cloudflare Pages sin SSR.

Contras: ecosistema más chico, debugging cross-isla más complejo, state global compartido requiere capa propia.

La decisión: Astro 5 con React por isla

Elegimos la opción D. Tres razones principales.

Razón uno: el contenido estático es la mayoría. En una suite donde cada tool tiene 30-40% del peso en copy SEO y FAQ, enviar React all-in es desperdicio. Astro permite que ese contenido viaje como HTML puro y solo el editor pague el costo de React.

Razón dos: deploy estático en Cloudflare Pages. No tenemos servidor. Cloudflare Pages sirve el build de dist/ con CDN global y SSL automático. Astro genera dist/ exactamente con esa estructura sin trucos. Costo de hosting: cero dentro del free tier.

Razón tres: React donde aporta, sin compromiso global. Cada editor React vive en src/islands/<tool>/ y se carga vía client:visible o client:load según el caso. El bundle global no incluye React; solo lo incluyen las páginas que tienen isla.

Implementación: configuración real

Este es el astro.config.mjs real de tools.ago.cl, simplificado a lo esencial. Define el adapter para Cloudflare Pages, registra React como integración, configura el sitio para canonicals y sitemap, y ajusta el build estático.

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://tools.ago.cl',

  // Build estático puro. Sin SSR, sin endpoints server.
  output: 'static',

  // Integraciones: solo React (no Vue, no Svelte) más sitemap automático.
  integrations: [
    react({
      // Importante: include solo los paths donde viven islas. Evita transform
      // accidental de archivos React utilitarios que no se hidratan.
      include: ['**/islands/**', '**/components/react/**']
    }),
    sitemap({
      filter: (page) => !page.includes('/draft/') && !page.includes('/sesion/')
    })
  ],

  // i18n nativo Astro: español default sin prefijo, inglés en /en, portugués en /pt.
  i18n: {
    defaultLocale: 'es',
    locales: ['es', 'en', 'pt'],
    routing: {
      prefixDefaultLocale: false
    }
  },

  // Vite config: optimizar bundle de tools que importan libs pesadas (jsPDF, pdf-lib, etc.).
  vite: {
    build: {
      // Dividir vendor de cada tool en su propio chunk para mejor caching CDN.
      rollupOptions: {
        output: {
          manualChunks(id) {
            if (id.includes('pdf-lib')) return 'pdf-lib';
            if (id.includes('jspdf')) return 'jspdf';
            if (id.includes('react-dom')) return 'react-dom';
            return undefined;
          }
        }
      }
    }
  }
});

Tres detalles que importan en la práctica.

Uno. include en la integración React es crítico. Sin él, Astro intenta transformar todo archivo .jsx o .tsx del proyecto, incluso utilitarios que no son componentes hidratables. Limitar a islands/ y components/react/ evita errores de build sutiles.

Dos. output: 'static' garantiza que Astro nunca emita endpoints SSR. Si por error escribes un archivo en src/pages/api/ con export GET, el build falla con error claro y no acaba en producción.

Tres. Los chunks manuales de Vite son la diferencia entre 2 MB de bundle gigante por tool y 200 KB con caching efectivo en CDN. Sin manualChunks, pdf-lib termina duplicado en cada tool que lo usa.

Ejemplo concreto: página de tool con isla

Esta es la estructura simplificada de una página de tool. El HTML estático con copy SEO y FAQ vive en el .astro. El editor React vive aparte y se hidrata solo cuando entra al viewport.

---
// src/pages/squeezer.astro
import Base from '@/layouts/Base.astro';
import SqueezerEditor from '@/islands/squeezer/SqueezerEditor.tsx';

const title = 'aGo Squeezer · Comprime imágenes en el navegador';
const description = 'Comprime fotos a WebP, AVIF, JPEG y PNG en batch sin subir nada a un servidor.';

const softwareLd = {
  '@context': 'https://schema.org',
  '@type': 'SoftwareApplication',
  name: 'aGo Squeezer',
  applicationCategory: 'DesignApplication',
  operatingSystem: 'Web Browser',
  offers: { '@type': 'Offer', price: '0', priceCurrency: 'CLP' }
};
---

<Base title={title} description={description} jsonLd={softwareLd}>
  <header class="tool-hero">
    <p class="eyebrow">§ COMPRESIÓN DE IMÁGENES</p>
    <h1>aGo Squeezer</h1>
    <p class="lead">{description}</p>
  </header>

  <!-- La isla React. client:visible la hidrata solo cuando entra en viewport. -->
  <SqueezerEditor client:visible />

  <section class="contexto">
    <h2>Cómo funciona</h2>
    <p>Todo el proceso ocurre en tu navegador. Las imágenes no se suben a ningún servidor.</p>
  </section>

  <section class="faq">
    <h2>Preguntas frecuentes</h2>
    <!-- HTML estático del FAQ, sin JavaScript. -->
  </section>
</Base>

Lo importante de esta estructura es que el HTML estático rinde inmediatamente. Google ve el título, la descripción, el JSON-LD y el FAQ en el primer fetch. El usuario ve el contenido sin esperar a JavaScript. Solo cuando hace scroll y el componente <SqueezerEditor> entra en viewport, Astro descarga el bundle React y lo hidrata. Mientras tanto el placeholder server-rendered del editor está visible.

Cuándo usar cada directiva client

Directiva Cuándo Ejemplo en tools.ago.cl
client:load El componente es crítico inmediato, above the fold. Hero interactivo de la landing del hub.
client:visible El componente vive debajo del fold y solo importa si el usuario llega ahí. Editor de aGo Squeezer, aGo Palette, aGo Canvas.
client:idle Funcionalidad secundaria que puede esperar al idle del navegador. Theme toggle del header, command palette.
client:media Solo se hidrata si una media query coincide. Sidebar exclusivo de desktop, dock móvil.
client:only="react" El componente no puede renderizarse en server (usa window, document directo). Editor con canvas WebGL que no soporta SSR.

Limitaciones honestas que descubrimos

1. State global cross-isla requiere capa propia

Cada isla es su propio árbol React. Si necesitas que dos islas en la misma página compartan estado, React Context entre ellas no funciona porque viven en árboles separados. Las opciones son nanostores (recomendada por Astro), Zustand con persist, o un canal explícito vía localStorage / IndexedDB. En tools.ago.cl elegimos lo último para cross-pollination entre tools (no entre islas de la misma página) y nanostores para estado dentro del shell.

2. Debugging cross-isla es más complejo

React DevTools muestra cada isla como un árbol separado. No hay "ver todo el árbol de la página" porque la página no es un árbol React, sino HTML con múltiples árboles embebidos. Esto confunde al inicio. La pista práctica: en DevTools abre "Components" y verás múltiples roots, uno por isla.

3. SSR de componentes React tiene limitaciones

Astro server-renderiza el componente React en build para que el placeholder sea HTML real y no un div vacío. Eso significa que el componente debe ser SSR-safe. Acceder a window o document en el primer render rompe el build. Hay que envolver en useEffect o usar client:only para componentes que no toleran SSR.

4. HMR a veces se siente raro

El hot reload de Astro funciona, pero los cambios en una isla React a veces requieren refresh completo si tocas la firma del export. No es un dealbreaker; es un costo comparado con la HMR perfecta de Vite vanilla.

5. Ecosistema más chico que Next.js

Libs como next-auth, next-mdx-remote, etc., no existen en Astro. Hay equivalentes (auth-astro, mdx integration nativa) pero la madurez varía. Si tu producto depende de una integración Next-específica, evalúa antes.

6. View transitions son recientes

Astro soporta view transitions y client-side routing opt-in. Funcionan bien pero hay edge cases con scripts globales que se ejecutan dos veces. En tools.ago.cl no las habilitamos por defecto.

Cuándo NO usar Astro con islas

  • App con estado global compartido entre páginas. Si tu producto es un dashboard con state que vive durante toda la sesión y se mueve cross-página, un framework con SPA routing (Next.js + Zustand, Remix con loaders) calza mejor. Astro asume navegación full-page por defecto.
  • Real-time pesado. Para apps con websockets, server-sent events o colaboración en vivo, conviene un framework con SSR y backend integrado. Astro estático no es la herramienta.
  • Equipo sin tolerancia para arquitectura por isla. Astro pide pensar dos veces dónde poner JavaScript. Esa disciplina rinde, pero si el equipo prefiere escribir todo en React y pagar el costo, Next.js es más cómodo.
  • Auth complejo con sesiones server-side. Mientras existan integraciones, el ecosistema sigue rezagado vs Next.js. Para auth con cookies server, callbacks OAuth múltiples y SSR autenticado, otro framework rinde más.

Métricas reales después de migrar

La migración de prototipo Next a Astro nos dio estos cambios medidos al 2026-05-24 en páginas de tool típicas:

  • Tamaño del bundle inicial de la landing: 280 KB en Next, 12 KB en Astro (solo HTML + CSS).
  • Lighthouse Performance mobile en landing: 78 en Next, 99 en Astro.
  • Time to Interactive en página de tool: 2.1 s en Next, 0.8 s en Astro (con isla cargada por client:visible, hidratación bajo el fold).
  • Tamaño total deploy: 18 MB en Next, 4 MB en Astro (sin server runtime).
  • Costo mensual de hosting: tier paid Vercel vs free tier Cloudflare Pages.

Estos números son específicos de nuestro caso. Si tu producto carga muchas libs React en todas las páginas, la diferencia se reduce. La gran ganancia ocurre cuando el contenido estático es la mayoría y la interactividad es puntual.

Recursos consultados

  • Astro docs oficiales, sección Islands Architecture.
  • Next.js 15 release notes y comparativas comunitarias al 2026.
  • Cloudflare Pages docs sobre adapter estático.
  • Lighthouse audits internos de aGo lab al 2026-05-24.

¿Quieres ver una isla React en acción?

Cada tool de tools.ago.cl es una isla. Abre cualquiera y mide tú mismo.

Ver las tools

¿Necesitas migrar tu suite a islas?

aGo lab construye software optimizado para performance y SEO. Si tu producto puede beneficiarse de la arquitectura por isla, conversemos.

Conversemos

§ CÓMO CITAR ESTE ARTÍCULO

aGo lab. (2026). "Astro islas, el encaje perfecto para tools cliente-puro". tools.ago.cl/notas/astro-islas-cliente-puro. Recuperado el 2026-05-25.

Esta nota es contenido propio de aGo lab y se publica bajo política de atribución requerida. Si la citas o referencias, incluye el link a la URL original.

§ aGo lab estudio

¿Quieres que aGo lab implemente esto para ti?

Si esta decisión técnica te resuelve un problema y necesitas aplicarla a un sistema propio, conversemos.

§ COMMAND

↑↓ navega · Enter abre · Esc cierra