Skip to main content

Astro: HTML First, JavaScript Only Where It's Needed

· 20 min read
Pere Pages
Software Engineer
Flat illustration of a calm sea made of layered paper waves in cream and indigo, with a few small interface elements — a toggle switch, a search bar and two round buttons — floating on it like islands, each glowing lime green

Astro is a web framework that sends plain HTML to the browser and adds JavaScript only to the few parts of a page that need it. This post explains how its engine works, the ideas behind it, and how it compares to Next.js, Gatsby, Eleventy, Hugo and the others.

note

This post describes Astro as of December 2024, one week after version 5.0. Web frameworks change fast, so check the current documentation before you choose one.

The mental model: a page of HTML with a few islands​

An Astro page is mostly finished HyperText Markup Language (HTML), with a few small interactive parts inside it that each load their own JavaScript. Astro calls those parts islands. The rest of the page is the "sea": text, images and links that the browser can show at once, with no JavaScript at all.

Think of a blog post page. The header, the article and the footer never change after the page loads. Only two parts react to the reader: a search box and a "like" button. Astro turns everything into HTML on the server. Then it sends JavaScript for the search box and the like button only, and each of them starts on its own.

Keep this picture in mind. Every other part of Astro, from the engine to the philosophy, comes from it.

The problem Astro was built to solve​

Many websites of the late 2010s were built as single-page applications (SPAs). In an SPA, the browser downloads a large JavaScript bundle, and that JavaScript draws the page. This works well for apps like an email client. It works badly for a blog, a documentation site or a shop's product page, where most of the content is just text.

Frameworks such as Gatsby and Next.js improved this with server-side rendering (SSR) and static site generation (SSG). Both produce the HTML before it reaches the browser: SSR on each request, SSG once at build time. The reader sees the content quickly. But then comes a second step called hydration. Hydration means the browser downloads the JavaScript for the whole page, runs it again, and attaches it to the HTML so the page becomes interactive. On a blog post, the browser still downloads the code for the header, the article and the footer, even though none of them ever change. On a slow phone, that extra work delays the moment the page responds to a tap.

In 2019 Katie Sylor-Miller, then front-end architect at Etsy, gave the solution a name: "component islands". Jason Miller, the creator of Preact, described the pattern in 2020 as the islands architecture[1]: render the page as HTML on the server, and hydrate only the small interactive "islands" inside it, each one independently. Astro was built entirely around this idea. It launched in June 2021 with the message "ship less JavaScript"[2].

The engine at a glance​

Astro's engine has one job: turn your source files into HTML at build time or on the server, and turn only the islands into small JavaScript files. The styles become Cascading Style Sheets (CSS) files next to the HTML. Astro is built on Vite, a fast build tool for web projects, and adds its own compiler for .astro files.

The next four sections follow this picture from left to right: the .astro component, the islands, when the HTML is produced, and where the content comes from.

Part 1: .astro components run once, on the server​

An .astro file is HTML with a small code section on top. The code section sits between two --- lines. It runs on the server (or at build time), never in the browser. You use it to fetch data, import other components and prepare values. The part below the second --- is the template: HTML with {expressions} inside, similar to JavaScript XML (JSX).

src/pages/index.astro
---
// This code runs on the server or at build time — never in the browser.
import Layout from '../layouts/Layout.astro';
import { getCollection } from 'astro:content';

const posts = await getCollection('blog');
---

<Layout title="My blog">
<h1>Latest posts</h1>
<ul>
{posts.map((post) => (
<li><a href={`/blog/${post.id}/`}>{post.data.title}</a></li>
))}
</ul>
</Layout>

When Astro builds this page, the output is plain HTML: a heading and a list of links. The import lines, the await and the map are gone. Astro sends zero JavaScript by default[3]. There is no framework runtime in the browser, because there is nothing left to run. A file in src/pages/ also becomes a route: src/pages/about.astro is the page at /about.

An .astro component is a template that runs once and disappears; it is not a component that lives in the browser. This is the biggest difference from React or Vue, and it explains the rest of the design.

Part 2: islands and client directives​

To make something interactive, you use a component from a user interface (UI) framework. Astro has official support for React, Preact, Svelte, Vue, SolidJS and Alpine.js[4]. Then you decide when its JavaScript loads, with a client: directive (a special attribute that Astro reads at build time):

src/pages/post.astro
---
import Search from '../components/Search.jsx'; // a React component
import LikeButton from '../components/Like.svelte'; // a Svelte component
import Chart from '../components/Chart.vue'; // a Vue component
---

<Search client:load />
<article>…</article>
<LikeButton client:visible />
<Chart /> <!-- no directive: rendered to HTML, no JavaScript sent -->

Without a directive, even a React component is turned into HTML and sent with no JavaScript. With a directive, it becomes an island. Each directive picks a different moment[5]:

