Astro Island Architecture Demystified
According to various sources, Island Architecture was pioneered somewhen in 2019/2020. It is only recently that we are starting to hear more and more about this concept, and it’s because of the undoubtful rise in popularity of the Astro framework. The state of JS 2022 survey shows us that this is the most peaking technology in terms of interest in Rendering Frameworks. Let’s see what is so special about Astro and if this might be the right tool for your next project.
source: https://2022.stateofjs.com/en-US/libraries/rendering-frameworks
What is the Astro Framework
Astro is a framework that is mainly focused on building fast, content-rich websites. It's possible due to it being primarily focused on SSG (Static Site Generation) and a feature that by default, no JavaScript is being sent to the user. As we will later find out, interactivity (which implies the presence of JS) is an opt-in feature with a fine granularity of control.
Astro focuses on content-based websites that require fast loading times, great performance, and SEO optimizations. All core features are built to support this kind of audience. It's important to keep this in mind as Astro's development team explicitly notes that this tool might not be the best choice for building full-fledged web applications.
source: https://docs.astro.build/en/concepts/why-astro/
Besides that, it's highly customizable due to a rich integration ecosystem with complete UI framework agnosticism, meaning that we can build websites with the tools we know and like with very little overhead from Astro API. We can also mix and match multiple UI frameworks at the same time; for example, in case we are migrating our codebase to a different tool, we are not bound to one solution, and we can gradually progress.
What is unique about Astro
Entry level
What I like about Astro is that its entry level is very low; if you know HTML, CSS, and basic JavaScript, you will feel at home. Obviously, you will have to learn some abstractions like Astro Components. Still, the whole API was developed so that it should be easy to use regardless of your prior web development experience.
Opt-in nature
More advanced concepts of UI development are opt-in and this is important for two reasons:
- If you do not feel confident with including solutions like React or Vue, you can still build a website with all the benefits of using Astro build environment.
- If you're a seasoned developer, you can bring as much complexity as you wish, Astro is flexible, and you can extend it with more than 100 various integrations.
Speed
Astro is fast by default; we don't have to trigger some configuration or use a special feature; it's like that out of the box. It's mainly due to the server-first approach and trying to render as much as it can on the server-side rather than on client-side. Well, isn't this just a normal static site generation that we have already had for decades? Well, not exactly, at least not in the way that Astro does it. What is different from other frameworks is the concept of interactivity islands, a so-called Island Architecture; it introduces a way to achieve interactive websites while preserving great loading times by selectively applying JS only where it's needed.
One quote from the Astro documentation page that stuck in my mind is "It should be nearly impossible to build a slow website with Astro.", and I think this gives a great, general feeling about this tool.
Island Architecture
The Astro core concept of Island Architecture is a way to build our websites by breaking them down into encapsulated and independent islands of functionality. Each island can be represented by a single component, a group of components, or even a whole page; it's up to us how granular it will be.
What is great about this approach is that if we have only a small subset of interactive page elements, we don't have to wrap the whole website structure in a UI framework. We can only perform a partial hydration on that island and leave the static content, as it is, untouched.
source: https://docs.astro.build/en/concepts/islands/
Each island is rendered in isolation, which means that they have their own separate life cycle and are not blocking each other. This is great in scenarios in which we have multiple islands and start loading them in parallel; by applying proper directives, we can set a priority and decide which islands should load faster and which can be deferred.
Let's look at some examples. First we need to discover a primary building block - an Astro Component. These components are placed in files with the .astro extension.
---
const message = "Hello, Astro!";
---
<main>
<h1 class="header">{message}</h1>
</main>
<style>
.header {
color: steelblue;
}
</style>
What directly stands out is the usage of triple dash ---, which signifies the division between Component Script and Component Template. The upper script part is only evaluated during build time and is never sent to the user, which makes it a great candidate for various network or database calls, as we have a guarantee that none of it will land in a final bundle.
In the template part, we will be defining the structure of our components using a JSX-like syntax, as well as its CSS styles.
Let's spice things up and add a UI framework component; in my examples, I will be using React, but other solutions are available; as of version 2.6.0, it's Preact, Svelte, Vue, SolidJs, AlpineJs, and Lit. I'm omitting the configuration part as it's really straightforward and boils down to executing one CLI command.
Let's create a simple counter component that uses a local state to persist numeric value; we have two buttons with handlers that can either add or subtract 1 and a span showcasing the current count value.
import React, { useState } from "react";
export const Counter: React.FC = () => {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount((count) => count - 1)}>-</button>
<span>{count}</span>
<button onClick={() => setCount((count) => count + 1)}>+</button>
</div>
);
};
Next, we import it into our Astro Component and render it as a child to the <main>
tag.
---
import Counter from "./Counter";
---
<main>
<Counter />
</main>
<style>
...
</style>
If you would run this code, you might be surprised that even though the component renders properly, clicking on either the + or - buttons would not change our local state. This is because our Counter component was rendered on the server, and only its markup has been sent to the user.
In order for it to become interactive, it must undergo a hydration process, and a way to achieve that is by using a client directive. Let's take a look at possible options:
---
import { Counter } from "./Counter";
---
<main>
<Counter client:load />
<Counter client:idle />
<Counter client:visible />
<Counter client:media="(max-width: 800px )" />
<Counter client:only="react" />
</main>
- client:load- sets a high priority, load and hydrate component immediately
- client:idle- sets a medium priority, load and hydrate component as soon as page is loaded
- client:visible - sets a low priority, load and hydrate component when it enters a viewport
- client:media=* - sets a low priority, load and hydrate component when provided css media query is met
- client:only=\ - acts the same as client:load, but the server rendering part is skipped and client is solely responsible for the rendering, we need to provide a proper framework name that we are using
- client:* - starting from Astro version 2.6.0 we can define our custom directives! - https://docs.astro.build/en/reference/directives-reference/#custom-client-directives
Let’s take a look at what has changed in terms of structure after applying one of those directives.
Our component was wrapped in <astro-island>
custom element; it’s main responsibilities are loading proper JS files and starting a hydration process. This is how Astro achieves isolation on a per-island basis.
As you can see, we have fine-grained control over how our islands will behave, and the beauty of this solution is that it's just one line of code; everything is handled for us and happens under the hood. It's worth mentioning that those directives can only be applied to UI framework components; we cannot attach them to the Astro Components.
Another benefit of the island's isolated nature is that we can compose different UI framework components in a single view. I would not strongly advocate doing so, but the possibility is there, and it's always better to have more flexibility.
---
import { ReactComponent } from "./ReactComponent";
import { VueComponent } from "./VueComponent";
import { SvelteComponent } from "./SvelteComponent";
---
<main>
<ReactComponent />
<VueComponent />
<SvelteComponent />
</main>
Overall, Island Architecture seems like a great concept for certain types of websites/applications that will allow us to achieve great performance speeds with first-class developer experience. It's great that we are seeing more and more tools moving towards a server-first approach that cleverly leverages advanced UI techniques hidden behind an abstraction layer.
Hope this post gave you a basic introduction to the Astro itself and its take on Island Architecture. Be sure to subscribe to our blog for more content like this!