ce-nextjs-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLLM 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
LLM文档头部说明:所有发往的请求必须携带https://llm-docs.commercengine.io请求头(或者在URL路径末尾追加Accept: text/markdown)。如果没有该头部,返回的响应会是HTML格式,而非可解析的markdown。.md
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
参考文档
| Reference | Impact |
|---|---|
| CRITICAL - |
| HIGH - Cookie-based token flow in Next.js |
| 参考文档 | 影响等级 |
|---|---|
| 严重 - |
| 高 - Next.js中基于Cookie的token流转逻辑 |
Mental Model
心智模型
The function adapts to the execution context:
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) |
storefront()| 上下文 | 用法 | Token存储位置 |
|---|---|---|
| 客户端组件 | | 浏览器Cookie |
| 服务端组件 | | 请求Cookie |
| Server Actions | | 请求Cookie(可读可写) |
| 根布局 | | 内存兜底 |
| 构建阶段(SSG) | | 内存(无用户上下文) |
Setup
搭建步骤
1. Install
1. 安装依赖
bash
npm install @commercengine/storefront-sdk-nextjsbash
npm install @commercengine/storefront-sdk-nextjs2. 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
undefinedenv
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
undefinedNEXT_PUBLIC_STORE_ID=your-store-id
NEXT_PUBLIC_API_KEY=your-api-key
NEXT_BUILD_CACHE_TOKENS=true # 开启token缓存提升构建速度
undefinedKey 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 with CE product fields for meta tags, Open Graph, and structured data:
generateMetadatatypescript
// 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 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()
使用Next.js的方法结合CE商品字段生成meta标签、Open Graph和结构化数据:
generateMetadatatypescript
// 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字段 |
|---|---|
| |
| |
| |
| |
| 标准URL | 基于 |
分类/商品列表页请使用返回的分类名称和描述,搜索页请使用搜索关键词作为元数据内容。listCategories()
Common Pitfalls
常见陷阱
| 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 |
| 等级 | 问题 | 解决方案 |
|---|---|---|
| 严重 | 服务端组件中未传入 | 服务端获取用户专属数据时请使用 |
| 严重 | 在服务端组件而非Actions中处理鉴权 | 会返回token的鉴权接口必须在Server Actions中调用,不能在服务端组件中调用 |
| 高 | 未引入 | 根布局中必须引入该组件,用于自动匿名鉴权和会话保持 |
| 高 | 在客户端组件中使用 | 客户端组件直接使用 |
| 中 | 构建速度慢 | 配置 |
| 中 | 根布局未传入 | 根布局运行在请求上下文之外,需使用 |
See Also
相关内容
- - Basic SDK installation
setup/ - - Authentication flows
auth/ - - Cart management
cart-checkout/
- - SDK基础安装教程
setup/ - - 鉴权流程说明
auth/ - - 购物车管理教程
cart-checkout/
Documentation
文档链接
- Next.js Integration: https://www.commercengine.io/docs/sdk/nextjs-integration
- Token Management: https://www.commercengine.io/docs/sdk/token-management
- LLM Reference: https://llm-docs.commercengine.io/sdk/