DirectiveWhen the island's JavaScript loadsGood for
client:loadRight away, when the page loadsThings the reader uses at once, like a menu or search box
client:idleWhen the browser has finished its first work and is not busyLess urgent parts, like a newsletter form
client:visibleWhen the island scrolls into viewParts far down the page, like comments
client:mediaWhen a CSS media query matches, for example a narrow screenParts that exist only on some screen sizes, like a mobile menu
client:only="react"Right away, and the server does not render it at all (you name the framework)Components that need the browser, for example ones that read window

The browser receives the HTML first and shows it. Then each island loads on its own schedule, and one slow island does not block the others:

The "CDN" in the diagram is a content delivery network: servers in many places that keep copies of your files close to the reader.

One consequence follows directly from the mental model. Islands are separate: each one has its own state, and they do not share a React or Vue tree. If two islands need the same data, for example a cart icon and an "add to cart" button, you share it through a small store library such as Nano Stores, which Astro's documentation recommends[6].

Part 3: when the HTML is made — static, on demand and server islands​

Every Astro page is HTML, but Astro can make that HTML at two different moments:

  • At build time (prerendered). Astro builds the page once, and any web host or CDN can serve the file. This is the default.
  • On demand, for each request. A server builds the page when a reader asks for it. Use this for pages that change per reader, like an account page.

On-demand pages need an adapter: a small plugin that makes the built site run on a given host, such as Node.js, Netlify, Vercel or Cloudflare[7]. Since Astro 5.0 you mix both kinds in one project without a special mode. A page opts in or out with one line, export const prerender = false[8].

Astro 5.0 also made server islands stable[8]. A normal island moves JavaScript to the browser. A server island is different. It keeps the work on the server but delays it. The main page is built once and cached. One slow, personal part, like the reader's avatar or a price for their country, is filled in later by a separate request:

src/pages/product.astro
---
import Avatar from '../components/Avatar.astro';
---

<Avatar server:defer>
<img slot="fallback" src="/generic-avatar.svg" alt="" />
</Avatar>

The page arrives with the generic image in place. Then the server renders Avatar for this reader and swaps it in. The same islands idea now works on both sides: client islands delay JavaScript, server islands delay server work.

Part 4: content collections and the content layer​

Astro was designed for content sites, so it treats content as data with a clear shape. A content collection is a group of similar entries, such as blog posts or authors. You describe each collection once: where its entries come from (a loader) and what fields each entry must have (a schema, written with the Zod validation library)[9].

src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const blog = defineCollection({
// Where the entries come from: every Markdown file in this folder.
loader: glob({ pattern: '**/*.md', base: './src/data/blog' }),
// What every entry must contain.
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
}),
});

export const collections = { blog };

If a post is missing its title, the build fails with a clear message, instead of the page breaking later in production. And getCollection('blog') returns typed entries, so your editor knows every field. Before Astro 5.0, collections could only read files from src/content/. The content layer that came with 5.0 made the loader replaceable. The same getCollection() function can now read local files, a headless content management system (CMS, a service where editors write content), or any web application programming interface (API)[8].

That completes the engine: templates that run once, islands that load alone, a choice of when HTML is made, and typed content. The next question is why it was designed this way.

The philosophy: five design principles​

Astro's documentation names five design principles[10]. Each one is a choice that other frameworks make differently:

  • Content-driven. Astro is designed for sites where content is the main point: blogs, documentation, marketing pages, shops. It does not try to be the best tool for every app.
  • Server-first. Pages are rendered on the server whenever possible, not in the browser. The browser gets HTML, which it is very good at showing.
  • Fast by default. A new Astro site sends no JavaScript. You add it deliberately, island by island. A slow page takes extra work to create; a fast page is the starting point.
  • Easy to use. An .astro file is HTML with an optional code section. If you know HTML, CSS and a little JavaScript, you can read it.
  • Developer-focused. Good error messages, typed content, and a large set of official integrations.

Two more ideas run through the design. The first is bring your own framework: Astro does not force one UI library on you, and a single page can mix React, Svelte and Vue islands. The second is use the web platform: HTML links for navigation, standard CSS, and normal pages instead of a custom client-side router.

The common thread is a reversal of defaults: other frameworks start with JavaScript everywhere and let you remove it; Astro starts with none and lets you add it.

Why that makes Astro so useful​

The principles turn into practical advantages you notice on real projects:

  1. Fast pages without effort. Less JavaScript means less to download, read and run. That matters most on mid-range phones and slow networks. Google measures this with Core Web Vitals, its metrics for loading speed, response to input and layout stability. In Astro's 2023 study of real-world data, Astro was the only framework where more than half of the sites passed Google's Core Web Vitals check[11]. Astro's team wrote that study, but the data comes from Google's public records of real visits.
  2. Performance that does not get worse over time. In a React SPA, every new feature adds to the bundle for every page. In Astro, a new island only costs JavaScript on the pages that use it.
  3. No lock-in to one UI framework. You can reuse the React components you already have, try Svelte for one widget, or move from Vue to React one island at a time.
  4. Content with guarantees. Collections check every entry against its schema at build time, and Markdown and MDX (Markdown with components inside) work without setup.
  5. Deploy almost anywhere. A fully static Astro site is a folder of files. Any host serves it, often for free. When you need a server, you add an adapter.
  6. A small thing to learn. There is no hydration to debug on static parts, and no client-side state to think about unless you create an island.

