Web Dev Diaries

A blog and resource for modern web development

Astro: A game-changing web framework

Astro is a web app framework for building experiences that are content-driven and highly performant in the browser. I’ve had some real fun building a project with it over the past few months, to the extent that I’m starting to plan how I’ll rebuild this entire blog with it to allow me to add new features more easily.

I’m so enamoured with is lately that I just had to shout about its best features 😍

Before diving in, just wanted to thank my buddy Ranajay Das for introducing me to Astro earlier this year! He’s an excellent engineer and you should definitely check out his work.

1. Incredible runtime performance

“It should be impossible to build a slow website in Astro.” - one of Astro’s 5 design principles

I think this is an afterthought for most developers, but its importance surely can’t be overstated. Think about how frustrating it is to visit a website for it to take forever to load a single page. Astro solves this in a few ways.

Server-Side Generation (SSG) has the biggest impact, by running all business logic at build time by default, and only shipping raw HTML and CSS to the browser. Almost zero JavaScript. This results in virtually immediate page loads.

It’s truly a beautiful thing seeing those πŸ’― results in Google Lighthouse. Say hello to better SEO automatically!

My WIP project’s homepage and its perfect Lighthouse performance score.

My WIP project’s homepage and its perfect Lighthouse performance score.

2. Islands of interactivity

When I first read that Astro supports React, Vue, Svelte, Solid, Lit, etc, I couldn’t fathom how or why.

If you’re wondering the same things, stay with me on this, because it’s one of the coolest things about the framework. The Astro team call them Islands - self-contained components that can render virtually any UI library whilst still sitting in the rest of the Astro component tree.

How? The Astro compiler (partially powered by Vite) handles building different libraries using their respective plugins, which get triggered as Islands are encountered in the code.

As for the why, keep in mind that Astro is SSG native, meaning your business logic, API calls and so on are done at build time.

To add interactivity, you can write a component in the syntax of another UI library! You can literally put a React component in your project, import it and render it into an Astro template:

---
import FancyReactThing from "./FancyReactThing.tsx";
---

<div>
	<!-- other astro template stuff... -->
	<FancyReactThing someProp="Hello!" client:visible />
</div>

You’ll notice that extra client:visible attribute - that’s your way of telling Astro when to render this component, and you can customise this to only render when the component is actually visible in the page, or when the page first loads. If you omit the attribute entirely, Astro is clever enough to render that component at build time, saving you from serving the JavaScript that would have been required to run it in the browser (at the expense of the component being non-interactive).

Do we really need to use other UI libraries? πŸ™„

Technically, you don’t need Islands for interactive elements as Astro still allows you to add <script> tags in your templates, which their docs cover really well. It’ll also give you the full power of using modern JS or TypeScript features, but you’ll have to wire up event handlers in a very vanilla way. If you’re comfortable with this and only need a tiny smattering of interactivity, this is OK.

My take is that Islands are a very welcome feature. If you want to add some fancy UI logic or a 3rd party library to do something like display a modal, you can just enable the UI library you like (React, Vue, etc), create a new component in that library and import the modules it needs. The resulting codebase might look a bit odd mixing file types like this, but I believe this is only a perspective. This is definitely a new concept to most (myself included), and perhaps there’s some additional cognitive overhead dealing with multiple component types, but I’ve personally enjoyed the flexibility of dropping back into my comfort zone of React when I needed to inject something fancy.

3. Easy to setup and learn

Setting aside potential complexities introduced by adding UI libraries via Islands, Astro is otherwise a synch to pick up and use from scratch, especially if you’ve dabbled with Vue or Svelte that place business logic next to templates inside component files.

For starters, the Astro CLI is on par with the likes of Vite and Next.js, stepping you through new application setups with common features included like enabling TypeScript support. You may also choose one of their basic starter templates in the setup process, or refer to their list of recommended templates for more packed-out boilerplates.

Once you’re setup, it’s easy to figure out how to put a site together. The src/pages directory will house Astro components, each of which will automatically turn into the routes of your website. Here’s some examples:

  • pages/index.astro - The homepage at: /
  • pages/blog/index.astro - A landing page for a blog at: /blog
  • pages/blog/[...slug].astro - Dynamically-generated, recursive routes under the blog, such as: /blog/my-post and: /blog/some/nested/article

