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

> Wire @commercengine/seo and @commercengine/ai into a Next.js App Router storefront with one SEO proxy and one client agent-tools component.

Next.js is the lowest-wiring server deployment: one pure public config, one server-bound SEO instance, one proxy/middleware for the discovery surfaces, and one Client Component for agent tools.

## 1. Create the shared public config

```typescript src/lib/commerce-seo.config.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { defineCommerceSeoConfig } from "@commercengine/seo/config";

export const commerceSeo = defineCommerceSeoConfig({
  site: {
    name: "Acme",
    url: "https://acme.example",
    brandName: "Acme",
    description: "Acme's official online store.",
    locale: "en_US",
  },
  routes: {
    productBase: "/product",
    categoryBase: "/category",
  },
});

export const { site, routes } = commerceSeo;
```

## 2. Create the server-bound SEO instance

```typescript src/lib/seo.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { createCommerceSeo } from "@commercengine/seo";
import { commerceSeo } from "@/lib/commerce-seo.config";
import { storefront } from "@/lib/storefront";

export const seo = createCommerceSeo({ ...commerceSeo, storefront });
```

<Warning>
  Client Components must not import `@/lib/seo`. Import `site` and `routes` from `commerce-seo.config.ts`. The SEO instance holds the storefront and belongs on the server/build side of the bundle boundary.
</Warning>

## 3. Serve every discovery surface from one proxy

Next.js 16 uses `proxy.ts`. On Next.js 15, export the same handler from `middleware.ts`.

```typescript src/proxy.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { createNextjsSeoProxy } from "@commercengine/seo/nextjs/server";
import { seo } from "@/lib/seo";

export default createNextjsSeoProxy(seo, {
  robots: true,
  sitemap: true,
});

export const config = {
  matcher: [
    "/product/:path*",
    "/category/:path*",
    "/search",
    "/search.md",
    "/llms.txt",
    "/sitemap.md",
    "/robots.txt",
    "/sitemap.xml",
    "/sitemap/:path*",
  ],
};
```

This one handler owns:

* product/category Markdown mirrors
* same-URL Markdown negotiation
* `/llms.txt`
* `/sitemap.md`
* `robots.txt`
* `sitemap.xml` and shards beyond the URL limit

You do not need eleven separate App Router route files.

### Static/prerendered Next output

If the deployment has no Next server at request time, use the same prebuild strategy as a React/Vite app and write assets into `public/` with `@commercengine/seo/build`.

## 4. Generate native Next metadata

Use the public storefront inside `generateMetadata`:

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

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

  if (!data?.product) return { title: "Product Not Found" };
  return createProductMetadata(seo, data.product);
}
```

If public/CMS slugs differ from CE slugs, resolve the route first:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const identifier = await seo.resolveProductRoute(slug);
if (!identifier) return { title: "Product Not Found" };
```

Category pages use `createCategoryMetadata()`.

## 5. Render product JSON-LD and breadcrumbs

Next Metadata covers normal head metadata. Render JSON-LD in the page body:

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
const productSchema = await seo.productJsonLd(product);

const category = product.categories?.[0];
const [productUrl, categoryUrl] = await Promise.all([
  seo.productUrl(product),
  category ? seo.categoryUrl(category) : Promise.resolve(null),
]);

const breadcrumb = seo.breadcrumbJsonLd([
  { name: "Home", url: seo.config.site.url },
  ...(category && categoryUrl ? [{ name: category.name, url: categoryUrl }] : []),
  { name: product.name, url: productUrl ?? seo.config.site.url },
]);

return (
  <>
    {[productSchema, breadcrumb].map((schema) => (
      <script
        key={schema["@type"]}
        type="application/ld+json"
        dangerouslySetInnerHTML={{
          __html: JSON.stringify(schema).replace(/</g, "\\u003c"),
        }}
      />
    ))}
    {/* page */}
  </>
);
```

You can use your own safe JSON-LD serializer or the package serializer. Do not emit a second hand-written `Product` schema for the same item.

### Product-specific Open Graph properties

`productOpenGraphTags()` from `@commercengine/seo/nextjs` exposes product Open Graph fields that do not map cleanly into the generic Next Metadata shape:

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { productOpenGraphTags } from "@commercengine/seo/nextjs";

{productOpenGraphTags(product).map((tag) => (
  <meta
    key={`${tag.property}:${tag.content}`}
    property={tag.property}
    content={tag.content}
  />
))}
```

## 6. Register WebMCP in a root Client Component

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

import { createHostedCheckoutBridge } from "@commercengine/ai/checkout";
import { registerCommerceWebMcp } from "@commercengine/ai/webmcp";
import { getCheckout } from "@commercengine/checkout";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { routes, site } from "@/lib/commerce-seo.config";
import { storefront } from "@/lib/storefront";

export function AgentTools() {
  const router = useRouter();

  useEffect(() => {
    let registration: AbortController | null = null;
    let cancelled = false;

    void registerCommerceWebMcp({
      storefront,
      siteUrl: site.url,
      routes,
      checkout: createHostedCheckoutBridge({ getState: () => getCheckout() }),
      navigation: { navigate: (url) => router.push(url) },
      diagnostics:
        process.env.NODE_ENV === "development"
          ? (event) => console.info("[commerce-ai]", event.code, event.message ?? "")
          : undefined,
    }).then((controller) => {
      if (cancelled) controller?.abort();
      else registration = controller;
    });

    return () => {
      cancelled = true;
      registration?.abort();
    };
  }, [router]);

  return null;
}
```

Mount `<AgentTools />` once in the root layout. It returns no UI.

<Tip>
  Do not wait for `storefront.bootstrap()` before registering the tools. Registration only declares them; session-dependent tools already report a retryable failure until the browser session is ready.
</Tip>

## 7. Hosted Checkout remains human-controlled

If you already use Hosted Checkout, keep its normal root bootstrap. The AI bridge only exposes `open_cart`, `open_checkout`, and optionally `open_login`; cart mutations themselves use the authenticated Storefront SDK.

See [Hosted Checkout + Next.js](/docs/sdk/nextjs-integration#hosted-checkout--nextjs) for the session synchronization pattern.

## Production checklist

* `proxy.ts` matcher includes the `.md`, robots, sitemap, and discovery paths you enable
* `generateMetadata` uses `publicStorefront()` rather than a live session accessor
* PDP has exactly one product schema plus its breadcrumb schema
* Client AgentTools imports `commerce-seo.config`, not the server SEO module
* `registration?.abort()` runs on unmount
* development console contains the Commerce AI registration diagnostic
* direct requests to `/product/example.md`, `/llms.txt`, and `/sitemap.xml` return successfully

<Card title="Reference starter" icon="github" href="https://github.com/tark-ai/ce-starter-projects/tree/main/apps/soja-next">
  Full Next.js production storefront with both packages wired.
</Card>
