Guides · 20 min

A storefront with Next.js

Read the catalog server-side, render mockups, and wire a working buy flow into a Next.js app — grounded in the URL primitive the whole way.

This builds a real storefront: a grid of products from the public catalog, each rendered as a live mockup, each linking to checkout. It uses the SDK for ergonomics, but everything underneath is the same img/buy URL you can build by hand.

1

Install & configure

Add the SDK (and the optional UI components for later). Then set your public Shop ID — it’s safe in a NEXT_PUBLIC_ var.

bash
pnpm add @snowcone-app/sdk @snowcone-app/ui
bash
# .env.local — the Shop ID is public, so NEXT_PUBLIC_ is fine.
NEXT_PUBLIC_SNOWCONE_SHOP=YOUR_SHOP_ID
2

List the catalog

The catalog is a public Meilisearch index, so a Server Component can fetch it directly — no SDK required to read it. Cache it; the catalog changes rarely.

tsx
// app/page.tsx — a Server Component. Read the catalog at request time.
// The catalog is a public Meilisearch index; the search key is public.
const SEARCH = "https://search.snowcone.app/indexes/snowcone";
const KEY = "eee819b849798ad9091228c486ec05d0931e5292";

async function getProducts() {
  const res = await fetch(`${SEARCH}/search`, {
    method: "POST",
    headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ limit: 24 }),
    next: { revalidate: 3600 }, // cache the catalog for an hour
  });
  const { hits } = await res.json();
  return hits as { id: string; name: string; price: number }[];
}

export default async function Storefront() {
  const products = await getProducts();
  return (
    <ul className="grid grid-cols-3 gap-6">
      {products.map((p) => (
        <ProductCard key={p.id} product={p} />
      ))}
    </ul>
  );
}
This is standard Meilisearch — add q, filters, or facets to the body and you have search and category pages for free. See the catalog reference.
3

Render & sell each product

For each product, getMockupUrl builds the image URL and the buy link is the same values with img buy. In a real app, SAMPLE_ART is whatever artwork your shopper picked or uploaded.

tsx
import { getMockupUrl } from "@snowcone-app/sdk";

const SHOP = process.env.NEXT_PUBLIC_SNOWCONE_SHOP!;
const SAMPLE_ART = "https://cdn.example.com/art.png"; // your shopper's artwork

function ProductCard({ product }: { product: { id: string; name: string; price: number } }) {
  const mockup = getMockupUrl(product.id, { shop: SHOP, asset: SAMPLE_ART, width: 800 });
  const buy = `https://buy.snowcone.app/${product.id}?asset=${encodeURIComponent(SAMPLE_ART)}&shop=${SHOP}`;
  return (
    <li>
      <a href={buy} className="block">
        <div className="aspect-square overflow-hidden rounded-xl">
          {/* eslint-disable-next-line @next/next/no-img-element */}
          <img src={mockup} alt={product.name} className="h-full w-full object-cover" />
        </div>
        <p className="mt-2 font-medium">
          {product.name} — ${(product.price / 100).toFixed(2)}
        </p>
      </a>
    </li>
  );
}
4

Optional: the React layer

If you’d rather not wire URLs by hand, the @snowcone-app/ui components wrap the exact same calls. Drop a <Shop> provider at the top and compose.

tsx
// Prefer components? The same storefront with the optional React layer.
import { Shop, Product, ProductImage, ProductPrice, AddToCart } from "@snowcone-app/ui";

export default function Card({ id }: { id: string }) {
  return (
    <Shop>
      <Product productId={id}>
        <ProductImage artwork="https://cdn.example.com/art.png" />
        <ProductPrice />
        <AddToCart />
      </Product>
    </Shop>
  );
}
The components are a convenience, not a requirement — they render the same URLs. See the Shop provider and the component reference.

Run the full example

A complete working storefront — this guide, assembled — lives in the examples/next-ecommerce directory of the Snowcone monorepo. It uses pnpm; from the repo root:

pnpm install

Install workspace dependencies (run once, from the repo root).

pnpm --filter next-ecommerce dev

Start the storefront in development mode.

The React layer is styled by your app’s Tailwind v4 build — import ui’s stylesheet, add an @source for your own code, and transpile the package:

css
/* app/globals.css — your Tailwind v4 entry compiles ui's styles.
 * ui's stylesheet brings in Tailwind, the theme tokens, and an @source
 * for the package's own components; add an @source for your code. */
@import "@snowcone-app/ui/styles/globals.css";
@source "../app/**/*.{js,ts,jsx,tsx}";

/* next.config.ts also needs: transpilePackages: ['@snowcone-app/ui']
 * For dark mode, toggle the "dark" class on an ancestor (e.g. <html>). */
The example ships with a demo Shop ID so it runs out of the box, but the demo shop is read-only and routes any sales elsewhere. Mint your own before wiring up real artwork — see Get a Shop ID.

You now have a storefront that lists, renders, and sells. To go further: add size & color options, let shoppers design their own art with the Canvas editor, or take payment yourself with the Orders API.