The latter example relies on the developer telling Astro what the routes will be at build time. This means adding some code into the component’s server-side logic. You’ll see an example of this if you bootstrapped an app using the blog template, which will look something like this:

---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.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();
---

<BlogPost {...post.data}>
  <Content />
</BlogPost>

The getStaticPaths is the important function here, which when exported, is what Astro looks for to know which routes should be recognised, and what data they will have available in the template (in this case it’s the post data pulled in from reading local Markdown files).

This is probably about as complex as Astro components get. You can also define components inside /src/layouts, which can be used to wrap the whole page.

And by the way, Astro’s documentation is top-tier and covers many use cases that you’d probably encounter. You should definitely use it.

4. Zen and the Art of Content-Driven Websites

If you’re at all interested in creating sites or apps that include content driven by Markdown, you’re in for a real treat with Astro! 🍦

You saw above how Astro components can import from astro:content - this is another killer feature of the framework. Not only can you simply import a bunch of Markdown files in your project, Astro even type-checks them for you!

Say you have this kind of Markdown content:

---
title: "How to build a boat"
description: "This is an article about the joy of boat-building"
pubDate: "05/09/2023"
tags: ["fun", "boats"]
---

# Welcome!

This is a special article dedicated to all the boat lovers out there.

The top section between the dashes called frontmatter is the metadata associated with this article. You can specify any fields you want in YAML format.

Astro reads these fields when importing a collection. A collection is made up of multiple Markdown files stored inside the src/content/blog/*, where “blog” can be whatever name you want. You can define multiple folders inside src/content, each being its own collection.

When Astro finds a Markdown file and imports it, it checks those frontmatter fields against a schema you define inside src/content/config.js, such as in this example:

import { defineCollection, z } from "astro:content";

const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    tags: z.array(z.string()).optional(),
  }),
});

export const collections = { blog };

If you try excluding a field in a Markdown file that’s not marked as optional, Astro will fail to build and tell you which field is missing, ensuring you have consistent content data flowing into your app.

On top of this awesomeness, you can properly type-check dates defined as strings via z.coerce.date(), which also converts it to a proper Date object before you read the data in your component!

I think Astro has absolutely nailed it when it comes to building content-first webapps that are simple and logic to build, whilst maintaining complete customisability. Other frameworks like Next.js support some of Astro’s other features, but the way Astro handles static content is head-and-shoulders above the rest.

One more thing about content…

Astro even makes this possible:

---
title: Article
---

import Tooltip from "~/components/Tooltip";

Bla bla... <Tooltip client:visible>interactive text</Tooltip> ...bla bla

What you’re seeing here is an MDX file that imports a React component and renders it into the body of the content. This is a feature of MDX that Astro fully supports, including rendering components in SSG mode by default, and accepting the client: attribute to control how it loads in the browser.

When I realised this, it opened up so many possibilities that previously would have held me back from creating Markdown-fuelled web apps. Now you really can have your cake AND eat it too. 🍰

Also: Astro v3.0 just landed!

Just a few days ago, the team behind Astro announced the release of v3.0 of the framework, which is a huge step forward for a number of reasons. To name a few:

  • Support for native View Transitions API - a brand new browser feature allowing web apps to animate smoothly between pages
  • Even better runtime performance - 30-75% faster component rendering 🀯
  • *Hot Module Replacement for React & Preact - when using components from either of these libraries, development refreshes will be near-instant
  • Better optimised HTML output - cleaner, faster HTML files when building an app

Continuing their trend of having fantastic documentation, the team have also given us a v3 migration guide here: https://docs.astro.build/en/guides/upgrade-to/v3/.

Convinced or confused?

As I said at the start, I’m head-over-heels with Astro. I think it’s a huge game-changer for web development in a number of ways. It’s definitely polarising with some of its design decisions, but I’m squarely in the camp of being open to trying new methodologies, figuring out what works and what doesn’t, and putting my loyalty towards my comfortable tools (React, Next.js) to the test.

Ultimately, I think if you’re building a webapp/website that needs great performance, SEO, is highly content-driven and mostly information-based, Astro is the best framework to choose at the moment and you should absolutely give it your time to try out.

comments powered by Disqus