Astro 5 Has It All

If you are not familiar with it, Astro is a web framework for building content-driven websites. It is best known for pioneering a new frontend architecture which reduces the overhead and complexity of JavaScript.

If this sounds too much like an abstract sales pitch, here is Astro broken down in 5 core features you really need to know:

1. Server First Approach

Astro renders HTML on the server and sends fully-rendered pages to the client. This leads to faster load times and better SEO, as the browser doesn’t need to parse and execute large amounts of JavaScript.

2. Zero JS by default

Unlike many frameworks, Astro ships with no JavaScript on the client unless you explicitly add it.

<html>
  <head>
    <title>Awesome</title>
  </head>
  <body>
    <h1>Awesome</h1>
    <button>Say hi!</button>
  </body>
</html>

This minimalistic approach keeps sites performant by default, with no unnecessary client-side JS overhead.

3. Islands Architecture

When interactivity is required, Astro uses “islands” of JavaScript. These are individual components that can be hydrated with JavaScript to enable dynamic functionality. Instead of loading all JavaScript upfront, only the necessary parts are hydrated.

4. UI Integrations

Astro allows you to bring in components from popular UI libraries like React, Vue, Svelte, Solid, and even Preact without locking you into one specific stack. This means you can mix and match the best tools for your project, allowing for flexibility and reducing the need for rewrites when incorporating components from different ecosystems.

5. Content-first with powerful Content Collections

Even though Astro is fast by default, it’s made for content-heavy sites. Features like Content Collections allow you to organize and manage your site’s content easily in markdown, with type-safe frontmatter, automatic slugs, and powerful API integrations. This makes Astro a good fit for any type of web app ranging from a blog to a fully fledged ecommerce site.

So at this point Astro is a mature framework, with a proven track record and with a wide variety of features. Now that we are all on the same page with the core offering, let’s see what the hype regarding V5 is all about.

Astro v5

One of the highlights of this release is a new way to manage content in your project. The content layer is a flexible and extensible way to interact with content, providing a unified, type-safe API to access and define your content.

While storing content in markdown directly in your repository is fairly common, it might not be the best dev experience. On top of that, Content Collections data is stored entirely in memory which can be sometimes problematic.

Content Collections & Content Layer

The Content Layer is an intermediary step between your Content Collections API

import { defineCollection } from 'astro:content';

const blogCollection = defineCollection();

export const collections = {
  blog: blogCollection,
};

and your data source.

This is where you can define functions which fetch data from any source and then unifies it in a type-safe format which can be used across your project.

import { defineCollection } from 'astro:content';
import { notionLoader } from 'notion-astro-loader';

const database = defineCollection({
  loader: notionLoader({
    /* ... */
  }),
  schema: z.object({
    /* ... */
  }),
});

export const collections = {
  database,
};

Server Islands

Next, Server Islands are a new primitive allowing you to defer the rendering of dynamic content until after the initial page load.

<UserButton server:default />

You can mark your components with the server-defer directive, which tells Astro to skip the rendering of this element in the initial server response. This lets you cache the static page behind a CDN with some initial placeholder content. When the dynamic HTML has loaded, a small inline script replaces the server island on the page with the HTML result of the deferred render.

This might not sound like much, but it’s interesting to see the ongoing efforts frameworks are making to speed up both initial page load times and dynamic interactions.

Astro:Env

Another small quality of life improvement is the astro:env module that provides a type-safe way to define and access environment variables.

import { defineConfig, envField } from "astro/config";

export default defineConfig({
  env: {
    schema: {
      CLIENT_API_URL: envField.string({ context: "client", access: "public" }),
      SERVER_API_URL: envField.string({ context: "server", access: "public" }),
      API_SECRET: envField.string({ context: "server", access: "secret" }),
    },
  },
});

---
import { SERVER_API_URL, API_SECRET, PORT } from "astro:env/server";

const data = await fetch(`${SERVER_API_URL}/api`, {
  headers: {
    Authorization: `Bearer ${API_SECRET}`,
  },
});
---

<script>
  import { CLIENT_API_URL } from "astro:env/client";

  const clientData = await fetch(CLIENT_API_URL);
</script>

Once your variables are defined in your astro.config file, you’ll be able to access them anywhere in your code in a seamless manner.

Static & Hybrid

Finally, in Astro 5 it is easier to mix static and dynamic pages. By default, Astro generates static pages, but you can render pages on-demand with a simple config tweak.

Rendering strategies is a more complex topic, and you can find out more about how various strategies can impact your app behavior in some of my other videos.

Until next time, thank you for reading!