A full-stack monorepo boilerplate for building content-driven websites. It pairs a Sanity v5 CMS studio with an Astro 6 frontend deployed to Cloudflare Workers, using Svelte 5 for interactive components and Tailwind CSS v4 for styling.
| Layer | Technology |
|---|---|
| Frontend | Astro 6 — SSG on Cloudflare Workers (SSR opt-in per page) |
| UI Components | Svelte 5 |
| Styling | Tailwind CSS v4 |
| CMS | Sanity v5 |
| Deployment | Cloudflare Workers via @astrojs/cloudflare |
| State | Nanostores |
| Routing | Astro ClientRouter (SPA-mode client-side navigation) |
| Video | Mux + hls.js |
| E-commerce (optional) | Shopify Storefront API — main-shopify / previews-shopify branches only |
This boilerplate ships as four branches to cover common project configurations:
| Branch | Rendering | Shopify | Sanity Live Preview |
|---|---|---|---|
main |
SSG | ❌ | ❌ |
main-shopify |
SSG | ✅ | ❌ |
previews |
SSR | ❌ | ✅ |
previews-shopify |
SSR | ✅ | ✅ |
- SSG — all pages prerendered at build time (Cloudflare Workers serves static output).
- SSR — pages rendered on-demand (
prerender = false), required for Sanity Live Preview. - Shopify branches include the full Storefront API integration (cart, product pages, variant selector).
- Preview branches wire up
perspective: "previewDrafts"and the Sanity<VisualEditing />overlay.
Start from the branch that best matches your project's needs.
- Node.js
>=22.12.0 - pnpm (recommended package manager)
- A Sanity account and project
- A Cloudflare account (for deployment)
- (Optional, Shopify branches) A Shopify store with a Storefront API token
sane-svelstro-tinderbox/
├── astro/ # Astro 6 frontend
│ ├── public/
│ └── src/
│ ├── components/ # Shared & section components
│ │ └── sections/ # One component per Sanity section type
│ ├── layouts/ # Astro layout wrappers
│ ├── pages/ # File-based routing ([slug].astro, etc.)
│ ├── stores/ # Nanostores atoms (nav)
│ ├── styles/ # Global CSS & typography
│ ├── types/ # Shared TypeScript types (barrel: index.ts)
│ └── utils/
│ ├── groq.ts # Reusable GROQ field-selection fragments
│ ├── queires.ts # Full composed GROQ queries
│ ├── load-query.ts # fetchQuery / fetchPage / loadQuery helpers
│ └── image/ # Sanity image processing (processNestedImages)
└── sanity/ # Sanity v5 Studio
└── src/
├── schemas/
│ ├── documents/ # Page, Project
│ ├── objects/
│ │ └── sections/ # One schema file per section type
│ └── singletons/ # Home Page, Settings, Site
├── lib/ # Desk structure & presentation resolve
└── plugins/ # Custom document actions
Click Use this template on GitHub to generate your own copy of this repository. Then clone it:
git clone https://github.com/your-username/your-repo-name.git
cd your-repo-name/astro && pnpm install
cd ../sanity && pnpm installCreate a new Sanity project at sanity.io/manage, then copy your Project ID and Dataset name.
# sanity/
cp .env.example .envEdit sanity/.env:
SANITY_STUDIO_PROJECT_ID="your-project-id"
SANITY_STUDIO_DATASET="production"Start the studio:
cd sanity && pnpm dev# astro/
cp .env.example .envEdit astro/.env:
# Sanity — required
PUBLIC_SANITY_PROJECT_ID="your-project-id"
PUBLIC_SANITY_DATASET="production"
# Sanity Token — only needed for private Sanity projects
# SANITY_TOKEN="your-sanity-read-token"Start the dev server:
cd astro && pnpm dev| Variable | Required | Description |
|---|---|---|
PUBLIC_SANITY_PROJECT_ID |
✅ | Sanity project ID |
PUBLIC_SANITY_DATASET |
✅ | Sanity dataset (production) |
SANITY_TOKEN |
— | Read token — only needed for private Sanity projects |
PUBLIC_ENABLE_SHOPIFY |
Shopify branches | Set "true" to enable Shopify features |
PUBLIC_SHOPIFY_STORE |
Shopify branches | your-store.myshopify.com |
PUBLIC_SHOPIFY_STOREFRONT_TOKEN |
Shopify branches | Storefront API public token |
| Variable | Required | Description |
|---|---|---|
SANITY_STUDIO_PROJECT_ID |
✅ | Sanity project ID |
SANITY_STUDIO_DATASET |
✅ | Sanity dataset |
SANITY_STUDIO_VISUAL_EDITING_ENABLED |
Preview branches | Set "true" to enable the Presentation tool for visual editing |
SANITY_STUDIO_PREVIEW_URL |
Preview branches | Origin URL of the Astro preview deployment |
| Command | Action |
|---|---|
pnpm dev |
Start Astro dev server at localhost:4321 |
pnpm build |
Type-check + build for production |
pnpm preview |
Preview the production build locally |
| Command | Action |
|---|---|
pnpm dev |
Start Sanity Studio at localhost:3333 |
pnpm build |
Build the studio for self-hosting |
pnpm deploy |
Deploy studio to <project>.sanity.studio |
Content flows from Sanity to Astro through a structured GROQ pipeline:
@utils/groq.ts → reusable field-selection fragments (image, sections, pageSEO, …)
@utils/queires.ts → full page-level queries composed from fragments
@utils/load-query.ts → fetchQuery<T> / fetchPage<T> / loadQuery<T>
fetchQuery<T>()— raw GROQ fetch, no image processing.fetchPage<T>()— fetches and auto-processes all nested Sanity images. Prefer this in page-level.astrofiles.loadQuery<T>()— entry point for pages; delegates tofetchQueryonmain/main-shopify, and tofetchPagewithperspective: "previewDrafts"on thepreviews/previews-shopifybranches.
All Sanity image data must flow through @utils/image. Never construct image URLs manually.
Sections are the primary content building block. Every new section type requires four changes:
| Layer | Location |
|---|---|
| Sanity schema | sanity/src/schemas/objects/sections/<name>.ts |
| Sanity sections array | sanity/src/schemas/objects/sections.ts |
| Sanity schema index | sanity/src/schemas/index.ts |
| Astro/Svelte component | astro/src/components/sections/<Name>.astro or .svelte |
| Astro registration | astro/src/components/Sections.astro |
Included section types: example, media, projectsList. (productsList is included on the Shopify branches.)
Shopify integration (main-shopify, previews-shopify) is gated behind the PUBLIC_ENABLE_SHOPIFY environment variable. When disabled, all Shopify code is excluded at runtime.
- Check
shopifyConfig.isEnabledfrom@utils/shopifybefore any Shopify logic. - Cart state lives in
@stores/cartas a nanostores atom (cart,toggleCart,openCart,closeCart,removeItem).
Client-side state uses nanostores atoms in src/stores/:
@stores/nav— Navigation open/close state@stores/cart— Shopify cart state (Shopify branches only)
Nanostores implements the Svelte store contract natively, so you can read store values in Svelte components using the $ prefix (e.g. $nav) without any additional imports.
The Astro frontend uses the @astrojs/cloudflare adapter. By default all pages are prerendered at build time (SSG) — the static output is served via Cloudflare Workers.
To opt a page into on-demand (server-side) rendering, export prerender = false from that page file. To enable it globally, add it to src/layouts/Layout.astro:
---
export const prerender = false;
---- Run
pnpm buildfromastro/to generate the static output. - Deploy via Workers Builds (connect your repo in the Cloudflare dashboard) or run
npx wrangler deployfromastro/. - Set secret environment variables via the Wrangler CLI or the Cloudflare dashboard — never commit them to your repository.
A
wrangler.jsoncis only required for custom Worker configurations (e.g. KV bindings, Durable Objects). For simple static deployments, Astro auto-generates the Worker configuration.