How Astro compares to the alternatives​

Astro competes with two groups of tools. The first group is static site generators that output HTML and very little JavaScript. The second group is full JavaScript frameworks that can build anything, including app-like interfaces, and ship a framework runtime to the browser.

  • Next.js is a React framework by Vercel. It renders on the server with React Server Components (RSC), which keep some components on the server. But the browser still loads React and hydrates every client component[12]. It is excellent for web apps and large products.
  • Gatsby is a React-based static site generator with a GraphQL data layer (GraphQL is a query language; Gatsby uses it to combine content from many sources). It was very popular around 2019–2021. Netlify bought the company in February 2023[13].
  • Eleventy (11ty) is a small, flexible static site generator in JavaScript[14]. It outputs HTML and adds no JavaScript. Interactivity is up to you.
  • Hugo is a static site generator written in Go, known for very fast builds on huge sites[15]. Its templates use Go's template language, which many web developers find unfamiliar.
  • SvelteKit is the full framework for Svelte[16]. Like Next.js it hydrates whole pages, but Svelte's runtime is small, so the cost is lower.
  • Docusaurus is a React-based site generator by Meta, specialised in documentation[17]. It is ready to use for docs, but every page is a React app. (This blog runs on it.)

Here they are side by side. "JavaScript sent by default" means what a plain text page costs the reader before you add any interactive feature.

ToolJavaScript sent by defaultContent sitesApp-like interfacesUI framework choiceLearning curve
AstroNoneExcellentFairAny, mixedLow
Next.jsReact runtimeGoodExcellentReact onlyHigh
GatsbyReact runtimeFairFairReact onlyMedium
EleventyNoneExcellentPoorAdd it yourselfLow
HugoNoneExcellentPoorAdd it yourselfMedium
SvelteKitSmall Svelte runtimeGoodExcellentSvelte onlyMedium-low
DocusaurusReact runtimeGood (docs)PoorReact onlyLow

bestgoodfairweak

The table hides one important point, which a picture shows better. The tools sit on two axes: how much JavaScript a page sends by default, and how much app-like interactivity the tool is built for. Astro sits in the corner that the others leave empty: very little JavaScript by default, with a clear way to add interactivity when a page needs it.

When Astro is the wrong choice​

Astro is built for content, and its limits come from the same design:

  • Highly interactive apps. A dashboard, an editor or a chat app is interactive almost everywhere. In Astro, the whole page becomes one giant island. You pay all the costs of an SPA, and you also have Astro's extra layer to learn. Next.js, SvelteKit or a plain SPA fit better.
  • Lots of shared state between parts of the page. Islands do not share state by default. A few shared values are easy with a store. Dozens of connected widgets are easier in one framework tree.
  • App-style navigation. Astro pages are separate HTML documents. Astro 5.0 offers the <ClientRouter /> component for animated page changes with the browser's View Transitions API[18], but it is not a full client-side router with the state that persists between pages.
  • A team that needs one stack for everything. If your company builds its product in Next.js, a marketing site in Next.js may be simpler to maintain, even if it sends more JavaScript.

Wrapping up​

Choose Astro when most of your pages are content and only some parts need to react to the reader. Its engine turns templates into HTML and ships JavaScript only for the islands you mark, and version 5 extends the same idea to server work and to where your content comes from. For a blog, a documentation site, a portfolio or a marketing site, that design gives you fast pages by default. For an app that is interactive everywhere, choose a full framework instead.

References​

  1. Jason Miller, Islands Architecture — jasonformat.com (2020)
  2. Introducing Astro: Ship Less JavaScript — Astro blog (2021)
  3. Components — Astro Docs
  4. Add integrations — Astro Docs
  5. Template directives reference — Astro Docs
  6. Share state between islands — Astro Docs
  7. On-demand rendering — Astro Docs
  8. Astro 5.0 — Astro blog (December 2024)
  9. Content collections — Astro Docs
  10. Why Astro? — Astro Docs
  11. Fred Schott, 2023 Web Framework Performance Report — Astro blog (March 2023)
  12. Server and Client Components — Next.js Docs
  13. Netlify acquires front-end platform Gatsby — TechCrunch (February 2023)
  14. Eleventy — 11ty.dev
  15. Hugo — gohugo.io
  16. SvelteKit docs — svelte.dev
  17. Docusaurus — docusaurus.io
  18. View transitions — Astro Docs