Loading...
Loading...
Advanced Next.js patterns for Commerce Engine - storefront() function, CookieTokenStorage for SSR, StorefrontSDKInitializer, Server Actions, SSG, and ISR.
npx skill4agent add commercengine/skills ce-nextjs-patternsLLM Docs Header: All requests tomust include thehttps://llm-docs.commercengine.ioheader (or appendAccept: text/markdownto the URL path). Without it, responses return HTML instead of parseable markdown..md
setup/| Reference | Impact |
|---|---|
| CRITICAL - |
| HIGH - Cookie-based token flow in Next.js |
storefront()| Context | Usage | Token Storage |
|---|---|---|
| Client Components | | Browser cookies |
| Server Components | | Request cookies |
| Server Actions | | Request cookies (read + write) |
| Root Layout | | Memory fallback |
| Build time (SSG) | | Memory (no user context) |
npm install @commercengine/storefront-sdk-nextjs// lib/storefront.ts
export { storefront } from "@commercengine/storefront-sdk-nextjs";// app/layout.tsx
import { StorefrontSDKInitializer } from "@commercengine/storefront-sdk-nextjs/client";
import { storefront } from "@/lib/storefront";
// Root Layout has no request context — use isRootLayout flag
const sdk = storefront({ isRootLayout: true });
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<StorefrontSDKInitializer />
{children}
</body>
</html>
);
}# .env.local
NEXT_PUBLIC_STORE_ID=your-store-id
NEXT_PUBLIC_API_KEY=your-api-key
NEXT_BUILD_CACHE_TOKENS=true # Faster builds with token caching// app/products/page.tsx
import { storefront } from "@/lib/storefront";
import { cookies } from "next/headers";
export default async function ProductsPage() {
const sdk = storefront(cookies());
const { data, error } = await sdk.catalog.listProducts({
page: 1, limit: 20,
});
if (error) return <p>Error: {error.message}</p>;
return (
<div>
{data.products.map((product) => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}// app/actions.ts
"use server";
import { storefront } from "@/lib/storefront";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export async function loginWithEmail(email: string) {
const sdk = storefront(cookies());
const { data, error } = await sdk.auth.loginWithEmail({
email,
register_if_not_exists: true,
});
if (error) return { error: error.message };
return { otp_token: data.otp_token, otp_action: data.otp_action };
}
export async function verifyOtp(otp: string, otpToken: string, otpAction: string) {
const sdk = storefront(cookies());
const { data, error } = await sdk.auth.verifyOtp({
otp,
otp_token: otpToken,
otp_action: otpAction,
});
if (error) return { error: error.message };
redirect("/account");
}
export async function addToCart(cartId: string, productId: string, variantId: string | null) {
const sdk = storefront(cookies());
const { data, error } = await sdk.cart.addDeleteCartItem(
{ id: cartId },
{ product_id: productId, variant_id: variantId, quantity: 1 }
);
if (error) return { error: error.message };
return { cart: data.cart };
}// app/products/[slug]/page.tsx
import { storefront } from "@/lib/storefront";
// Pre-render product pages at build time
export async function generateStaticParams() {
const sdk = storefront(); // No cookies at build time
const { data } = await sdk.catalog.listProducts({ limit: 100 });
return (data?.products ?? []).map((product) => ({
slug: product.slug,
}));
}
export default async function ProductPage({ params }: { params: { slug: string } }) {
const sdk = storefront(); // No cookies for static pages
const { data, error } = await sdk.catalog.getProductDetail({
product_id_or_slug: params.slug,
});
if (error) return <p>Product not found</p>;
const product = data.product;
return (
<div>
<h1>{product.name}</h1>
<p>{product.selling_price}</p>
{/* AddToCartButton is a Client Component */}
</div>
);
}"use client";
import { storefront } from "@/lib/storefront";
export function AddToCartButton({ productId, variantId }: Props) {
async function handleClick() {
const sdk = storefront(); // No cookies in client components
const { data, error } = await sdk.cart.addDeleteCartItem(
{ id: cartId },
{ product_id: productId, variant_id: variantId, quantity: 1 }
);
}
return <button onClick={handleClick}>Add to Cart</button>;
}generateMetadata// app/products/[slug]/page.tsx
import { storefront } from "@/lib/storefront";
import type { Metadata } from "next";
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
const sdk = storefront(); // No cookies — metadata runs at build time for static pages
const { data } = await sdk.catalog.getProductDetail({
product_id_or_slug: params.slug,
});
const product = data?.product;
if (!product) return { title: "Product Not Found" };
const image = product.images?.[0];
return {
title: product.name,
description: product.short_description,
openGraph: {
title: product.name,
description: product.short_description ?? undefined,
images: image ? [{ url: image.url_standard, alt: image.alternate_text ?? product.name }] : [],
},
};
}| Meta Tag | CE Field |
|---|---|
| |
| |
| |
| |
| Canonical URL | Build from |
For category/PLP pages, use the category name and description from. For search pages, use the search query.listCategories()
| Level | Issue | Solution |
|---|---|---|
| CRITICAL | Missing | Use |
| CRITICAL | Auth in Server Components instead of Actions | Auth endpoints that return tokens MUST be in Server Actions, not Server Components |
| HIGH | Missing | Required in root layout for automatic anonymous auth and session continuity |
| HIGH | Using | Client Components use |
| MEDIUM | Slow builds | Set |
| MEDIUM | Root Layout missing | Root Layout runs outside request context — use |
setup/auth/cart-checkout/