How to Use Astro with a Sprinkling of React
Here's how to use Astro to incrementally adopt a "reduced JavaScript" approach to building websites. No more refactoring every component!
Oct 24th, 2023 7:00am by
Image via Unsplash.
- 🚀 Sample: https://the-new-stack-astro-react-sprinkling.netlify.app/
- ⚙️ Repo: https://github.com/PaulieScanlon/the-new-stack-astro-react-sprinkling
What Is Astro?
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.
Using Astro and React
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.Getting Started with Astro
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.
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify/functions';
export default defineConfig({
output: 'hybrid',
adapter: netlify(),
});
How to Create a Page Using Astro
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.
---
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>
Page Rendering Methods in Astro
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
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!
---
import Main from '../layouts/main.astro';
---
<Main title='Home'>
<h1>Home</h1>
...
</Main>
src file for this page in the repository here: src/pages/index.astro
The Product Page
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 usingprerender, 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.
---
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>
src file for this page in the repository here: src/pages/products.astro
The Contact Page
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!
---
import Main from '../layouts/main.astro';
import ContactForm from '../components/contact-form';
---
<Main title='Contact'>
<h1>Contact</h1>
<ContactForm client:only=”react”/>
</Main>
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.
Lift-and-Shift React
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.
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()]
})
Final Thoughts
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.
YOUTUBE.COM/THENEWSTACK
Tech moves fast, don't miss an episode. Subscribe to our YouTube
channel to stream all our podcasts, interviews, demos, and more.