Jose Rosendo

Migrando mi web de Next.js a Astro ⚡️🚀

Me encanta Next.js, pero Astro me enamoró desde el momento en que lo probé por primera vez... Te cuento por qué.

10/24/2023 — 8 mins.

Transición del logo de Next.js al logo de Astro

En primer lugar, ¿por qué crear un blog/portfolio?

Mi viaje en la creación de esta web personal comenzó con la idea de tener un espacio donde compartir mi pasión por la tecnología y la programación. Aunque en el post Hola mundo 👋🌍lo explico en detalle, la razón principal detrás de esta iniciativa fue expresar mis ideas, proyectos y experiencias con los demás.

Y, a pesar de que existen numerosas plataformas y servicios que facilitan la creación de blogs y sitios web, decidí programar mi propia web por múltiples razones. En primer lugar, quería el control total sobre el diseño, la funcionalidad y la personalización. Programarla desde cero me permitió adaptarla a mis necesidades específicas y experimentar con nuevas tecnologías.

Además, como desarrollador de software de profesión, consideré que la creación de mi propia web sería una oportunidad ideal para aplicar y mejorar mis habilidades. Programar la web yo mismo me desafió a aprender y crecer en el proceso, y me enriqueció profesionalmente.

Pienso que cualquier desarrollador de software, sobre todo si se dedica al desarrollo web, debe tener una presentación digital en forma de portfolio; y estoy convencido de que contar con un blog es una buena idea de cara al crecimiento profesional. Así te obligas a recapacitar sobre ciertos temas técnicos, y desarrollas algunas soft skills como la comunicación escrita.

¿Por qué elegí Next.js inicialmente?

Aunque el stack tecnológico debe ser completamente transparente para el usuario final, la primera decisión importante que tuve que tomar fue qué tecnologías usar para el desarrollo de la web. Después de investigar y evaluar diversas opciones (React “puro”, Remix, Gatsby…), opté por Next.js por varias razones clave.

En primer lugar, la velocidad y el rendimiento fueron factores determinantes en mi elección. Por defecto, Next.js pre-renderiza las páginas en el servidor, usando el renderizado del lado del servidor (SSR) y la generación de sitios estáticos (SSG), lo que garantiza tiempos de carga rápidos y una experiencia fluida para los usuarios ya que el grueso del trabajo no tiene que hacerse en el lado del cliente y este recibe todo el HTML ya “preparado”.

La optimización para motores de búsqueda (SEO) también fue esencial en mi decisión. Con Next.js, la generación de páginas estáticas se combina con la capacidad de realizar renderizado en el servidor, lo que mejora la visibilidad de mi contenido en los motores de búsqueda. Me explico, al generar HTML estático en el servidor, Google ya conoce el contenido de las páginas de mi web porque nunca va a cambiar dinámicamente.

La estructura de archivos que ofrece Next.js también fue algo que me ayudó a decantarme por usarlo; enrutamiento basado en sistema de archivos, rutas de API dentro del mismo repositorio, rutas con segmentos dinámicos, etc.

La comunidad activa, documentación y ecosistema de Next.js también jugaron un papel fundamental en mi elección. La disponibilidad de plugins, bibliotecas y recursos online facilitó la resolución de problemas y la expansión de la funcionalidad de mi sitio.

Ojo, ninguna de estas características son exclusivas ni las ha inventado Next.js. Pero es verdad que en el momento en el que yo me encontraba cuando comencé el desarrollo de esta web, mi foco principal era React, y Next.js estaba rompiendo moldes y petándolo como ningún otro framework.

Pero… Publican Astro 1.0

Astro fue desarrollado por Fred K. Schott. La idea detrás de Astro era simplificar el desarrollo web y reducir la complejidad inherente a las tecnologías modernas. El 9 de Agosto de 2022, Fred publicó el artículo Astro 1.0en el blog de Astro. Este artículo llega para anunciar la salida de la primera versión estable de Astro y romper con todo lo establecido por Next.js.

