> ## Documentation Index
> Fetch the complete documentation index at: https://www.commercengine.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js

> Integrate the Commerce Engine Storefront SDK into Next.js with server-side session management, cookie-backed tokens, and three distinct accessors for every rendering context.

The `@commercengine/storefront/nextjs` package provides a Next.js-specific wrapper around the core Storefront SDK. It gives you three accessors — `publicStorefront()`, `serverStorefront()`, and `clientStorefront()` — so the right token strategy is used automatically in every rendering context.

### NPM Package

<Card title="@commercengine/storefront" icon="npm" href="https://www.npmjs.com/package/@commercengine/storefront">
  Next.js integration for Commerce Engine Storefront SDK. Import from `@commercengine/storefront/nextjs`.
</Card>

<Warning>
  `@commercengine/storefront-sdk-nextjs` is **deprecated**. See the [migration table](#migration-from-deprecated-package) below if you are upgrading.
</Warning>

## Installation

<CodeGroup>
  ```bash npm theme={"theme":{"light":"github-light","dark":"github-dark"}}
  npm install @commercengine/storefront
  ```

  ```bash pnpm theme={"theme":{"light":"github-light","dark":"github-dark"}}
  pnpm add @commercengine/storefront
  ```

  ```bash yarn theme={"theme":{"light":"github-light","dark":"github-dark"}}
  yarn add @commercengine/storefront
  ```
</CodeGroup>

## Quick Start

<Steps>
  <Step title="Set Environment Variables">
    Add your store credentials to `.env.local`:

    ```bash .env.local theme={"theme":{"light":"github-light","dark":"github-dark"}}
    NEXT_PUBLIC_STORE_ID=your-store-id
    NEXT_PUBLIC_API_KEY=your-api-key
    ```

    <Info>
      `NEXT_PUBLIC_API_KEY` is safe for client-side use — it is scoped to public storefront operations.
    </Info>
  </Step>

  <Step title="Create the Storefront Config">
    ```typescript lib/storefront.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import { Environment } from "@commercengine/storefront";
    import { createNextjsStorefront } from "@commercengine/storefront/nextjs";

    export const storefront = createNextjsStorefront({
      storeId: process.env.NEXT_PUBLIC_STORE_ID!,
      apiKey: process.env.NEXT_PUBLIC_API_KEY!,
      environment: Environment.Staging, // or Environment.Production
      tokenStorageOptions: { prefix: "myapp_" },
    });
    ```

    <Info>
      `createNextjsStorefront()` does **not** infer credentials from environment variables — you must pass `storeId` and `apiKey` explicitly.
    </Info>
  </Step>

  <Step title="Create the StorefrontBootstrap Component">
    A Client Component that establishes the anonymous session on first visit. It renders nothing visible.

    ```tsx components/storefront-bootstrap.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
    "use client";

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

    export function StorefrontBootstrap() {
      useEffect(() => {
        storefront.bootstrap().catch(console.error);
      }, []);
      return null;
    }
    ```

    <Tip>
      `bootstrap()` is deduped and idempotent. If session cookies already exist (returning user), it is a no-op.
    </Tip>
  </Step>

  <Step title="Mount in Root Layout">
    The root layout stays a Server Component — `StorefrontBootstrap` is a Client Component child rendered inside it.

    ```tsx app/layout.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import { storefront } from "@/lib/storefront";
    import { StorefrontBootstrap } from "@/components/storefront-bootstrap";

    const { data: storeConfig } = await storefront
      .publicStorefront()
      .store.getStoreConfig();

    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <StorefrontBootstrap />
            <header>{storeConfig?.store_config?.brand.name}</header>
            {children}
          </body>
        </html>
      );
    }
    ```

    <Warning>
      Always use `publicStorefront()` in the root layout. Do not call `serverStorefront()` or `clientStorefront()` here — the root layout should not participate in the user session.
    </Warning>
  </Step>
</Steps>

## Accessor Rules

Use the right accessor for each rendering context:

| Context                               | Accessor                              | Why                                                    |
| ------------------------------------- | ------------------------------------- | ------------------------------------------------------ |
| Root Layout                           | `storefront.publicStorefront()`       | Root layouts should stay public — no live user session |
| Build time / SSG / `generateMetadata` | `storefront.publicStorefront()`       | No request context at build time                       |
| Public Server Component               | `storefront.publicStorefront()`       | Public reads that don't need a session                 |
| Session-aware Server Component        | `await storefront.serverStorefront()` | Auto-reads cookies via `next/headers`                  |
| Server Action / Route Handler         | `await storefront.serverStorefront()` | Can read and write cookies                             |
| Client Component                      | `storefront.clientStorefront()`       | Uses the browser-side session client                   |

<Info>
  `serverStorefront()` is **async** — it dynamically imports `next/headers` and uses React `cache()` to dedupe within a single request. `clientStorefront()` **throws** if called on the server. `serverStorefront()` **throws** if called in the browser.
</Info>

## Key Patterns

### Public Server Component

Fetch catalog data without touching the user session.

```tsx app/products/page.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { storefront } from "@/lib/storefront";

export default async function ProductsPage() {
  const sdk = storefront.publicStorefront();
  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>
  );
}
```

### Session-Aware Server Component

Read session-scoped data such as account details or wish lists.

```tsx app/account/page.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { storefront } from "@/lib/storefront";

export default async function AccountPage() {
  const sdk = await storefront.serverStorefront();
  const { data, error } = await sdk.customer.getCustomer();

  if (error) return <p>Error: {error.message}</p>;
  return <div>Welcome, {data?.customer?.first_name}</div>;
}
```

### Server Actions (Mutations)

Server Actions can read and write cookies, making them ideal for auth and cart mutations.

```tsx app/actions.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
"use server";

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

export async function loginWithEmail(email: string) {
  const sdk = await storefront.serverStorefront();

  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 addToCartAction(
  cartId: string,
  productId: string,
  variantId: string | null,
) {
  const sdk = await storefront.serverStorefront();
  const { data, error } = await sdk.cart.addDeleteCartItem(
    { id: cartId },
    { product_id: productId, variant_id: variantId, quantity: 1 }
  );
  return { data: data?.cart, error: error?.message };
}
```

### Static Site Generation (SSG)

Use `publicStorefront()` for `generateStaticParams` and any build-time rendering — no session is available at build time.

```tsx app/products/[slug]/page.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { storefront } from "@/lib/storefront";

export async function generateStaticParams() {
  const sdk = storefront.publicStorefront();
  const { data } = await sdk.catalog.listProducts({ limit: 100 });

  return (data?.products ?? []).map((product) => ({
    slug: product.slug || product.id,
  }));
}

export default async function ProductPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const sdk = storefront.publicStorefront();
  const { data, error } = await sdk.catalog.getProductDetail({
    product_id_or_slug: slug,
  });

  if (error || !data) return <p>Product not found</p>;
  return <h1>{data.product.name}</h1>;
}
```

### Client Component

Use `clientStorefront()` for browser-side interactions like add-to-cart buttons.

```tsx components/add-to-cart-button.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
"use client";

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

export function AddToCartButton({
  cartId,
  productId,
  variantId,
}: {
  cartId: string;
  productId: string;
  variantId: string | null;
}) {
  async function handleClick() {
    const sdk = storefront.clientStorefront();
    await sdk.cart.addDeleteCartItem(
      { id: cartId },
      { product_id: productId, variant_id: variantId, quantity: 1 }
    );
  }

  return <button onClick={handleClick}>Add to Cart</button>;
}
```

### SEO Metadata

`generateMetadata` runs at build time or request time without a live session — use `publicStorefront()`.

```tsx app/products/[slug]/page.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import type { Metadata } from "next";
import { storefront } from "@/lib/storefront";

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}): Promise<Metadata> {
  const { slug } = await params;
  const sdk = storefront.publicStorefront();
  const { data } = await sdk.catalog.getProductDetail({
    product_id_or_slug: slug,
  });

  const product = data?.product;
  if (!product) return { title: "Product Not Found" };

  return {
    title: product.name,
    description: product.short_description ?? undefined,
  };
}
```

## Session Helpers

The storefront config accepts `tokenStorageOptions` to control cookie behavior:

| Property   | Default             | Notes                        |
| ---------- | ------------------- | ---------------------------- |
| `prefix`   | `"ce_"`             | Cookie name prefix           |
| `maxAge`   | `2592000` (30 days) | Cookie max-age in seconds    |
| `path`     | `"/"`               | Available across all routes  |
| `domain`   | ---                 | Defaults to request domain   |
| `secure`   | auto-detected       | `true` in production (HTTPS) |
| `sameSite` | `"lax"`             | CSRF protection              |

Client and server cookie configurations are automatically aligned — you do not need to configure them separately.

For the full token lifecycle and returning-user flow, see [Token Management](/docs/sdk/token-management).

## Hosted Checkout + Next.js

If you use the [Commerce Engine Hosted Checkout](/docs/hosted-checkout/react), follow this three-step pattern to keep the storefront SDK session and checkout session in sync. No provider wrapper is needed.

<Steps>
  <Step title="Add onTokensUpdated to Storefront Config">
    This callback forwards token updates from the storefront SDK to the checkout iframe.

    ```typescript lib/storefront.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import { Environment } from "@commercengine/storefront";
    import { createNextjsStorefront } from "@commercengine/storefront/nextjs";

    export const storefront = createNextjsStorefront({
      storeId: process.env.NEXT_PUBLIC_STORE_ID!,
      apiKey: process.env.NEXT_PUBLIC_API_KEY!,
      environment: Environment.Staging,
      tokenStorageOptions: { prefix: "myapp_" },
      onTokensUpdated: (accessToken, refreshToken) => {
        if (typeof window !== "undefined") {
          void import("@commercengine/checkout").then(({ getCheckout }) => {
            getCheckout().updateTokens(accessToken, refreshToken);
          });
        }
      },
    });
    ```
  </Step>

  <Step title="Bootstrap + Init Checkout in a Root Client Component">
    Call both `storefront.bootstrap()` and `initCheckout()` in a single Client Component. The checkout is initialized with `authMode: "provided"` so the storefront SDK owns the session.

    ```tsx components/storefront-bootstrap.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
    "use client";

    import { useEffect } from "react";
    import { initCheckout, destroyCheckout } from "@commercengine/checkout/react";
    import { storefront } from "@/lib/storefront";

    export function StorefrontBootstrap() {
      useEffect(() => {
        async function init() {
          await storefront.bootstrap();

          const sdk = storefront.clientStorefront();
          const accessToken = await sdk.getAccessToken();
          const refreshToken = await sdk.session.peekRefreshToken();

          initCheckout({
            storeId: process.env.NEXT_PUBLIC_STORE_ID!,
            apiKey: process.env.NEXT_PUBLIC_API_KEY!,
            authMode: "provided",
            accessToken: accessToken ?? undefined,
            refreshToken: refreshToken ?? undefined,
            onTokensUpdated: ({ accessToken, refreshToken }) => {
              void sdk.setTokens(accessToken, refreshToken);
            },
          });
        }

        init();
        return () => destroyCheckout();
      }, []);

      return null;
    }
    ```
  </Step>

  <Step title="Mount in Root Layout">
    ```tsx app/layout.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import { StorefrontBootstrap } from "@/components/storefront-bootstrap";

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

    Then use `useCheckout()` in any Client Component — no provider needed:

    ```tsx components/cart-button.tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
    "use client";
    import { useCheckout } from "@commercengine/checkout/react";

    function CartButton() {
      const { cartCount, openCart, isReady } = useCheckout();
      return (
        <button onClick={openCart} disabled={!isReady}>Cart ({cartCount})</button>
      );
    }
    ```
  </Step>
</Steps>

## Migration from Deprecated Package

If you are migrating from `@commercengine/storefront-sdk-nextjs`, use this mapping:

| Old (`storefront-sdk-nextjs`)          | New (`storefront/nextjs`)                                               |
| -------------------------------------- | ----------------------------------------------------------------------- |
| `@commercengine/storefront-sdk-nextjs` | `@commercengine/storefront/nextjs`                                      |
| `createStorefront()`                   | `createNextjsStorefront({ storeId, apiKey, ... })`                      |
| `storefront.public()`                  | `storefront.publicStorefront()`                                         |
| `storefront.session()` (client)        | `storefront.clientStorefront()`                                         |
| `storefront.session(await cookies())`  | `await storefront.serverStorefront()`                                   |
| `StorefrontSDKInitializer`             | Custom `StorefrontBootstrap` component calling `storefront.bootstrap()` |
| Env var auto-inference                 | Explicit `storeId` and `apiKey` in config                               |
| `storefront({ isRootLayout: true })`   | `storefront.publicStorefront()`                                         |
| `NEXT_BUILD_CACHE_TOKENS` env flag     | Not needed — use `publicStorefront()` for build-time reads              |

## Common Pitfalls

| Level    | Issue                                                     | Solution                                                                              |
| -------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| CRITICAL | Using `publicStorefront()` for cart, auth, or order flows | Use `await serverStorefront()` on the server or `clientStorefront()` on the client    |
| CRITICAL | Using `clientStorefront()` on the server                  | `clientStorefront()` throws on the server — use `await storefront.serverStorefront()` |
| HIGH     | Missing `StorefrontBootstrap` in root layout              | Mount a Client Component calling `storefront.bootstrap()` in the root layout          |
| HIGH     | Calling `serverStorefront()` in a Client Component        | Client Components must use `storefront.clientStorefront()`                            |
| HIGH     | Using session accessors in SSG or root layout             | Use `storefront.publicStorefront()` for build-time and root-layout reads              |
| MEDIUM   | Session-aware data in the root layout                     | Move it into a nested Server Component and use `await storefront.serverStorefront()`  |

## Best Practices

<CardGroup cols={2}>
  <Card title="One Config File" icon="file-code">
    Create a single `lib/storefront.ts` that calls `createNextjsStorefront()` and export `storefront` from it. Import this everywhere.
  </Card>

  <Card title="Right Accessor, Right Context" icon="arrows-split-up-and-left">
    `publicStorefront()` for public reads, `serverStorefront()` for session-aware server code, `clientStorefront()` for browser interactions.
  </Card>

  <Card title="Server Actions for Mutations" icon="bolt">
    Auth, cart updates, and order creation should go through Server Actions using `await serverStorefront()` so cookies are read and written correctly.
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation">
    Every SDK call returns `{ data, error }`. Always check `error` before using `data` and implement error boundaries for graceful failures.
  </Card>
</CardGroup>

## Cross-References

<CardGroup cols={2}>
  <Card title="SDK Installation" icon="download" href="/docs/sdk/installation">
    Core SDK setup and framework detection guide.
  </Card>

  <Card title="Token Management" icon="key" href="/docs/sdk/token-management">
    Full token lifecycle, cookie hydration, and returning-user flow.
  </Card>

  <Card title="Authentication Guide" icon="user-shield" href="/docs/storefront/authentication">
    OTP, email, and social login patterns for storefronts.
  </Card>

  <Card title="Hosted Checkout" icon="cart-shopping" href="/docs/hosted-checkout/react">
    Full hosted checkout integration with the React hook API.
  </Card>
</CardGroup>
