Paul is a Senior Software Engineer, Independent Developer Advocate and Technical Writer. More from Paul can be found on his site, paulie.dev.
Read more from Paul Scanlon
The thought of migrating away from a React-based framework might seem a little scary at first, but if you’re intrigued about how that might work, read on.
I recently made the decision to migrate my site, paulie.dev, away from a React-based framework to Astro, a web framework known for its “islands architecture.” Naturally, in my old site, everything was React, so my first thought was: this is gonna be a lot of work. But it wasn’t!
During my development, I was able to lift-and-shift a lot of the “React-specific” components and drop them straight into my new Astro site. In this post, I’ll explain an approach that might work for you, and if you’re interested in incrementally adopting a no/low JavaScript approach to building websites, I think you’ll like Astro!
Here’s a sample site and GitHub repository that I’ll be referencing throughout this post that demonstrates how Astro can be used alongside a sprinkling of React.
But first…
Astro builds fast content sites, powerful web applications, dynamic server APIs, and everything in-between.
…and they’re not lying. Astro, like a few other frameworks, uses a fresh approach to building websites called “islands architecture”.
What this means for the likes of you and me is: Astro is predominantly a framework-agnostic static site generator, but can quite easily be configured to work with a number of UI libraries or frameworks. It can also work with various cloud providers, to support client-side requests using Serverless or Edge functions, server-side rendering, or both.
Astro by default will ship zero client-side JavaScript, but it’s quite likely during a migration from a React-based framework that you’ll need a bit of React here and there. React is great, but is it required on every page of your website, or is it only needed in a few “islands” around your site?
I’ll now explain how you might tackle a website migration, but rather than having to refactor absolutely everything to work in a world without React, you can keep React but only use it in pages/components where it’s needed. And more than that, using Astro’s client directives to control when the JavaScript required by those components should be loaded by the browser.
To get started with Astro, follow the Start your first project guide from the Astro docs, which will explain a little more about the CLI wizard.
I generally start by selecting the “Empty” option from the CLI prompt. It’s just my preference, but I find it easier to see the woods for the trees when I’m starting with as few files as possible. It also highlights just how little there is to an Astro site. A single config file and a single dependency — very nice indeed!
However, one thing you might need to change right away is the “output” mode in astro.config.mjs
.
By default, Astro is static, but in my sample site, I have a mix of static and server-side rendered pages. To support both, I changed the output mode to “hybrid” and added the appropriate server-side runtime adapter. My sample site is deployed to Netlify, so I’ll be using the Astro and Netlify adapter.
1
2
3
4
5
6
7
8
|
import
{
defineConfig
}
from
'astro/config';
import
netlify
from
'@astrojs/netlify/functions';
export
default
defineConfig({
output:
'hybrid',
adapter:
netlify(),
});
|
You can read more about hybrid rendering in the Astro docs: Converting a static site to hybrid rendering.
Astro has its own file extension, .astro
. Astro files look like the love child of MDX and JSX, where you have “frontmatter-like” syntax at the top of the file and HTML-like syntax lower down.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
---
import
thing
from
'./thing'
const
{
title
}
=
Astro
.
props
const
request
=
await
fetch(
'https://dummyjson.com/products?limit=10');
const
data
=
await
request
.
json();
---
<
div
>
<
h1
>{
title}
</
h1
>
<
Thing
/
>
<
ul
>
{
data
.
map((
item)
=
>
{
return
<
li
>{
item
.
name}
</
li
>
})
}
</
ul
>
</
div
>
|
The “frontmatter-like” part of the file can be used to declare imports, access props and perform server-side requests; it looks a little unusual to begin with, but it’ll soon start to feel familiar once you get going.
The method used to render a page is usually determined by what the page is for, where the data comes from, how often the data changes and what components are used on the page. Below is a breakdown of each page from my sample site, together with a rationale about which page rendering method I’ve used and if React is required.
The home page of my sample site doesn’t need React, because it doesn’t have content that changes frequently. As such, I’ve opted to make this page static — meaning, the final output is only the HTML and CSS required by the browser to render the page… and it loads super fast!
1
2
3
4
5
6
7
8
|
---
import
Main
from
'../layouts/main.astro';
---
<
Main
title=
'Home'
>
<
h1
>
Home
<
/h1>
...
</Main
>
|
You can see the src
file for this page in the repository here: src/pages/index.astro
The product page of my sample site also doesn’t need React, since the only job for this page is to fetch product data and display it on the page (no interactivity or state management is required). As such, I’ve opted out of using prerender
, which means this page will be server-side rendered. The page makes a server-side request to an API to fetch products before, once again, the final output is only the HTML and CSS required by the browser to render the page. The page load speed is still fast, but if the API is slow to respond then it may not be quite as fast as a static page.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
---
export
const
prerender
=
false;
import
Main
from
'../layouts/main.astro';
const
request
=
await
fetch(
'https://dummyjson.com/products?limit=10');
const
data
=
await
request
.
json();
---
<
Main
title=
'Products'
>
<
h1
>
Products
</
h1
>
<
ul
>
{
data
.
products
.
map((
product)
=
>
{
const
{
title,
description,
thumbnail
}
=
product;
return
(
<
li
>
<
img
src={
thumbnail}
alt={
title}
/>
<strong>{title}</strong
>
<
p
>{
description}
</
p
>
</
li
>
);
})
}
<
/ul>
</Main
>
|
You can see the src
file for this page in the repository here: src/pages/products.astro
The contact page of my sample site uses a component that does require React, but rather than refactor the component to work in a world without React, I can leave it as it is and simply lift-and-shift it from my old React-based framework site, and drop it straight into my new Astro site — Lovely stuff!
1
2
3
4
5
6
7
8
9
10
|
---
import
Main
from
'../layouts/main.astro';
import
ContactForm
from
'../components/contact-form';
---
<
Main
title=
'Contact'
>
<
h1
>
Contact
</
h1
>
<
ContactForm
client:
only=”
react”
/>
</Main
>
|
You can see the src
file for this page in the repository here: src/pages/contact.astro
The src
for the contact form itself, which uses react-hook-form, can be found here: src/components/contact-form.jsx.
This lift-and-shift approach is made possible because of Astro’s integrations. By installing and adding the React integration to the config, Astro (Vite) will know how to handle any files that use React, and more to the point, will only include React in the page that needs it. The result is that only the Contact Page sends the additional JavaScript required for React to work in the browser. All other pages remain as they were, with zero client-side JavaScript!
You can see the difference by visiting the sample site and looking at the Network tab in your developer tools. The Home page (without React) is ~6kb, the Contact page (with React) is ~61kb!
And here’s a snippet of my updated astro.config.mjs
file with the React integration added.
1
2
3
4
5
6
7
8
9
10
|
import
{
defineConfig
}
from
'astro/config';
import
netlify
from
'@astrojs/netlify/functions';
+ import react from "@astrojs/react";
export default defineConfig({
output: 'hybrid',
adapter: netlify(),
+ integrations: [react()]
})
|
You can read more about integrations in the Astro docs here: @astro/react.
This is just a hypothetical migration of course, and just one scenario where React was being used for something quite trivial but, as I mentioned, I used a similar approach when migrating my site paulie.dev to Astro and it worked out beautifully.
I think this approach of incrementally opting in or out of React offers a nice middle ground, where it’ll allow you to tackle a migration without getting into the weeds and refactoring every component. Perhaps later down the line, you might want to remove React completely, but that’s a job for another day!
I hope I’ve been able to demonstrate how you can use Astro to incrementally adopt a “reduced JavaScript” approach to building websites; and for what it’s worth, it was kinda cool developing my new site without my “React brain”. It reminded me of a simpler time in web development and I rather enjoyed the walk down memory lane.