Nowadays, we are all used to seeing new frameworks emerging with the promise of revolutionizing the way we build for the web. Nevertheless, we most often remain tied to a specific UI library (React, Vue, Svelte, etc.) to define our components and build the experiences for the users.
This time, the story is different! Using the power of Vite.js, we received Astro: an agnostic framework that can work as SSG (static site generator) and provide SSR (server-side rendering).
Using the Astro plugin system, we can build and enhance our websites the way that we want, and even combine different UI libraries into a single project.
In this post, weโll learn about Astro islands and how they work under the hood to provide this functionality.
Jump ahead:
When you look at the definition on their official website, you will find:
Astro is an all-in-one web framework for building fast, content-focused websites.
Recently, they released the first stable release, marking the framework as ready for production, a huge milestone for emerging frameworks. The web community has reacted very positively, and every day we are seeing more and more projects shipping with Astro frontends.
Astro has many cool features to make the developer and user experience great. I recommend checking out this other LogRocket article for a more general overview of Astro.
In my personal opinion, the key differentiator between Astro and other frameworks relies on its architecture: islands architecture. This concept was first described in 2019 by Katie Sylor-Miller and expanded on later in this post by the Preact creator Jason Miller.
The term โAstro islandโ refers to an interactive UI component on an otherwise static page of HTML. Multiple islands can exist on a page, and an island always renders in isolation, which means that each island can use any UI framework, or even just plain Astro code, alongside the other islands on a page.
Something quite important to highlight is that Astro generates every website with zero client-side JavaScript by default. Every time we render an island on a page, Astro automatically renders it to HTML ahead of time and then strips out all the JavaScript. This keeps every site fast by removing all unused JavaScript from the page.
Letโs take the implementation of a Counter
component made in React as an example. When rendering the first time, it will show a button with the text โCounter: 0
โ, and every time the user clicks it, the counter will increase by 1
.
// src/components/Counter.tsx import { useState } from 'react'; const Counter = () => { const [count, setCounter] = useState(0); return ( <button onClick={() => setCounter((number) => number + 1)}> Counter: {count} </button> ); }; export default Counter;
Then, letโs render this component in Astro.
To use React components in your Astro project, you have to add the @astrojs/react
integration into your project.
// src/pages/index.astro --- import Counter from '../components/Counter'; --- <Counter />
This code will render the button, but because JavaScript is removed by Astro at build time, the user wonโt be able to increment the counter. When we want to make our app interactive, we have to be explicit by using client directives (we are going to cover them later).
This process is called partial or selective hydration. Essentially, it means shipping any framework code or runtime that is needed to support a componentโs dynamic runtime requirements. Things like state changes and interactivity are prime examples.
// src/pages/index.astro --- import Counter from '../components/Counter.jsx'; -- <Counter client:load />
One of the benefits of Astro islands, besides their heavy focus on making your app as light as it can be, is that every island is loaded in parallel. You can even specify the loading strategy for each island individually using client directives! This means we are in total control of how and when assets are loaded for the client and provide the best experience that we can.
As I mentioned at the beginning of this post, the Astro team did not create islands architecture. The same technique is implemented in many frameworks, and has been shipped as an individual library.
A great example is is-land
(from the 11ty
team), which provides additional conditions when hydrating the component (a.k.a., client directives) such as:
on:interaction
on:save-data
It also allows you to specify a fallback when the component has not been hydrated yet:
<is-land on:interaction> <form> <button type="button">Hydrate the island</button> </form> <p>This content is before hydration.</p> <template data-island="replace"> <vanilla-web-component>My component content after hydration</vanilla-web-component> </template> </is-land>
Despite some differences in syntax among frameworks, the main idea is that each framework ultimately implements partial hydration. The way that Astro composes the UI ensures that user demands always define the strategy for hydration, which is optional in other island frameworks such as is-land
.
If youโd like to learn more about islands architecture, I found the GitHub awesome-islands
to be a great resource that organizes a lot of content related to islands architecture.
A directive is a component attribute that tells Astro how your component should be rendered. At the moment of writing this article, Astro supports a total of five different client directives. This number may change as the framework adds new features.
Assuming we want to render our component, called MyComponent
, depending on the client directive that we use, we can modify the way the user can interact with it:
<MyComponent client:load/>
: Hydrates the component JavaScript immediately on page load<MyComponent client:idle/>
: Hydrates the component JavaScript once the page is done with its initial load and the requestIdleCallback
event has fired<MyComponent client:visible/>
: Hydrates the component JavaScript once the component has entered the userโs viewport. This uses an IntersectionObserver
internally to keep track of visibility<MyComponent client:media={string}/>
: Hydrates the component JavaScript once a certain CSS media query is met<MyComponent client:only={string}/>
: Skips HTML server rendering and renders only on the client. The component will be skipped at build time, which makes this useful for components that are entirely dependent on client-side APIsTo illustrate the power of client directives, I created a small Astro project where I render the same Counter
component that I showed above, using different client directives for anyone to try them all in one place. Feel free to take a look at the live application on Netlify.
Below is the whole code for the page in the above screenshot. The content of the page is basically the same Counter
component rendered a total of six times: first without specifying any client directive (the component is not interactive at all), and then the rest using all the different client directives that we covered above.
// src/pages/index.astro --- import Layout from '../layouts/Layout.astro'; import Counter from '../components/Counter'; --- <Layout title="Welcome to Astro"> <main> <h1>Welcome to <span class="text-gradient">Astro</span></h1> <h2><pre>no directive</pre></h2> <p class="instructions"> <code>No JS, no interactive</code> <Counter /> </p> <h2><pre>client:load</pre></h2> <p class="instructions"> <code>Loads JS as soon as possible</code> <Counter client:load /> </p> <h2><pre>client:idle</pre></h2> <p class="instructions"> <code>Loads JS when rendering is over</code> <Counter client:idle /> </p> <h2><pre>client:visible</pre></h2> <p class="instructions"> <code>Loads JS when the button is visible to the user</code> <Counter client:visible /> </p> <h2><pre>client:media</pre></h2> <p class="instructions"> <code>Loads JS when the media query (min-width: 680px) is valid</code> <Counter client:media="(min-width: 680px)" /> </p> <h2><pre>client:only</pre></h2> <p class="instructions"> <code>Loads JS only in client (No SSR)</code> <Counter client:only="react" /> </p> </main> </Layout>
The whole source code can be found in this GitHub repository. I highly recommend forking the project and running the project locally to fully understand how client directives can modify your applicationโs behavior.
I see Astro as a new fresh framework for building a website with the power to ship super-light websites using zero JavaScript code. It makes us be more aware of when we do need JavaScript in this heavy ecosystem, and when we can ship fewer KB to our clients.
Another great advantage of Astro is that itโs UI-agnostic, which means you can Bring Your Own UI Framework (BYOF)! React, Preact, Solid, Svelte, Vue, and Lit are all officially supported in Astro. You can even mix and match different frameworks on the same page, making future migrations easier and preventing project lock-in to a single framework.
Thanks for reading, and letโs keep building stuff together! ๐ทโโ๏ธ
Thereโs no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, youโll need more visibility to ensure your users donโt run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.
LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your appโs performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently โ start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocketโs Content Advisory Board. Youโll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
One Reply to "Understanding Astro islands architecture"
Can you shade some light on how dynamic ssr works in astro?