Skip to content
Dashboard

Introducing Next.js Commerce 2.0

Link to headingWhy is building an ecommerce site difficult?

Web Almanac's 2021 Median Lighthouse scores for e-commerce sites on mobileWeb Almanac's 2021 Median Lighthouse scores for e-commerce sites on mobile
Web Almanac's 2021 Median Lighthouse scores for e-commerce sites on mobile

Sanity Toolkit for Next.js

Achieve next-level ecommerce speed with Sanity's enhanced Next.js toolkit—tailored to developers and designed for optimal user experiences.

Learn more

Link to headingNext.js Commerce v1

Link to headingNext.js Commerce 2.0—dynamic storefronts at the speed of static

Link to headingPerformance results

Link to headingLayouts and pages

Link to headingReact Server Components, Streaming, and Suspense

Link to headingData fetching and caching

lib/shopify/index.ts
const domain = `https://${process.env.SHOPIFY_STORE_DOMAIN!}`;
const endpoint = `${domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`;
const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
export async function shopifyFetch({
cache = 'force-cache',
headers,
query,
tags,
variables
}) {
const result = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Storefront-Access-Token': key,
...headers
},
body: JSON.stringify({
...(query && { query }),
...(variables && { variables })
}),
cache,
...(tags && { next: { tags } })
});
const body = await result.json();
if (body.errors) {
throw body.errors[0];
}
return {
status: result.status,
body
};
}

Shopify fetch wrapper

lib/shopify/index.ts
import { getProductQuery } from './queries/product';
const res = await shopifyFetch({
query: getProductQuery,
tags: ['products'],
variables: {
handle: 'acme-t-shirt'
}
});

Get a Shopify product specifying a revalidation tag

app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req) {
const productWebhooks = ['products/create', 'products/delete', 'products/update'];
const topic = headers().get('x-shopify-topic') || 'unknown';
const isProductUpdate = productWebhooks.includes(topic);
if (!isProductUpdate) {
// We don't need to revalidate anything for any other topics.
return NextResponse.json({ status: 200 });
}
if (isProductUpdate) {
revalidateTag('products');
}
return NextResponse.json({ status: 200, revalidated: true, now: Date.now() });
}

Revalidate products and collections using Shopify webhooks

Link to headingServer Actions

components/cart/add-to-cart.tsx
'use client';
import { addItem } from 'components/cart/actions';
import { useRouter } from 'next/navigation';
import { useTransition } from 'react';
export function AddToCart({selectedVariantId}) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
return (
<button
aria-label="Add item to cart"
disabled={isPending}
onClick={() => {
startTransition(async () => {
const error = await addItem(selectedVariantId);
if (error) {
alert(error);
return;
}
router.refresh();
});
}}
>
'Add To Cart'
</button>
);
}

Next.js Commerce add to cart button

components/cart/actions.ts
'use server';
import { addToCart, createCart, getCart } from 'lib/shopify';
import { cookies } from 'next/headers';
export const addItem = async (variantId) => {
let cartId = cookies().get('cartId')?.value;
let cart;
if (cartId) {
cart = await getCart(cartId);
}
if (!cartId || !cart) {
cart = await createCart();
cartId = cart.id;
cookies().set('cartId', cartId);
}
if (!variantId) {
return new Error('Missing variantId');
}
try {
await addToCart(cartId, [{ merchandiseId: variantId, quantity: 1 }]);
} catch (e) {
return new Error('Error adding item', { cause: e });
}
};

Next.js Commerce add to cart button Server Action

Link to headingRepository strategy