ce-nextjs-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
LLM Docs Header: All requests to
https://llm-docs.commercengine.io
must include the
Accept: text/markdown
header (or append
.md
to the URL path). Without it, responses return HTML instead of parseable markdown.
LLM文档头部说明:所有发往
https://llm-docs.commercengine.io
的请求必须携带
Accept: text/markdown
请求头(或者在URL路径末尾追加
.md
)。如果没有该头部,返回的响应会是HTML格式,而非可解析的markdown。

Next.js Patterns

Next.js模式

For basic setup, see
setup/
.
基础搭建步骤请查看
setup/
目录。

Impact Levels

影响等级

  • CRITICAL - Breaking bugs, security holes
  • HIGH - Common mistakes
  • MEDIUM - Optimization
  • 严重(CRITICAL):可导致功能崩溃的bug、安全漏洞
  • 高(HIGH):常见开发错误
  • 中(MEDIUM):性能优化点

References

参考文档

ReferenceImpact
references/server-vs-client.md
CRITICAL -
storefront(cookies())
vs
storefront()
references/token-management.md
HIGH - Cookie-based token flow in Next.js
参考文档影响等级
references/server-vs-client.md
严重 -
storefront(cookies())
storefront()
的差异
references/token-management.md
高 - Next.js中基于Cookie的token流转逻辑

Mental Model

心智模型

The
storefront()
function adapts to the execution context:
ContextUsageToken Storage
Client Components
storefront()
Browser cookies
Server Components
storefront(cookies())
Request cookies
Server Actions
storefront(cookies())
Request cookies (read + write)
Root Layout
storefront({ isRootLayout: true })
Memory fallback
Build time (SSG)
storefront()
Memory (no user context)
storefront()
函数会根据执行上下文自适应调整:
上下文用法Token存储位置
客户端组件
storefront()
浏览器Cookie
服务端组件
storefront(cookies())
请求Cookie
Server Actions
storefront(cookies())
请求Cookie(可读可写)
根布局
storefront({ isRootLayout: true })
内存兜底
构建阶段(SSG)
storefront()
内存(无用户上下文)

Setup

搭建步骤

1. Install

1. 安装依赖

bash
npm install @commercengine/storefront-sdk-nextjs
bash
npm install @commercengine/storefront-sdk-nextjs

2. Create Config

2. 创建配置文件

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

3. Root Layout

3. 配置根布局

tsx
// 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>
  );
}
tsx
// app/layout.tsx
import { StorefrontSDKInitializer } from "@commercengine/storefront-sdk-nextjs/client";
import { storefront } from "@/lib/storefront";

// 根布局没有请求上下文 —— 需传入isRootLayout标记
const sdk = storefront({ isRootLayout: true });

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

4. Environment Variables

4. 配置环境变量

env
undefined
env
undefined

.env.local

.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
undefined
NEXT_PUBLIC_STORE_ID=your-store-id NEXT_PUBLIC_API_KEY=your-api-key NEXT_BUILD_CACHE_TOKENS=true # 开启token缓存提升构建速度
undefined

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>
  );
}
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.message}</p>;

  return (
    <div>
      {data.products.map((product) => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  );
}

Server Actions (Mutations)

Server Actions(数据变更)

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

静态站点生成(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>
  );
}
typescript
// app/products/[slug]/page.tsx
import { storefront } from "@/lib/storefront";

// 构建阶段预渲染商品页面
export async function generateStaticParams() {
  const sdk = storefront(); // 构建阶段没有cookie
  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(); // 静态页面不需要cookie
  const { data, error } = await sdk.catalog.getProductDetail({
    product_id_or_slug: params.slug,
  });

  if (error) return <p>商品不存在</p>;
  const product = data.product;

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.selling_price}</p>
      {/* AddToCartButton是客户端组件 */}
    </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>;
}
tsx
"use client";

import { storefront } from "@/lib/storefront";

export function AddToCartButton({ productId, variantId }: Props) {
  async function handleClick() {
    const sdk = storefront(); // 客户端组件不需要传入cookie
    const { data, error } = await sdk.cart.addDeleteCartItem(
      { id: cartId },
      { product_id: productId, variant_id: variantId, quantity: 1 }
    );
  }

  return <button onClick={handleClick}>加入购物车</button>;
}

SEO Metadata

SEO元数据

Use Next.js
generateMetadata
with CE product fields for meta tags, Open Graph, and structured data:
typescript
// 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 }] : [],
    },
  };
}
CE field → meta tag mapping:
Meta TagCE Field
<title>
product.name
meta description
product.short_description
og:image
product.images[0].url_standard
og:image:alt
product.images[0].alternate_text
Canonical URLBuild from
product.slug
For category/PLP pages, use the category name and description from
listCategories()
. For search pages, use the search query.
使用Next.js的
generateMetadata
方法结合CE商品字段生成meta标签、Open Graph和结构化数据:
typescript
// 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(); // 不需要cookie —— 静态页面的元数据在构建阶段生成
  const { data } = await sdk.catalog.getProductDetail({
    product_id_or_slug: params.slug,
  });

  const product = data?.product;
  if (!product) return { title: "商品不存在" };

  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 }] : [],
    },
  };
}
CE字段 → meta标签映射关系:
Meta标签CE字段
<title>
product.name
meta description
product.short_description
og:image
product.images[0].url_standard
og:image:alt
product.images[0].alternate_text
标准URL基于
product.slug
构建
分类/商品列表页请使用
listCategories()
返回的分类名称和描述,搜索页请使用搜索关键词作为元数据内容。

Common Pitfalls

常见陷阱

LevelIssueSolution
CRITICALMissing
cookies()
in Server Components
Use
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
StorefrontSDKInitializer
Required in root layout for automatic anonymous auth and session continuity
HIGHUsing
cookies()
in Client Components
Client 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
flag
Root Layout runs outside request context — use
storefront({ isRootLayout: true })
等级问题解决方案
严重服务端组件中未传入
cookies()
服务端获取用户专属数据时请使用
storefront(cookies())
严重在服务端组件而非Actions中处理鉴权会返回token的鉴权接口必须在Server Actions中调用,不能在服务端组件中调用
未引入
StorefrontSDKInitializer
根布局中必须引入该组件,用于自动匿名鉴权和会话保持
在客户端组件中使用
cookies()
客户端组件直接使用
storefront()
(无需传入cookie),token由浏览器Cookie自动管理
构建速度慢配置
NEXT_BUILD_CACHE_TOKENS=true
开启SSG阶段的token缓存
根布局未传入
isRootLayout
标记
根布局运行在请求上下文之外,需使用
storefront({ isRootLayout: true })

See Also

相关内容

  • setup/
    - Basic SDK installation
  • auth/
    - Authentication flows
  • cart-checkout/
    - Cart management
  • setup/
    - SDK基础安装教程
  • auth/
    - 鉴权流程说明
  • cart-checkout/
    - 购物车管理教程

Documentation

文档链接