My love for Astro and the web platform is welldocumented. I’ve contributed to the docs, I’ve built some integrations and themes. I recently rediscovered the joy of building with Astro when I made a demo integration with SuperTokens. The ultimate goal for it was to become a part of the create-supertokens-app CLI (published here).
We’ll start with a brief introduction to Astro and the parts relevant to this story and then move on to the integration (including the not-so-nice parts).
So, let’s have a look at what makes Astro an absolute joy to build web stuff with!
It’s just the web platform - with a nicer API
Astro doesn’t reinvent wheels. It gives you a nice and consistent API (or a set of APIs, if you want it pedantic) to work with. You can import anything from plain HTML (via .astro components) to CSS, components, and scripts… if you can imagine it, Astro can probably import it.
Everything is static HTML unless you tell it otherwise (more on that later). You get to work with the basic building blocks of the web - HTML, CSS, and JS. But here’s the catch - you can make it as complex or as simple as you desire (or your project requires). Want to write TypeScript? It’s a CLI command away. Need a modern UI library or framework (don’t get me started) for writing components? Take your pick; you can use whatever you like. Yes, Astro supports almost all of them, which is important for integrating SuperTokens.
Let’s have a look at an Astro component:
---// This is the frontmatter section where you can define component props and import other components or modules. This is server-side rendered.const { name } = Astro.props;---<!-- style tag are scoped to the component, unless you tell them otherwise --><style> .greeting { font-family: Arial, sans-serif; color: #333; background-color: #f0f0f0; padding: 20px; border-radius: 5px; text-align: center; }</style><div class="greeting"> <h1>Hello, {name}!</h1> <p>Welcome to your first Astro component.</p></div><script> // This is an optional script section where you can add interactivity to your component. document.querySelector('.greeting').addEventListener('click', () => { alert('You clicked the greeting!'); });</script>
Remember that static-unless-told-otherwise bit from above? The frontmatter section above illustrates that well - basically, whatever is between those dashes gets executed on the server. You can use the variables defined there inside the HTML. <script> tags are sort of the exception here - any client-side code there still runs on the client.
Of course, if you prefer writing CSS in separate files, you can easily use a style tag to import the stylesheet or even use an import statement in the frontmatter to access those styles inside the component.
So, in a sense, Astro is just a nicer layer over the standard web platform APIs. But it’s also much, much more. If you want it to be.
Components. Yes, all of them.
Whether you’re a fan of React, Vue, Svelte, Solid - Astro’s got you. A quick npx astro add my-framework-of-choice is all you need. Let’s take React as an example.
First, you need to add it to an existing Astro project:
npx astro add react
Once you’ve done this, you can create React components in your Astro project and use them via the import statement wherever you like (yes, even inside .astro components).
To slightly tweak our example above - let’s first move the greeting markup to a React component:
// src/components/Greeting.tsximport React from "react";export const Greeting = ({ name }: { name: string }) => { return ( <div className="greeting" onClick={() => alert("You clicked the greeting!")} > <h1>Hello, {name}!</h1> <p>Welcome to your first Astro component using React.</p> </div> );};
With the change above, our .astro component ends up looking something like this:
---// Import the React componentimport { Greeting } from '../components/Greeting';// Define the props for the Astro componentconst { name } = Astro.props;---<style> .greeting { font-family: Arial, sans-serif; color: #333; background-color: #f0f0f0; padding: 20px; border-radius: 5px; text-align: center; }</style><!-- Use the React component inside the Astro component --><Greeting name={name} />
But here’s our first surprise: once we click on the greeting div, we no longer see the alert. This is a feature. As you might remember from above, everything in Astro is static unless we tell it otherwise. That means Astro renders the component on the server and doesn’t hydrate any JavaScript along with the component.
That can be useful, but we still want the alert, right? Astro has a concept called directives for this and an architectural paradigm called islands (too long to get into here, here’s a source for a deeper dive). In practice, if we want the component to produce an alert when we click it, we need to add a client:load (or similar depending on your use case) directive to the React component:
---// Import the React componentimport { Greeting } from '../components/Greeting';// Define the props for the Astro componentconst { name } = Astro.props;---<style> .greeting { font-family: Arial, sans-serif; color: #333; background-color: #f0f0f0; padding: 20px; border-radius: 5px; text-align: center; }</style><!-- Use the React component inside the Astro component, interactive --><Greeting name={name} client:load />
SSG and SSR
By default, an Astro project compiles to a static site. However, to integrate an auth solution like SuperTokens, we need a server. Luckily, Astro comes with an SSR mode, which allows us to do server-side processing, too.
With the SSR mode in place, we have all the Astro-specific pieces we need to integrate SuperTokens with Astro.
Integrating SuperTokens’ prebuilt UI in Astro
Generally speaking, SuperTokens has two ways of integrating: using the pre-built UI or going the custom route (i.e., using functions and your own UI). The pre-built UI further forks down to two options: the React SDK and the universal pre-built UI. The universal pre-built UI is based on the React one, but it’s compiled down to a standalone JS library that can be imported into just about anything.
Whenever I write an integration for the create-supertokens-app CLI, I usually pick the universal pre-built UI when working with a non-react frameworks. But remember the bit about Astro working with all components out there? Yep, that means we can use SuperTokens’ regular React SDK to make it work with Astro.
The necessary pieces for a React SDK integration are:
A home component to show some session info once we’re logged in
Let’s have a look at how easy it is to fit that in with Astro.
The good
Astro can be a React framework. And on top of that, Astro can do SSR and API routes without making your head spin. Given the list of requirements above, I just went ahead and copied some files from an existing Remix integration:
superTokensHelpers.ts - a list of utility functions that help with common tasks. I usually copy and paste this file in most integrations I do, React or otherwise.
Root.tsx - a wrapper component, which helps with all things auth on the frontend (specifically, for React components).
Home.tsx and Auth.tsx - the formed renders the Home screen (authenticated state) and gives you some session info if you’re authenticated. The latter renders the authentication screen.
tryRefreshClientComponent.ts- granted, it’s named a bit unconventionally, but it plays a key role in the whole setup - it tries to refresh an existing auth session.
All of these, together with the config worked quite literally out of the box (and I copy-pasted most of it).
The server was a bit of a different story, however 😅
The not-so-good
Now, it wasn’t bad per se. It’s just that multi-level catch-all routes don’t seem to be a thing in Astro (yet). For example, I couldn’t find a way to properly define a * route in non-file-based routing in Astro.
Initially, I tried using middleware, but that went nowhere. It led to weird responses, where the middleware caught the route being triggered every time, but that didn’t reflect in the response. Getting a valid response and token from the auth core and returning a 404 to the client isn’t what I’d call working here.
Luckily, there is a hacky but easy fix for this - define the multi-level catch-all routes the long way. A picture is a thousand words, so this will certainly explain it better:
In Astro, the pages directory is where pages (routes) live. Due to how I had SuperTokens configured, it expected to have full ownerships of the /auth/** route for frontend and /api/auth/** for backend calls. However, it also expects to be able to find a route at any of those levels.
Ultimately, it worked fine, but it took some hacking to make it work. Caveat - I may have misunderstood something, so feel free to ping me on Twitter with what I did wrong. Also, PRs open if that’s more your jam 😉
A possible alternative
Doing SSR with Astro requires something called an adapter. The default is node. Now, in theory, you could just add those routes to the node context. Or, as a quick Google search showed me, you can simply change the adapter to fastify to get more routing flexibility. I’ll likely try that next!
Takeaways
Astro can be whatever you want it to be - even a react framework. In a talk, I once called it a meta-metaframework - in a sense, you can build a framework of your own inside Astro. And you can pick products like SuperTokens off the shelf and integrate them easily.
So, I’ll leave you with this: Use the standards and the platform where they make sense; don’t fight them. Webdev can be simpler than we’re used to.
Liked this article? Subscribe to the newsletter and get notified of
new articles.