|

React SPA in Astro with TanStack Router

React SPA in Astro with TanStack Router
Astro + React + TanStack Router

Astro is usually billed as a web framework for content-driven and/or static websites, shipping zero bytes of JavaScript by default. On the other end of the spectrum, there are solutions like Remix and Next.js for highly dynamic and interactive server rendered web applications.

But Astro can do much more than just static sites. If you need to have any amount of client-side interactivity, the reality is that you’re going to need some JavaScript. Astro offers a great middle ground for this with its Islands architecture. This feature allows you to render HTML pages on the server (or at build time) and sprinkle in “islands” of interactivity where needed, with or without the first render pass on the server.

Another approach is to embed a full blown client rendered Single Page Application (SPA) within a sub route tree of your Astro site for a dashboard, admin panel, or some other highly interactive feature. This is where TanStack Router comes in, probably my favorite new React library. React Router is another option, and it (and by extension, Remix) is great and I’ve used it before, but the type-safety and search parameters handling in TanStack Router is just… *chef’s kiss*.

Setup

React

Getting started with TanStack Router in Astro is pretty straightforward. First, you’ll need to add the react integration to your Astro project. I prefer pnpm, but use whatever package manager you like:

pnpm astro add react

This will automatically install @astrojs/react, react, and react-dom as well as add the necessary configuration to your astro.config.mjs file. In this file, you’ll want to ensure that the output is set to hybrid, enabling dynamic routes for the SPA:

// astro.config.mjs
import react from "@astrojs/react";
import { defineConfig } from "astro/config";

// https://astro.build/config
export default defineConfig({
  output: "hybrid",
  integrations: [react()],
  // ... other config
});

TanStack Router

TanStack Router supports file-based routing as well code based routing. Code based routing is more flexible, but file-based routing is generally preferred due to convention and ease of use. To enable file-based route generation, there are two options: @tanstack/router-vite-plugin and @tanstack/router-cli. Since Astro is built on Vite, we can use the Vite plugin:

pnpm add @tanstack/react-router @tanstack/router-vite-plugin
// astro.config.mjs
import { TanStackRouterVite } from "@tanstack/router-vite-plugin";
import { defineConfig } from "astro/config";

// https://astro.build/config
export default defineConfig({
  // ... other config
  vite: {
    plugins: [TanStackRouterVite()],
  },
});

Next, create a tsr.config.json file in the root of your project so that TanStack Router knows where to look for your routes. I’ll be putting everything SPA related in the src/dashboard directory:

// tsr.config.json
{
  "routesDirectory": "./src/dashboard/routes",
  "generatedRouteTree": "./src/dashboard/routeTree.gen.ts",
  "routeFileIgnorePrefix": "-",
  "quoteStyle": "single"
}

Follow the TanStack Router Quick Start and file-based routing guide on how to create your routes. One thing not to forget is to set the basepath in the createRouter call. Base path /dashboard corresponds to src/pages/dashboard in the Astro project.

const router = createRouter({
  routeTree,
  basepath: "/dashboard",
});

Astro

Rendering a SPA in Astro is the same as rendering any other React component, except that it needs to handle all the requests in the /dashboard route tree. Create a “catch-all” route src/pages/dashboard/[...app].astro, but you can name the file whatever you like:

---
// src/pages/dashboard/[...app].astro
import { App } from "~/dashboard/app";

export const prerender = false;
---

<App client:only="react" />

Two things to note here. First, the prerender export is set to false to prevent Astro from trying to generate static pages at build time. Second, the client:only directive needs to be set to react so Astro knows what framework to use for client-side rendering.

Conclusion

That should be it! You can now build out the react app just like you would for any app built with TanStack Router. If you want to see a working project, check out the example source code, deployed to Cloudflare Pages.

Contact me