Desde entonces, Astro se ha posicionado como una de las principales alternativas a las tecnologías tradicionales de desarrollo web por su marcado enfoque a enviar menos cantidad de JavaScript al cliente por defecto.

Sin embargo, creo que una de las principales características de Astro que, de momento, no es posible encontrar en Next.js es la compatibilidad con cualquier librería de UI que puedas imaginar. Si no quieres usar ninguna puedes hacerlo pero, en caso de que quieras, puedes usar React, Svelte, Vue, Lit… ¡Solas o mezcladas, como quieras!

Por simplificar un poco la historia, Astro hace exactamente todo lo que Next.js hace, pero aprendiendo de los errores que este presenta:

  • Uso obligado de React.
  • Mucho boilerplate, incluso para las aplicaciones más simples.
  • Hasta la implementación de App Routery RSC, la manera de mezclar cliente y servidor era muy engorrosa.

¿Por qué he decidido hacer la migración?

Hacía tiempo que no sentía lo mismo al usar Next.js, y nunca llegué a actualizar a la versión 13 (que incluye App Router y RSC) por la complejidad que esto suponía. Sinceramente, me ha sido mucho más fácil migrar toda la web a Astro que migrar Next.js de la versión 12 a la 13…

Dejando a un lado este tema, que tiene más que ver con desgana que otra cosa, he decidido hacer la migración porque cada vez que he usado Astro para pequeños proyectos (como Simple Weather App) he podido prototipar nuevas funcionalidades extremadamente rápido, el uso de Vite como compilador es crucial para mi, y por supuesto el poder usar JSX en archivos .astro sin necesidad de cargar React en cada página.

Debo decir que, por mucho que me guste Next.js, Astro se ha convertido para mi en una de las tecnologías a las que acudo primero para casi cualquier tipo de proyecto personal.

Aunque no todo es un campo de rosas con Astro…

Astro es genial y me encanta, pero no todo ha sido tan fácil como podría haber sido. Una de las cosas que más me frustra de Astro es la imposibilidad de usar código de servidor (.astro) dentro de código de cliente (.tsx con react). Componentes de Astro no pueden usarsedentro de componentes de frameworks de UI a no ser que hagas “ñapas” como usar los <slot><slot> y la prop children.

Dicho esto, entiendo la limitación y debo decir que en Next.js pasa algo parecidocon el uso de React Server Components.

Ahora sí, ¿qué hace Astro claramente mejor que Next.js?

  • Content Collections: ¿CMS, para qué? En su versión 2.0, Astro introdujo Content Collections. Una manera súper sencilla de administrar contenido estático como, sorpresa, los posts de un blog (o los proyectos de un portfolio).
  • Integraciones: El ecosistema de Astro ha mejorado con creces en los últimos meses, y ya cuenta con más de 300 integraciones (oficiales y de la comunidad) que facilitan enormemente el SEO, las Analytics, los despliegues, y casi cualquier cosa que se te ocurra.
  • Zero JavaScript: Por defecto, Astro envía las páginas al cliente con cero JavaScript. Esto implica que no son interactivas, pero al contar con las Astro Islandsesto no es un inconveniente ya que a estas mínimas partes que sí necesitan interactividad, se las hidrata con su JS correspondiente sin afectar al resto de la página.

Comparativa de código

En esta comparativa, que a simple vista resulta chocante (porque realmente lo es), puedes ver cómo en Next.js gestiono los datos de los blog posts manualmente. Tengo una función getArticlegetArticle que uso para recuperar el frontmatter y el contenido del post.

En cambio, en Astro hago uso de las Content Collections y sus métodos predefinidos como getEntryBySluggetEntryBySlug. Solo tengo que delegar este trabajo a Astro y él lo hace todo solito.

Ojo, seguro que hay mil maneras distintas de abordar esta situación, y han pasado casi tres años entre que desarrollé cada versión, pero así podemos hacernos una idea de cuánto Astro simplifica el desarrollo.

