The Composable Stack: 5 Frontend Frameworks To Consider
Want to build with new technologies but are unsure where to start? This guide introduces 5 promising front end frameworks and how they integrate with Sanity.

Knut Melvær
Principal Developer Marketing Manager
Published:

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
However, amid that crowded list, some frameworks have matured over several years and been “proven” through
Why a composable stack can be right for you
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
A few months later, we launched a new website running on
- 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
from the agency rebrand using Sanity as well.design system
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.
What are the common features of composable front end frameworks?
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
, meta-tags, image optimizations, and so onredirects - 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,
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
Next.js (React): A popular and mature choice
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.
Minimal example
Recently, Next.js shipped a new way of defining routes (often called “the app directory”), leveraging
// 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>
);
} Learn more
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 (Vue): Fully featured and more than capable
Minimal example
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> Learn more
- 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 (React): Powerful while getting out of your way
Minimal example
Below is an example of pulling in 10 blog posts on a Remix route and @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>);
} Learn more
- 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
SvelteKit (Svelte): Ship fast, with less JavaScript penalty
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.
Minimal example
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> Learn more
Try out the Clean SvelteKit + Sanity app to quickly get up and running.
Astro: Bring your favorite template language
.astro file format, you can add business logic and data fetching in a “Frontmatter” convention known from
Minimal example
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
---
// 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> Learn more
Try out the Clean Astro + Sanity app to quickly get up and running.
The right tools are the tools that work for you
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!