Niraj Chauhan

Niraj Chauhan

#father #husband #SoftwareCraftsman #foodie #gamer #OnePiece #naruto

NextJS vs. Astro: Choosing Developer Ease over Complexity

Posted by on

In the dynamic world of web development, the tools and technologies we choose significantly impact our productivity and the performance of our applications. My recent experience of transitioning my blog from NextJS to Astro offers a telling example of this. For those unfamiliar, my blog was originally developed using NextJS coupled with Markdown, allowing me to write in MDX files that supported React components. This approach eliminated the need for an external database for content storage. NextJS’s server-side rendering (SSR) capability ensured excellent page load speeds, contributing to a perfect PageSpeed score of 100, bolstered by its image optimization service.

Race Tracks

However, development in NextJS was not without its challenges. Markdown support wasn’t built-in, requiring reliance on external packages for full functionality. For instance, creating code blocks with three backticks (`) lacked syntax highlighting, necessitating additional configuration for markdown support and external theme CSS files. This setup became increasingly cumbersome.

Seeking a less management-intensive solution, I discovered Astro, which has been gaining traction for its robust features. Like NextJS, Astro supports TypeScript but offers enhanced markdown support through well-integrated external dependencies. The npm create astro@latest command provided a streamlined setup with a blog-based template, perfectly aligned with my markdown needs. Astro’s approach to modeling markdown content significantly reduced the need for extensive TypeScript files. Additionally, I opted for Tailwind CSS over custom CSS to minimize the amount of CSS code.

Astro’s routing system is akin to that of NextJS. A key attraction towards Astro was the opportunity to diverge from React, despite my eight years of experience with it. Astro maintained the image optimization feature, ensuring my PageSpeed score remained at 100. The transition to Astro resulted in a codebase change of +7647 lines (Astro) and -12563 lines (NextJS), reflecting a leaner, more efficient structure.

For a comparative perspective, here’s a snippet of my NextJS [slug].tsx post page:

import fs from "fs";
import matter from "gray-matter";
import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from "next";
import { MDXRemote } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import path from "path";
import CodeBlock from "../../components/CodeBlock";
import ExternalLink from "../../components/ExternalLink";
import MDXImage from "../../components/MDXImage";
import SEO from "../../components/SEO";
import Separator from "../../components/Separator";
import { Tags } from "../../components/Tags";
import { Post } from "../../models";

export const getStaticPaths: GetStaticPaths = async () => {
  const files = fs.readdirSync(path.join("posts"));
  const paths = files.map((filename) => ({
    params: {
      slug: filename.replace(".mdx", ""),
    },
  }));
  return {
    paths,
    fallback: false,
  };
};

export const getStaticProps: GetStaticProps = async ({ params }: GetStaticPropsContext) => {
  const slug = params?.slug;
  const markdownWithMeta = fs.readFileSync(path.join("posts", slug + ".mdx"), "utf-8");
  const { data: frontMatter, content } = matter(markdownWithMeta);
  const mdxSource = await serialize(content);
  return {
    props: {
      slug,
      date: frontMatter.date,
      title: frontMatter.title,
      description: frontMatter.description,
      imageUrl: frontMatter.imageUrl,
      imageAlt: frontMatter.imageAlt,
      tags: frontMatter.tags,
      content: mdxSource,
    },
  };
};

const PostPage = (post: Post) => {
  const { asPath } = useRouter();
  return (
    <section className="single">
      <Head>
        <script async src="https://platform.twitter.com/widgets.js" charSet="utf-8"></script>
        <script async src="https://platform.linkedin.com/in.js" type="text/javascript">
          lang: en_US
        </script>
      </Head>
      <SEO
        url={asPath}
        schemaType="article"
        title={post.title}
        description={post.description}
        image={post.imageUrl}
        createdAt={new Date(post.date).toISOString()}
        updatedAt={new Date(post.date).toISOString()}
      />
      <article>
        <time dateTime={post.date}>{post.date}</time>
        <Link href={"/blog/" + post.slug} passHref>
          <h1 className="post-title">{post.title}</h1>
        </Link>
        {post.content && (
          <MDXRemote {...post.content} components={{ CodeBlock, Separator, MDXImage, ExternalLink, Image }} />
        )}
        <Tags tags={post.tags} />
      </article>
      <hr className="divider" />
      <div className="share-btns">
        <div>
          <a
            href="https://twitter.com/share?ref_src=twsrc%5Etfw"
            className="twitter-share-button"
            data-show-count="false">
            Tweet
          </a>
        </div>
        <div className="linkedin">
          <script type="IN/Share" data-url={asPath}></script>
        </div>
      </div>
    </section>
  );
};

export default PostPage;

Contrastingly, the Astro equivalent is markedly more concise:

---
import { type CollectionEntry, getCollection } from 'astro:content';
import MainLayout from '../../layouts/MainLayout.astro';

export async function getStaticPaths() {
	const posts = await getCollection('blog');
	return posts.map((post) => ({
		params: { slug: post.slug },
		props: post,
	}));
}
type Props = CollectionEntry<'blog'>;

const post = Astro.props;
const { Content } = await post.render();
---

<MainLayout title={title} description={description} image={imageUrl}>
  <article class="pt-4 md:w-4/5 m-auto md:max-w-4xl py-4 border-b">
    <div class="content">
      <div class="text-center">
        <FormattedDate date={date} />
        <h1 class="text-4xl">{title}</h1>
      </div>
      <hr class="my-3" />
      <Content />
    </div>
    <Tags tags={tags} />
  </article>
</MainLayout>

From a developer’s standpoint, the transition to Astro has been highly positive. Writing cleaner, more concise code has been refreshing, and it’s easy to see why the developer community is gravitating towards Astro.

Performance Deep Dive: NextJS’s SSR Efficiency vs. Astro’s Islands Model

NextJS has established itself as a powerhouse for web application performance, primarily through its robust server-side rendering (SSR) and static site generation (SSG) capabilities. This framework is designed to pre-render pages on the server, which means that the initial page load for users is incredibly fast, as the HTML is already generated. Additionally, NextJS intelligently splits code at the component level, ensuring that only the necessary JavaScript and CSS are loaded for each page. This granular level of optimization is a significant boon for web applications that require dynamic content and seek to improve their SEO and user experience.

On the other hand, Astro introduces an innovative approach known as “Islands Architecture.” This concept revolutionizes the way we think about loading and executing JavaScript on the web. Instead of shipping a single large bundle of JavaScript, Astro allows developers to break their UI into individual components, or “islands,” each acting independently. These islands are only hydrated (i.e., turned into interactive elements with JavaScript) when necessary, reducing the amount of JavaScript sent to the browser. This results in faster page loads, especially for static sites where interactive elements are sparse. The Islands Architecture is a game-changer for developers aiming to create high-performance, content-driven websites with minimal client-side overhead.

Both NextJS and Astro offer unique solutions to web performance, each catering to different types of web applications. While NextJS optimizes through SSR and code splitting for dynamic, content-rich applications, Astro’s Islands Architecture paves the way for highly efficient, static-content-focused sites, making the web faster and more accessible.

Conclusion:

The switch from NextJS to Astro in my project illustrates a broader trend in web development: the continuous search for tools that optimize both developer experience and application performance. While NextJS offers robust SSR and an ecosystem conducive to React developers, its complexity in certain areas, like markdown integration, can be a drawback for projects with specific needs. On the other hand, Astro emerges as a compelling alternative, especially for developers seeking a more streamlined, markdown-friendly framework. It’s a testament to the evolving landscape of web development, where the suitability of a tool is highly dependent on the specific requirements and goals of a project. This journey from NextJS to Astro not only resulted in a more efficient codebase but also highlighted the importance of continually reassessing and adapting the tools we use to stay aligned with our evolving development needs.