Next.js
export const getStaticProps: GetStaticProps = async ({ params }) => {
  const post = getArticle(ArticleTypes.POSTS, params?.slug as string);
  const [previous, next] = getPreviousNext(
    ArticleTypes.POSTS,
    params?.slug as string
  );
 
  const mdxSource = await serialize(post.content, {
    scope: post.data,
    mdxOptions: {
      remarkPlugins: [
        RemarkAutolinkHeadings,
        RemarkSlug,
        RemarkCodeTitles as any,
      ],
      rehypePlugins: [mdxPrism],
    },
  });
 
  return {
    props: {
      source: mdxSource,
      post,
      previous,
      next,
    },
  };
};
Next.js
export const getStaticProps: GetStaticProps = async ({ params }) => {
  const post = getArticle(ArticleTypes.POSTS, params?.slug as string);
  const [previous, next] = getPreviousNext(
    ArticleTypes.POSTS,
    params?.slug as string
  );
 
  const mdxSource = await serialize(post.content, {
    scope: post.data,
    mdxOptions: {
      remarkPlugins: [
        RemarkAutolinkHeadings,
        RemarkSlug,
        RemarkCodeTitles as any,
      ],
      rehypePlugins: [mdxPrism],
    },
  });
 
  return {
    props: {
      source: mdxSource,
      post,
      previous,
      next,
    },
  };
};
Astro
const post = await getEntryBySlug("posts", slug!);
 
let previousPost: CollectionEntry<"posts"> | undefined;
if (previous) previousPost = await getEntryBySlug("posts", previous);
let nextPost: CollectionEntry<"posts"> | undefined;
if (next) nextPost = await getEntryBySlug("posts", next);
 
const {
  remarkPluginFrontmatter: { readingTime },
  Content,
} = await post!.render();
Astro
const post = await getEntryBySlug("posts", slug!);
 
let previousPost: CollectionEntry<"posts"> | undefined;
if (previous) previousPost = await getEntryBySlug("posts", previous);
let nextPost: CollectionEntry<"posts"> | undefined;
if (next) nextPost = await getEntryBySlug("posts", next);
 
const {
  remarkPluginFrontmatter: { readingTime },
  Content,
} = await post!.render();

Comparativa de rendimiento

He realizado algunas pruebas entre las versiones de producción de las dos versiones de la web. Puede que los resultados te sorprendan tanto como a mi:

MétricaNext.jsAstroMejora aprox.
Duración media de 10 despliegues01:4200:4250%
Carga de JavaScript inicial1.4MB217kB84%
First Contentful Paint2.8 s1.4 s50%
Total Blocking Time8300 ms0 ms100%
Speed Index5.2 s1.4 s73%
Largest Contentful Paint3.0 s2.7 s10%
MétricaNext.jsAstroMejora aprox.

Y, ¿ahora qué?

Uno de los siguientes pasos que me gustaría dar es implementar Bunahora que ha salido la primera versión estable. Aún está por ver si llevaré a cabo este cambio, porque en el momento en el que escribo este post, Bun aún tiene muchas cosas que pulir y todavía le queda mucho para asentarse en el mercado.

Esto implicaría sustituir Vite y PNPM por Bun, consiguiendo unas velocidades de vértigo durante el desarrollo y el despliegue (proceso de build).

En conclusión…

Creo que he vendido muy bien Astro en este post, pero me gusta de manera sincera. Creo que es una alternativa que, en muy poco tiempo, ha sabido hacer las cosas de manera adecuada y escuchando muy atentamente a la comunidad del desarrollo web.

Te recomiendo darle una oportunidad a Astro si tienes en mente hacer alguna web estática o con poca interactividad y, por supuesto, también te recomiendo probar Next.js si no lo has hecho aún, ya que te va a aportar enormes conocimientos sobre React y Server Components.