Published
The Composable Stack: 5 Frontend Frameworks To Consider
Are you looking at modernizing your web stack? Maybe you’re moving out of a monolithic CMS where templating is tightly coupled with an opinionated way of doing things? But navigating the world of modern front-end tooling—where it feels like there’s a new JavaScript framework every week—can be daunting.
However, amid that crowded list, some frameworks have matured over several years and been “proven” through deployment in production for real applications. And others seem to gain fast traction, building on established conventions. In this post, I’ll briefly present some of the frameworks I think you should consider, based on the traction they are getting among developers, and give a minimal example of how it looks integrated with Sanity.
Moving to a composable stack means more freedom and flexibility to solve unique business problems. You can work with more specialized tools that are awesome at what they do. In many cases, it also reduces developer operations, maintenance, and reliability complexity. However, you will need to decide on services, tools, and frameworks that are suited to solve your problems for your specific teams.
The first time I worked seriously with this type of composable stack was in 2017. I was working as a technologist at an agency, and we decided to build our new website with a headless CMS and a decoupled front end. Coming from WordPress and Craft CMS, we started the project with Contentful. But when I got access to the beta of Sanity in early 2017, there was no question: Sanity suited us much better because it was more customizable and focused on great foundational design principles.
A few months later, we launched a new website running on Next.js and Sanity.io. It wasn’t only about delivering marketing landing pages; having structured content on APIs allowed us to use and reuse our content in novel ways—and work more efficiently:
- We synced the consultant information from our CV backend into Sanity, always keeping the external-facing consultant profiles up to date
- We could manage and publish workshops and events from Sanity Studio, including sign-ups and so on. We reused the structured content to generate calendar events and email reminders.
- We documented the design system from the agency rebrand using Sanity as well.
I found this setup empowering because it was easy to go from idea to execution. We didn’t need to figure out how to fit into an opinionated system but how we wanted the different components to work and feel. That flexibility is a sign of great developer experience.
Composable front end frameworks enable you to make a library of components that you arrange into views presented to the users of your website or applications. These components can have placeholders for content/data that you can pass around the application differently.
These frameworks have some common patterns. They all:
- let you use JavaScript/TypeScript closely to a templating language to generate web pages
- don’t require a traditional server but can be deployed to static hosting or combine edge and serverless functions to serve fresh content at scale without sacrificing performance
- offer a special function or syntax that lets you fetch content from an API and use that as data in templating and to generate pages dynamically
- offer additional abstractions for handling redirects, meta-tags, image optimizations, and so on
- have active and passionate communities and an ecosystem with a lot of prior art and experience you can draw from
Most of these frameworks are abstractions on top of user interface libraries (React, Vue, Svelte). UI libraries usually provide ways to compose smaller components by passing data and state between them within the constraints of a web browser and the document object model.
This is very handy, especially when building dynamic web applications (for example, a shopping cart). Although you can deploy a pure React or Vue application to the web as a “Single Page Application” (SPA), most experts don’t advise this if performance and SEO are important to you. Building great accessibility in SPAs is typically harder than leveraging how browsers work out of the box.
Next.js is an open-source framework for creating web applications with React. It has been developed since 2016 and is actively maintained by Vercel (who provides serverless web hosting). It has gained a lot of traction and is the most popular React-based framework right now. Next.js has different ways of building your web applications’ routes (URL structure) using file placement and naming conventions. It also has tooling and abstractions for defining serverless functions, optimizing images, controlling cache behavior, etc. Being widely adopted, you’ll find a large community and many resources for building with Next.js.
Recently, Next.js shipped a new way of defining routes (often called “the app directory”), leveraging React Server Components to decentralize how you can fetch data into individual components. While this method is still in beta and active development, it’s worth looking into if you’re starting out with Next.js. Below is an example of how it can look to fetch a list of 10 blog posts from Sanity and populate a simple list in the front end.
// src/app/page.tsx
import { client } from "./lib/sanity";
function getPosts() {
// Use GROQ to fetch content from the Sanity Content Lake
return client.fetch(`*[_type == "post"][0...10]`);
}
export default async function Home() {
const posts = await getPosts();
return (
<main>
<h2>Latest posts</h2>
<ul>
{posts.length > 0 && posts.map((post) => (
<li key={post._id}>
<a href={`/posts/${post.slug.current}`}>
<a>{post.title}</a>
</a>
</li>
))}
</ul>
</main>
);
}
If you want to give this combo a try, check out our:
- Next.js template that lets you deploy a complete website in a couple of minutes
- guide on how to build a blog with Next.js
- next-sanity tooling that lets you embed Sanity Studio into Next.js and set up real-time content previews
Nuxt.js describes itself as “The Intuitive Vue Framework.” It‘s similar to Next.js in providing tooling to generate websites using Vue.js. Nuxt has been developed since 2016 and is today backed by NuxtLabs. You’ll find that Nuxt has features similar to Next.js but with Vue conventions instead of React. In my experience, Vue often appeals to more “design-oriented” developers, who enjoy the “single file components” pattern, where logic, markup, and styling are found in the same file.
This example assumes that you use the Sanity module for Nuxt, which exposes functions (like useSanityQuery
) that you can use in your components.
// app.vue
<script setup>
const query = groq`*[_type == "post"][0..10]`;
const { data, refresh } = useSanityQuery(query);
</script>
<template>
<main>
<h2>Latest posts</h2>
<ul>
<li v-for="post in data" :key="post._id">
<router-link :to="`/post/${post.slug.current}`">
{{ post.title }}
</router-link>
</li>
</ul>
</main>
</template>
- Try our clean Nuxt template
- Read Cloudflare's guide on how to create a blog with Nuxt.js and Sanity.io
- The Sanity module for Nuxt.js makes it easier to query content idiomatically
Remix is a full-stack web framework that’s developed by the maintainers of React Router, which the React community has widely adopted over many years. Remix was open-sourced in 2021 and acquired by Shopify in 2022, keeping on the same maintainers. Shopify leverages Remix to power Hydrogen 2, which lets you build custom performant and up-to-date custom storefronts. Remix focuses on being close to the Web API, with just enough abstraction to deal with state across client and server environments. My impression from conversations on Twitter and other places is that Remix is enjoyed by developers who want a less opinionated framework.
Below is an example of pulling in 10 blog posts on a Remix route and rendering a list in the front end. Here, we’re using a configured @sanity/client
in a separate file. Remix will automatically run the loader function and make the return data accessible through the useLoaderData()
hook.
// ./app/routes/index.tsx
import { useLoaderData } from "@remix-run/react";
import { client } from "~/lib/sanity";
export const loader = async () => {
const posts = await client.fetch(`*[_type == "post"][0...10]`);
return { posts };
};
export default function Index() {
const { posts } = useLoaderData();
return (<main>
<h1>Blog</h1>
<h2>Latest posts</h2>
<ul>
{
posts.length > 0 && posts.map((post) => (
<li key={post._id}>
<a href={post.slug.current}>{post.title}</a>
</li>
))
}
</ul>
</main>);
}
- Try out the Clean Remix + Sanity app to quickly get up and running
- Read the fundamentals of building with Sanity and Remix, as well as making live preview
Svelte was developed internally by Rich Harris when he worked for New York Times as a way to build dynamic and interactive web experiences without shipping too much JavaScript. SvelteKit provides the tooling and abstractions you need to build a performant web application using Svelte for your templating language. SvelteKit is similar to Vue because the Single File Component pattern is common. Fans of Svelte find that they can realize ideas fast and that they like how Svelte deals with state management.
Contrary to the other frameworks, you must split the data-fetching part from the templating part with Svelte. The logic is to make a .js/ts
file with a corresponding file name as your template file. The data that you return from the JavaScript file will be accessible in the variable that you export in the template file’s script tags, following Svelte conventions.
// src/routes/+page.ts
import { client } from '$lib/sanity';
export async function load() {
const posts = (await client.fetch(`*[_type == "post"][0...10]`)) || [];
return {
posts
};
}
<!-- src/routes/+page.svelte -->
<script>
// Automagically picks up the data returned from +page.ts
export let data;
</script>
<h1>My Blog</h1>
<h2>Latest Posts</h2>
<ul>
{#each data.posts as post}
<li><a href="/posts/{post.slug}">{post.title}</a></li>
{/each}
</ul>
Try out the Clean SvelteKit + Sanity app to quickly get up and running.
Astro is one of the newest website frameworks on the scene and has steadily grown since it was published in 2022. Astro is built for “content sites” and tries to make building light and performant sites easy by generating static files and letting you opt-in to client-side JavaScript where needed. Another feature of Astro is that you can choose mix-and-match template languages (React, Preact, Svelte, Vue, SolidJS, AlpineJS, and Lit). Using the .astro
file format, you can add business logic and data fetching in a “Frontmatter” convention known from Markdown-based Static Site Generators like Jekyll.
The example below shows how we’re running JavaScript between the three dashes that fetch the content and assign it to a variable (posts
). This variable is available in the templating part, where we use JSX as our templating language in this case.
---
// src/pages/index.astro
import { useSanityClient } from "astro-sanity";
const posts = await useSanityClient().fetch(`*[_type == "post"][0...10]`);
---
<html lang="en">
<body>
<h1>Blog</h1>
<h2>Latest posts</h2>
<ul>
{
posts.length > 0 && posts.map((post) => (
<li key={post._id}>
<a href={post.slug.current}>{post.title}</a>
</li>
))
}
</ul>
</body>
</html>
Try out the Clean Astro + Sanity app to quickly get up and running.
We developers tend to be passionate about our tools, and opinions on the “best” solutions to try may vary wildly depending on who you ask. So it’s important to state upfront that the best tool is the tool that is right for you and your team. Obviously, all frameworks come with strengths and weaknesses. Only you can factor in all of the nuances of your particular situation, what your team is used to, and any other preferences you might have to find the best front-end framework for your needs. Good luck!