AgentSkillsCN

ce-nextjs-patterns

Commerce Engine 的高级 Next.js 设计模式——storefront() 函数、用于 SSR 的 CookieTokenStorage、StorefrontSDKInitializer、Server Actions、SSG 与 ISR。

SKILL.md
--- frontmatter
name: ce-nextjs-patterns
description: Advanced Next.js patterns for Commerce Engine - storefront() function, CookieTokenStorage for SSR, StorefrontSDKInitializer, Server Actions, SSG, and ISR.
license: MIT
allowed-tools: Bash
metadata:
  author: commercengine
  version: "1.0.0"

Next.js Patterns

For basic setup, see setup/.

Impact Levels

  • CRITICAL - Breaking bugs, security holes
  • HIGH - Common mistakes
  • MEDIUM - Optimization

References

ReferenceImpact
references/server-vs-client.mdCRITICAL - storefront(cookies()) vs storefront()
references/token-management.mdHIGH - Cookie-based token flow in Next.js

Mental Model

The storefront() function adapts to the execution context:

ContextUsageToken Storage
Client Componentsstorefront()Browser cookies
Server Componentsstorefront(cookies())Request cookies
Server Actionsstorefront(cookies())Request cookies (read + write)
Root Layoutstorefront({ isRootLayout: true })Memory fallback
Build time (SSG)storefront()Memory (no user context)

Setup

1. Install

bash
npm install @commercengine/storefront-sdk-nextjs

2. Create Config

typescript
// lib/storefront.ts
export { storefront } from "@commercengine/storefront-sdk-nextjs";

3. Root Layout

tsx
// 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 });

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <StorefrontSDKInitializer />
        <h1>Welcome to My Brand Store</h1>
        {children}
      </body>
    </html>
  );
}

4. Environment Variables

env
# .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

Key Patterns

Server Component (Data Fetching)

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, 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>
  );
}

Server Actions (Mutations)

typescript
// 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 };
}

Static Site Generation (SSG)

typescript
// 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>
  );
}

Client Component

tsx
"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>;
}

Common Pitfalls

LevelIssueSolution
CRITICALMissing cookies() in Server ComponentsUse storefront(cookies()) for user-specific data on the server
CRITICALAuth in Server Components instead of ActionsAuth endpoints that return tokens MUST be in Server Actions, not Server Components
HIGHMissing StorefrontSDKInitializerRequired in root layout for automatic anonymous auth and session continuity
HIGHUsing cookies() in Client ComponentsClient Components use storefront() (no cookies) — tokens managed via browser cookies
MEDIUMSlow buildsSet NEXT_BUILD_CACHE_TOKENS=true for token caching during SSG
MEDIUMRoot Layout missing isRootLayout flagRoot Layout runs outside request context — use storefront({ isRootLayout: true })

See Also

  • setup/ - Basic SDK installation
  • auth/ - Authentication flows
  • cart-checkout/ - Cart management

Documentation