The complete architecture story: How a fitness coaching website with meal plans, coaching packages, and e-commerce runs entirely on Sanity as the backend—no Express, no Prisma, no database migrations. Just TypeScript schemas and GROQ queries.
// Fetch meal plan with client data and all fields
export async function getMealPlanRequest(id: string) {
const query = `
*[_type == "mealPlanRequest" && _id == $id][0] {
_id,
client->{ name, email },
goal,
activityLevel,
dietaryType,
allergies,
favoriteFoods,
targetCalories,
macros { protein, carbs, fats },
days[]{ day, breakfast, lunch, dinner, snack },
status
}
`
return sanityClient.fetch(query, { id })
}
// Webhook handler for email automation
export async function POST(req: Request) {
const { _id, status, client } = await req.json()
if (status === 'delivered') {
await resend.emails.send({
to: client.email,
subject: 'Your Meal Plan is Ready!',
react: MealPlanEmail({ name: client.name })
})
}
return Response.json({ success: true })
}When I started building Bring The Shreds (www.bringtheshreds.com), I had a familiar decision to make: what's the backend? The site needed meal plan questionnaires, coaching package purchases, a blog, and an admin interface for the coach to manage everything. The traditional answer would be Express + Prisma + PostgreSQL. I chose something different.
Bring The Shreds is a fitness coaching business. The core features needed:
1. Multi-step meal plan questionnaire (goal, activity level, dietary preferences, allergies, favorite foods) 2. Coaching packages with Stripe checkout 3. Blog for fitness content and SEO 4. Admin dashboard for the coach to manage clients and create meal plans 5. Email automation when meal plans are delivered
The constraint: I was the only developer, and maintenance needed to be minimal. The coach isn't technical—she needed an interface she could use without calling me.
I evaluated four headless CMS options: Contentful, Storyblok, Strapi, and Sanity.
Industry standard, great documentation, but the content modeling felt rigid. The pricing scales quickly once you exceed the free tier. The admin UI is polished but generic—it doesn't feel like YOUR app.
Great visual editor for marketing sites. But Bring The Shreds isn't primarily a marketing site—it's a data-driven application. Storyblok's strength (visual editing) wasn't relevant for meal plan management.
Self-hosted, which means I'd need to manage a server. The whole point was reducing infrastructure. Strapi is powerful but felt like trading one backend for another.
Schema-as-code (TypeScript), real-time collaboration, customizable Studio, generous free tier, and GROQ—a query language that felt like writing code, not wrestling with an API. This was the one.
The architecture is simple: Next.js frontend + Sanity backend. No Express server. No Prisma. No database migrations. The entire data layer is defined in TypeScript schema files that deploy to Sanity.
When a client fills out the meal plan questionnaire, the form submits directly to Sanity via the client SDK. When the coach creates a meal plan, she uses Sanity Studio—a fully customized admin interface that I built with React components. When the meal plan status changes to 'delivered', a webhook triggers and Resend sends the email.
The meal plan request schema captures everything from the questionnaire. Each field maps directly to a question in the multi-step form. The schema is TypeScript, so I get autocomplete and type checking when writing queries.
The key insight: Sanity's schema-as-code means I can version control my data model. Schema changes go through PR review. I can see exactly what changed and when. No database migration scripts to manage.
Sanity supports both GROQ and GraphQL. I chose GROQ for several reasons:
1. Projections are more flexible. I can reshape data in the query itself, not in application code. 2. Joins are implicit. Reference fields resolve automatically with the -> operator. 3. No schema synchronization. GROQ queries run against the live schema—no codegen step. 4. It's a Sanity superpower. GraphQL works everywhere; GROQ is optimized for Sanity's content lake.
For my portfolio site, I use GraphQL because I wanted to demonstrate that skill and use GraphQL Codegen. For Bring The Shreds, GROQ was the right choice—faster iteration, less tooling.
When the coach marks a meal plan as 'delivered', the client should receive an email. Traditional approach: set up a cron job or background worker. Sanity approach: webhook.
Sanity webhooks fire on document changes. I configured a webhook that triggers when a mealPlanRequest document's status field changes to 'delivered'. The webhook hits a Next.js API route, which sends the email via Resend. Zero infrastructure to manage.
Sanity Studio is a React app. This means I could customize it to match the coach's workflow. The meal plan creation interface groups fields logically. Client information is referenced, not duplicated. The status field has clear visual states.
The coach doesn't know she's using a 'headless CMS'. She just knows she has an app where she manages her business. That's the goal.
Bring The Shreds launched with zero backend code to maintain. The coach manages everything through Studio. Clients submit meal plan requests through the site. Emails go out automatically. Stripe handles payments.
Total infrastructure: Vercel (frontend) + Sanity (backend) + Resend (email) + Stripe (payments). No servers. No databases. No DevOps.
1. A headless CMS can BE the backend, not just store content. The distinction between 'content' and 'application data' is artificial. 2. Schema-as-code is a superpower. Version control, PR review, and TypeScript types for your data model. 3. Webhooks replace background jobs for simple workflows. No infrastructure to manage. 4. The best admin interface is one your client actually uses. Sanity Studio let me build exactly that.