Next.js starter
A complete headless store on the Next.js App Router: server components, a cart, checkout, buyer accounts and pages that refresh when you save.
A complete store on the Next.js App Router, running on your ShopFast store through the Storefront API and the SDK. Server components fetch the catalog, server actions run the cart and sign-in, and your store’s own checkout takes the payment. Deploy it as it is, or make it yours. The code is on GitHub (MIT).
| Page | What it does |
|---|---|
| Home | The store’s hero, collections, categories and featured products |
| Product | Gallery, options, live stock, reviews, recommendations, add to cart, Product JSON-LD |
| Collection, category | Sorting and “Show more” with cursor pagination |
| Search | Suggestions as you type, results with type filters and sorting |
| Cart | Quantities, discount codes and gift cards in one field |
| Checkout | Its own checkout page (your ways to pay, fields, questions, terms, tips and email codes), or the embedded or hosted checkout |
| Account | Sign-in with an emailed code, orders, license keys and downloads, wallet, subscriptions, tickets |
| Order | The receipt link (/orders/<id>?access=…) with keys and downloads once paid |
| Policies, support | Your policy pages and a support form |
It also has a sitemap, robots, and a loading.tsx on every route drawn from that page’s own layout, so nothing moves
when the page arrives.
Set up
-
Get the code and install it:
npx degit anurag12340/shopfast-next-storefront my-store cd my-store npm install -
In Settings → Headless & MCP → Storefront keys, create a private key (
sfsk_…). The starter calls the API from its server only, so the key never reaches a browser. -
Copy
.env.exampleto.env.localand fill it in (see Environment). -
Run it:
npm run dev # http://localhost:3000 -
In Settings → Developers → Webhooks, add an endpoint at
https://<your site>/api/revalidatefor thestorefront.updatedevent and put its signing secret inSHOPFAST_WEBHOOK_SECRET. Pages then update seconds after you save a product; without it they refresh every 5 minutes. -
Add your site’s origin (
https://shop.example.com, andhttp://localhost:3000while you develop) to the storefront key’s allowed origins. Checkout sends buyers back only to a site listed there, and the embedded checkout opens only there.
Environment
| Variable | |
|---|---|
SHOPFAST_SHOP |
Your store’s handle (the part before .shopfa.st) or its st_ id |
SHOPFAST_STOREFRONT_KEY |
The private storefront key (sfsk_live_…). Server only |
SHOPFAST_WEBHOOK_SECRET |
The signing secret of your storefront.updated endpoint (whsec_…) |
SITE_URL |
Where the site is served, for canonical links, Open Graph, the sitemap and the checkout’s return pages |
SHOPFAST_CHECKOUT |
custom (the default: the site’s own checkout page), embedded or hosted |
SHOPFAST_API_URL |
Leave it out: https://api.shopfa.st |
How it fits together
lib/shopfast.ts makes two clients. The catalog client is shared by every buyer and keeps its answers in Next’s Data
Cache with tags; the buyer client carries this buyer’s address and session and is never cached:
import { createNextStorefrontClient } from '@shopfast-sdk/client/next';
export function catalog() {
return createNextStorefrontClient({ shop: config.shop, key: config.key, revalidate: 300 });
}
export const buyer = cache(async () => {
const [h, c] = await Promise.all([headers(), cookies()]);
return catalog().with({ buyerIp: buyerIp(h), customerToken: c.get('sf_customer')?.value ?? null });
});
| File | What it does |
|---|---|
lib/shopfast.ts |
The two clients, and the cached reads every page shares |
app/actions/*.ts |
Server actions: the cart, sign-in, support, live stock and search suggestions |
components/BuyBox.tsx |
Options, quantity, add to cart and the live stock line |
components/EmbeddedCheckout.tsx |
Your store’s checkout in an overlay on this site |
app/api/revalidate/route.ts |
The storefront.updated webhook |
The cart id and the buyer’s session token live in httpOnly cookies. Writes go out from server actions, so a buyer never talks to the API directly and your private key stays on the server.
Pages that refresh when you save
Every cached answer carries the tag shopfast:<shop>, and product, collection and page answers carry their own
(shopfast:<shop>:product:<handle>…). When you change something, ShopFast sends
storefront.updated and the route refreshes exactly those tags:
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { handleRevalidationWebhook } from '@shopfast-sdk/client/next';
export function POST(request: Request) {
return handleRevalidationWebhook(request, { secret: process.env.SHOPFAST_WEBHOOK_SECRET!, revalidateTag, shop: process.env.SHOPFAST_SHOP! });
}
Checkout
SHOPFAST_CHECKOUT picks one of the three ways in Headless checkout:
custom:/checkoutis the site’s own page, drawn fromcart.checkoutOptions. It starts the checkout withcart.checkout, asks for the emailed code when the store wants one, and sends the buyer topayUrl. After paying, the buyer comes back to/checkout/complete, which keeps the order’s access token in a cookie and opens the order.embedded: your store’s checkout in an overlay on the cart page, opened with the same cart (embed.cart).hosted: a signed link to your store’s checkout, back to the site after paying.
Payment methods, taxes, fraud checks and delivery are your store’s own in all three.
Deploy
Any host that runs Next.js works (Vercel, Netlify, a Node server). Set the variables above, then npm run build. The
buyer’s address is read from the first x-forwarded-for hop, which is what Vercel and most proxies send; on your own
server, make sure your proxy sets it.