jamstack-storefront
Build a blazing-fast storefront with Next.js or Astro that pre-renders product pages as static HTML and fetches live data from commerce APIs
What this skill does
# Jamstack Storefront
## Overview
A Jamstack storefront pre-renders catalog pages at build time for maximum performance and CDN cacheability, while using client-side JavaScript and commerce APIs for dynamic functionality (cart, checkout, account). Next.js with Incremental Static Regeneration (ISR) and Astro with on-demand rendering are the two dominant approaches, each offering different tradeoffs between build times, freshness, and interactivity. This skill covers setting up a Jamstack commerce site, managing catalog regeneration, and integrating headless commerce APIs.
## When to Use This Skill
- When SEO and Core Web Vitals scores are top priorities — static HTML scores near-perfect Lighthouse results
- When you have a large catalog that rarely changes and want sub-100ms page loads from CDN
- When you want to decouple the commerce backend (Shopify, Saleor, commercetools) from the storefront deployment cycle
- When your team wants to use modern React/Astro tooling rather than a platform's proprietary theme system
- When you need to combine commerce data with a CMS (Contentful, Sanity) at build time
## Prerequisites & Platform Notes
**This skill is written for custom/headless storefronts** (Node.js, Python, or similar backend). The code examples use TypeScript/Node.js and can be adapted to any stack.
**Shopify**: Shopify Hydrogen is Shopify's headless framework. MACH/composable patterns apply when using Shopify as the commerce backend with a custom frontend, or when mixing Shopify with other best-of-breed services.
**WooCommerce**: WooCommerce can serve as a headless backend via its REST API and WPGraphQL. These patterns apply when decoupling the frontend from WordPress.
**Magento**: Magento's GraphQL API and PWA Studio support headless architectures. These composable patterns apply to Magento as a backend service in a MACH stack.
**You'll need**:
- Node.js 18+ (or adapt to your backend language)
- Redis for caching/queues
- An email sending service (SendGrid, AWS SES, or Postmark)
- CDN (Cloudflare, CloudFront, or Fastly)
## Core Instructions
1. **Bootstrap a Next.js commerce storefront**
```bash
npx create-next-app@latest my-store --typescript --tailwind --app
cd my-store
npm install @shopify/storefront-api-client graphql
```
Configure the Storefront API client:
```typescript
// lib/shopify.ts
import {createStorefrontApiClient} from '@shopify/storefront-api-client';
export const shopify = createStorefrontApiClient({
storeDomain: process.env.SHOPIFY_STORE_DOMAIN!,
publicAccessToken: process.env.SHOPIFY_STOREFRONT_TOKEN!,
apiVersion: '2025-01',
});
```
2. **Statically generate product pages with ISR**
```typescript
// app/products/[handle]/page.tsx (Next.js App Router)
import {shopify} from '@/lib/shopify';
import {notFound} from 'next/navigation';
// ISR: revalidate every 60 seconds
export const revalidate = 60;
// Pre-build top products at build time
export async function generateStaticParams() {
const {data} = await shopify.request(TOP_PRODUCTS_QUERY, {
variables: {first: 200},
});
return data.products.edges.map(({node}: any) => ({handle: node.handle}));
}
export default async function ProductPage({params}: {params: {handle: string}}) {
const {data} = await shopify.request(PRODUCT_QUERY, {
variables: {handle: params.handle},
});
if (!data.product) notFound();
return <ProductDetail product={data.product} />;
}
const PRODUCT_QUERY = `
query ProductByHandle($handle: String!) {
product(handle: $handle) {
id title descriptionHtml
images(first: 5) { edges { node { url altText } } }
variants(first: 20) {
edges { node { id title price { amount currencyCode } availableForSale } }
}
}
}
`;
```
3. **Build an Astro storefront for minimal JavaScript overhead**
Astro ships zero JS by default — components are server-rendered to static HTML unless explicitly hydrated:
```bash
npm create astro@latest -- --template minimal
cd my-astro-store
npx astro add tailwind
npm install @astrojs/node graphql-request
```
```astro
---
// src/pages/products/[handle].astro
import {GraphQLClient, gql} from 'graphql-request';
import Layout from '../../layouts/Layout.astro';
import AddToCartButton from '../../components/AddToCartButton.tsx'; // Island
export async function getStaticPaths() {
const client = new GraphQLClient(import.meta.env.SALEOR_API_URL);
const {products} = await client.request(gql`query { products(first: 200, channel: "default-channel") { edges { node { slug } } } }`);
return products.edges.map(({node}: any) => ({params: {handle: node.slug}}));
}
const {handle} = Astro.params;
const client = new GraphQLClient(import.meta.env.SALEOR_API_URL);
const {product} = await client.request(PRODUCT_QUERY, {slug: handle, channel: 'default-channel'});
---
<Layout title={product.name}>
<h1>{product.name}</h1>
<img src={product.thumbnail.url} alt={product.thumbnail.alt} />
<!-- Only this interactive island ships JavaScript -->
<AddToCartButton client:load variantId={product.variants[0].id} />
</Layout>
```
4. **Implement on-demand ISR webhooks for catalog freshness**
When a product is updated in your CMS or commerce platform, trigger Next.js to revalidate only that page:
```typescript
// app/api/revalidate/route.ts
import {NextRequest, NextResponse} from 'next/server';
import {revalidatePath, revalidateTag} from 'next/cache';
export async function POST(req: NextRequest) {
const authHeader = req.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.REVALIDATION_TOKEN}`) {
return NextResponse.json({error: 'Unauthorized'}, {status: 401});
}
const body = await req.json();
const {type, handle, collectionHandle} = body;
switch (type) {
case 'product':
revalidatePath(`/products/${handle}`);
revalidateTag('products');
break;
case 'collection':
revalidatePath(`/collections/${collectionHandle}`);
revalidateTag('collections');
break;
case 'all':
revalidateTag('products');
revalidateTag('collections');
break;
}
return NextResponse.json({revalidated: true, timestamp: Date.now()});
}
```
Configure your commerce platform to POST to this endpoint on product updates.
5. **Implement client-side cart with Zustand**
Static product pages need client-side cart state. Use lightweight state management with localStorage persistence:
```typescript
// lib/cart-store.ts
import {create} from 'zustand';
import {persist} from 'zustand/middleware';
interface CartItem {
variantId: string;
title: string;
price: number;
quantity: number;
image: string;
}
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (variantId: string) => void;
updateQuantity: (variantId: string, quantity: number) => void;
clearCart: () => void;
total: () => number;
}
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
addItem: (item) => set((state) => {
const existing = state.items.find(i => i.variantId === item.variantId);
if (existing) {
return {items: state.items.map(i => i.variantId === item.variantId ? {...i, quantity: i.quantity + item.quantity} : i)};
}
return {items: [...state.items, item]};
}),
removeItem: (variantId) => set((state) => ({items: state.items.filter(i => i.variantId !== variantId)})),
updateQuantity: (variantId, quantity) => set((state) => ({items: state.items.map(i => i.variantId === variantId ? {...i, quantityRelated in headless-modern
medusa-development
IncludedExtend the open-source Medusa commerce platform with custom services, event subscribers, and API endpoints for unique business requirements
commerce-api-gateway
IncludedAggregate multiple commerce microservices behind a single API gateway with GraphQL federation, rate limiting, and unified authentication
pwa-storefront
IncludedTurn your store into an installable Progressive Web App with offline product browsing, push notifications, and home screen access for mobile shoppers
commerce-js-integration
IncludedBuild a lightweight headless store using the Commerce.js SDK for product display, cart management, and checkout without a heavy backend
composable-commerce
IncludedArchitect a modern store using MACH principles — independent microservices, API-first integrations, cloud-native hosting, and headless frontend
saleor-development
IncludedBuild and extend Saleor's GraphQL-based headless commerce platform with custom apps, webhook handlers, and dashboard UI customizations