Skip to content
Dashboard

Introducing Next.js Commerce 2.0

Copy 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

Copy link to headingNext.js Commerce v1

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

Copy link to headingPerformance results

Copy link to headingLayouts and pages

Copy link to headingReact Server Components, Streaming, and Suspense

Copy 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

Copy 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

Copy link to headingRepository strategy

Ready to deploy?