Next.js wrapper for the Commerce Engine Storefront SDK
Production-ready Next.js wrapper for the CommerceEngine Storefront SDK. Provides the perfect developer experience with automatic context detection, universal API, and zero configuration complexity.
Built on @commercengine/ssr-utils for robust server-side token management.
✨ Perfect DX Pattern:
- 🎯 One Config File - Single lib/storefront.ts using createStorefront()
- 🌍 Universal API - Same storefront() import works everywhere
- 🔥 Automatic Tokens - Creates and manages tokens automatically
- 🧠 Smart Context Detection - Detects Server vs Client vs Build contexts
- 🍪 Cookie-based State - Shared authentication via Next.js cookies
- ⚡ Request Isolation - Proper per-request SDK instances on server
- 🛠 Zero Complexity - Environment variables + one lib file = done
``bash`
npm install @commercengine/storefront-sdk-nextjsor
pnpm add @commercengine/storefront-sdk-nextjs
Add your store configuration to .env.local:
`bash`
NEXT_PUBLIC_STORE_ID=your-store-id
NEXT_PUBLIC_API_KEY=your-api-keyEnvironment (defaults to "staging")
NEXT_PUBLIC_ENVIRONMENT=staging # or "production"
Create lib/storefront.ts in your project:
`typescript
// lib/storefront.ts
import {
createStorefront,
type StorefrontRuntimeConfig,
} from "@commercengine/storefront-sdk-nextjs";
// Optional advanced configuration (everything not in environment variables)
const storefrontConfig: StorefrontRuntimeConfig = {
debug: true,
timeout: 15000,
logger: (msg: string, ...args: any[]) =>
console.log("[STOREFRONT]", msg, ...args),
onTokensUpdated: (access: string, refresh: string) => {
console.log("🔥 TOKENS UPDATED:", {
access: access.slice(0, 20) + "...",
refresh: refresh.slice(0, 20) + "...",
});
},
onTokensCleared: () => {
console.log("🔄 TOKENS CLEARED");
},
};
// Create the configured storefront function (storefrontConfig is OPTIONAL)
export const storefront = createStorefront(storefrontConfig);
// Re-export types for convenience
export type { StorefrontRuntimeConfig };
`
All Core SDK Types Available:
The Next.js SDK re-exports all types from the core SDK, so you can import them directly:
`typescript
// Import any core SDK types you need
import type {
UserInfo,
SupportedDefaultHeaders,
StorefrontSDKOptions,
components,
operations
} from "@commercengine/storefront-sdk-nextjs";
// Use imported types
const headers: SupportedDefaultHeaders = {
customer_group_id: "01ABC123..."
};
`
`typescript
// app/layout.tsx
import { StorefrontSDKInitializer } from "@commercengine/storefront-sdk-nextjs/client";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
$3
`typescript
// Import your configured storefront everywhere
import { storefront } from "@/lib/storefront";
import { cookies } from "next/headers"; // Only for server contexts// ✅ Client Component - No cookies needed
const products = await storefront().catalog.listProducts();
// ✅ Server Component, Server Action, or API Route - MUST pass cookies
const products = await storefront(cookies()).catalog.listProducts();
// ✅ Root Layout - Special exception with explicit flag
const products = await storefront({ isRootLayout: true }).catalog.listProducts();
// ✅ SSG/ISR (build contexts) - Automatic fallback to memory storage
// (NEXT_BUILD_CACHE_TOKENS=true enables this)
const products = await storefront().catalog.listProducts();
`Usage in Different Next.js Contexts
$3
Client Components run in the browser and can persist tokens via cookies:
`typescript
// components/ProductList.tsx
"use client";import { useState, useEffect } from "react";
import { storefront } from "@/lib/storefront";
export default function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
async function loadProducts() {
const sdk = storefront(); // No cookies() needed on client-side
// Tokens are automatically managed by StorefrontSDKInitializer
const { data } = await sdk.catalog.listProducts();
if (data) setProducts(data.products);
}
loadProducts();
}, []);
return (
{products.map(product => (
{product.name}
))}
);
}
`$3
Server Components run on the server and can read cookies:
`typescript
// 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: products } = await sdk.catalog.listProducts();
return (
{products?.products.map(product => (
{product.name}
))}
);
}
`$3
Server Actions can both read and write cookies, perfect for authentication:
`typescript
// app/actions.ts
"use server";import { storefront } from "@/lib/storefront";
import { cookies } from "next/headers";
export async function loginWithEmail(email: string, password: string) {
const sdk = storefront(cookies());
const { data, error } = await sdk.auth.loginWithPassword({ email, password });
if (data) {
// Tokens are automatically saved to cookies
return { success: true, user: data.user };
}
return { success: false, error: error?.message };
}
`$3
API Routes work identically to Server Actions:
`typescript
// app/api/products/route.ts
import { storefront } from "@/lib/storefront";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";export async function GET() {
try {
const sdk = storefront(cookies());
const { data, error } = await sdk.catalog.listProducts();
if (error) {
return NextResponse.json({ error: error.message }, { status: 400 });
}
return NextResponse.json(data);
} catch (error) {
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
`$3
Root Layout requires explicit flag since it's outside request context. Passing this flag will instruct the SDK to fallback to MemoryStore when rendering the root layout as the root layout runs on both server and client before cookie context is available.
`typescript
// app/layout.tsx
import { StorefrontSDKInitializer } from "@commercengine/storefront-sdk-nextjs/client";
import { storefront } from "@/lib/storefront";// Root Layout requires explicit flag - no request context available
const sdk = storefront({ isRootLayout: true });
const { data: storeConfig } = await sdk.store.getStoreConfig();
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
Welcome to {storeConfig?.store_config?.brand.name}
{children}
);
}
`Static Site Generation (SSG) & Build-Time Optimization
The SDK provides powerful build-time optimizations through intelligent token caching.
$3
Create or update your
next.config.ts (or next.config.js) to enable automatic token caching during builds:`typescript
// next.config.ts
import type { NextConfig } from "next";
import { PHASE_PRODUCTION_BUILD } from 'next/constants';const nextConfig = (phase: string): NextConfig => {
const isBuild = phase === PHASE_PRODUCTION_BUILD;
return {
env: {
// Enable build-time token caching during production builds and when explicitly set
NEXT_BUILD_CACHE_TOKENS: process.env.NEXT_BUILD_CACHE_TOKENS ?? (isBuild ? 'true' : 'false'),
// Critical: tells SDK to use MemoryStore during SSG/Build/ISR to avoid cookie context failures
NEXT_IS_BUILD: isBuild ? 'true' : 'false',
},
// Ensure static generation is enabled
output: undefined, // Let Next.js decide based on usage
};
};
export default nextConfig;
`$3
`typescript
// app/products/[slug]/page.tsx
import { storefront } from "@/lib/storefront";
import { notFound } from "next/navigation";interface ProductPageProps {
params: Promise<{ slug: string }>;
}
export default async function ProductPage({ params }: ProductPageProps) {
const { slug } = await params;
const sdk = storefront(); // No cookies() - uses build-time storage
const { data, error } = await sdk.catalog.getProductDetail({
product_id_or_slug: slug
});
if (error || !data) {
notFound();
}
return (
{data.product.name}
SKU: {data.product.sku}
Description: {data.product.short_description}
);
}// Generate static params from real API data
export async function generateStaticParams() {
const sdk = storefront(); // Token will be cached and reused
const { data: productsData, error } = await sdk.catalog.listProducts({
limit: 100
});
if (error || !productsData) {
return [];
}
return productsData.products.map(product => ({
slug: product.slug || product.id
}));
}
`$3
With token caching enabled:
- ✅ Token created once and reused across all pages
- ✅ 100 pages = ~1 anonymous token API call total
- ✅ Faster builds, dramatically lower API usage
Authentication Patterns
> ⚠️ Important: Any authentication endpoint that returns tokens (like
loginWithPassword, verifyOtp, register, etc.) must be called in contexts where cookies can be set and managed:
> - ✅ Server Actions (recommended for authentication flows)
> - ✅ API Routes (/api directory)
> - ✅ Client Components (browser environment)
> - ❌ Server Components (cannot set cookies, tokens won't persist)
>
> This ensures the SDK can automatically handle token storage and user session continuity.$3
The SDK automatically creates anonymous tokens via the
StorefrontSDKInitializer component imported in your root layout. If this component is not imported (not recommended), a new token will be minted for each request as a fallback. It is highly recommended to use the StorefrontSDKInitializer and ensure all token-returning endpoints are called from request contexts that can set cookies. Authentication Patterns.`typescript
// This works everywhere - creates anonymous token automatically
const { data: products } = await storefront(cookies()).catalog.listProducts();
`$3
`typescript
// Server Action (✅ Recommended - can set cookies for token persistence)
"use server";
export async function loginUser(email: string, password: string) {
const sdk = storefront(cookies());
const { data, error } = await sdk.auth.loginWithPassword({ email, password });
if (data) {
// Tokens automatically saved to cookies
redirect('/dashboard');
}
return { error: error?.message };
}
`$3
`typescript
// Server Action - Step 1: Send OTP (✅ Recommended context)
export async function sendOTP(phone: string, country_code: string) {
const sdk = storefront(await cookies());
return await sdk.auth.loginWithPhone({
phone,
country_code,
register_if_not_exists: true
});
}// Server Action - Step 2: Verify OTP (✅ Recommended - can set cookies for token persistence)
export async function verifyOTP(otp: string, otp_token: string, otp_action: string) {
const sdk = storefront(await cookies());
const { data, error } = await sdk.auth.verifyOtp({
otp,
otp_token,
otp_action
});
if (data) {
// Tokens automatically saved to cookies
return { success: true, user: data.user };
}
return { success: false, error: error?.message };
}
`API Reference
$3
Creates a configured storefront function that works universally across all Next.js contexts.
`typescript
import { createStorefront } from "@commercengine/storefront-sdk-nextjs";export const storefront = createStorefront({
debug: true,
logger: (msg, ...args) => console.log('[DEBUG]', msg, ...args)
});
`Parameters:
-
config (optional): StorefrontRuntimeConfig - Advanced configuration optionsReturns:
- Universal
storefront() function that works in all Next.js contexts$3
Optional configuration object for
createStorefront():`typescript
interface StorefrontRuntimeConfig {
// Override environment variables
storeId?: string;
apiKey?: string;
environment?: Environment;
baseUrl?: string;
timeout?: number;
debug?: boolean;
// Advanced options (not available via environment variables)
accessToken?: string;
refreshToken?: string;
defaultHeaders?: SupportedDefaultHeaders;
logger?: (message: string, ...args: any[]) => void;
onTokensUpdated?: (accessToken: string, refreshToken: string) => void;
onTokensCleared?: () => void;
tokenStorageOptions?: NextJSTokenStorageOptions;
}
`$3
The function returned by
createStorefront() works in all contexts with strict enforcement:`typescript
// Client-side (browser)
const sdk = storefront();// Server-side (requires cookies for user continuity)
const sdk = storefront(cookies());
// Root Layout (special exception with explicit flag)
const sdk = storefront({ isRootLayout: true });
// Server-side without cookies - throws helpful error to protect user sessions
const sdk = storefront(); // ❌ Throws error in server contexts
`$3
`typescript
function storefront(): StorefrontSDK;
function storefront(cookieStore: NextCookieStore): StorefrontSDK;
function storefront(options: { isRootLayout: true }): StorefrontSDK;
function storefront(cookieStore: NextCookieStore, options: { isRootLayout?: boolean }): StorefrontSDK;
`$3
Client-side initializer component (must be imported from
/client):`typescript
import { StorefrontSDKInitializer } from "@commercengine/storefront-sdk-nextjs/client";
`No props needed - configuration comes from environment variables and your
lib/storefront.ts file.Error Handling
All SDK methods return a consistent error structure:
`typescript
const { data, error, response } = await storefront(cookies()).catalog.listProducts();if (error) {
console.error("API Error:", error.message, error.code);
console.log("Status:", response.status);
} else {
console.log("Products:", data.products);
}
`Best Practices
$3
- Set required environment variables:
NEXT_PUBLIC_STORE_ID and NEXT_PUBLIC_API_KEY are mandatory
- Create one lib/storefront.ts file: Use createStorefront() to configure advanced options
- Initialize once: Call StorefrontSDKInitializer only in your root layout
- Use storefront(cookies()): Always pass cookies in server contexts
- Handle errors: Always check the error property in responses
- Use Server Actions: For authentication flows that need to persist tokens$3
- Don't call
cookies() in Client Components: It will throw an error
- Don't initialize multiple times: The SDK handles singleton behavior
- Don't forget error handling: API calls can fail for various reasons
- Don't skip environment variables: Missing required variables will cause errorsTroubleshooting
$3
1. "Server context requires cookies for user continuity!"
`typescript
// ❌ Wrong - server context without cookies (breaks user sessions)
storefront()
// ✅ Correct - server context with cookies
storefront(cookies())
// ✅ Root Layout exception
storefront({ isRootLayout: true })
`2. Missing Environment Variables
`bash
# Ensure your .env.local has the required variables:
NEXT_PUBLIC_STORE_ID=your-store-id
NEXT_PUBLIC_API_KEY=your-api-key
`3. "Cookie store passed in client environment"
`typescript
// ❌ Wrong - client component with cookies
storefront(cookies())
// ✅ Correct - client component without cookies
storefront()
`4. Server Actions and async cookies()
`typescript
// ✅ Correct - Server Actions need await cookies()
const sdk = storefront(await cookies());
// ✅ Server Components and API Routes use cookies() directly
const sdk = storefront(cookies());
`Migration Guide
$3
`typescript
// Before (core SDK)
import { StorefrontSDK } from "@commercengine/storefront-sdk";
const sdk = new StorefrontSDK({
storeId: "...",
tokenStorage: new BrowserTokenStorage()
});// After (Next.js SDK)
import { storefront } from "@/lib/storefront";
const sdk = storefront(cookies());
`Why This Pattern?
The Perfect DX Pattern provides:
- One Config File: All advanced configuration in
lib/storefront.ts
- Environment Variables: Basic config (storeId, apiKey) via env vars
- Universal Import: Same storefront() import everywhere
- Strict User Continuity: Enforces cookie passing to protect user sessions and analytics
- Explicit Exceptions: Clear patterns for special cases like Root Layout
- Zero Guesswork: Helpful errors guide developers to correct patternsResult: Production-ready e-commerce with bulletproof user session management! 🛡️
API Reference
For complete API documentation of all available endpoints, visit SDK DOCS REFERENCE
The Next.js SDK provides access to all the same endpoints as the core SDK:
-
sdk.auth.* - Authentication and user management
- sdk.customer.* - Customer profiles and preferences
- sdk.catalog.* - Products, categories, and search
- sdk.cart.* - Shopping cart management
- sdk.order.* - Order creation and tracking
- sdk.payments.* - Payment methods and processing
- sdk.store.* - Store configuration
- sdk.helpers.* - Countries, currencies, utilitiesRelated Packages
@commercengine/storefront-sdk` - Core Storefront SDKAll Rights Reserved