ce-ssr-patterns

SSR/meta-framework patterns for Commerce Engine using @commercengine/storefront. Covers Next.js and TanStack Start with publicStorefront(), clientStorefront(), serverStorefront(), bootstrap, server functions/actions, pre-rendering, and cookie-based token management.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "ce-ssr-patterns" with this command: npx skills add commercengine/skills/commercengine-skills-ce-ssr-patterns

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.

SSR / Meta-Framework Patterns

For basic installation, see setup/. For building custom bindings with @commercengine/ssr-utils (SvelteKit, Nuxt, Astro), see ssr/.

This skill covers frameworks with first-party Commerce Engine wrappers: Next.js and TanStack Start. Both ship as subpath exports of @commercengine/storefront.

Impact Levels

  • CRITICAL - Session continuity bugs, auth bugs
  • HIGH - Common mistakes
  • MEDIUM - Performance or maintainability issues

References

ReferenceWhen to Use
references/nextjs.mdCRITICAL - Next.js setup, accessor rules, Server Actions, SSG
references/tanstack-start.mdCRITICAL - TanStack Start setup, server functions, route loaders, pre-rendering
references/token-management.mdHIGH - Token flow, bootstrap pattern, cookie configuration

Shared Mental Model

All first-party SSR wrappers follow the same three-accessor pattern:

AccessorEnvironmentPurpose
publicStorefront()Server or clientBuild-safe public reads (catalog, categories, store config). API-key-backed, no session.
clientStorefront()Client onlyBrowser-side session client. Throws if called on server.
serverStorefront()Server onlyRequest-bound session with cookie-backed token storage. Next.js: await storefront.serverStorefront(). TanStack Start: serverStorefront() from the app-local server-only module.

Bootstrap

SSR apps must mount a client component that calls storefront.bootstrap() in the root layout. This establishes the anonymous session cookie before later serverStorefront() reads depend on it.

// Works identically for Next.js and TanStack Start
"use client"; // Next.js only — TanStack Start components are universal

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

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

Mount this once in the root layout (app/layout.tsx for Next.js, __root.tsx for TanStack Start). In Next.js, the root layout is a Server Component by default — this is fine. Server Components can render Client Components as children; only the StorefrontBootstrap component itself runs on the client. Place it as high in the tree as possible so the session is established before any downstream components need it.

Why bootstrap matters: Cold server-component reads without prior client bootstrap cannot reliably persist session cookies. The first session-bound call in a Server Component may create an anonymous token for that request, but session continuity is not guaranteed without the client bootstrap.

Pre-rendering for SEO & Initial Load

Use publicStorefront() in route loaders (TanStack Start) or generateStaticParams / Server Components (Next.js) to pre-render catalog pages. This gives you excellent SEO and fast initial page loads:

// Route loader (TanStack Start) or Server Component (Next.js)
const sdk = storefront.publicStorefront();
const { data } = await sdk.catalog.listProducts({ limit: 100 });

publicStorefront() never creates sessions, never reads/writes cookies, and is safe for build-time, pre-render, and ISR contexts.

Client-Side Fetching After Hydration

Once the app is hydrated, client-side fetching is equally fast and much less complex. There is no need to wrap SDK calls in server functions — the SDK talks to a public API with no secrets to protect. Use React Query (or any fetching library) directly:

// Public reads — no session needed
const sdk = storefront.publicStorefront();
const { data } = await sdk.catalog.listProducts({ limit: 20 });

// Session-bound reads (cart, wishlist, account) — use client accessor
const sessionSdk = storefront.clientStorefront();
const { data: wishlist } = await sessionSdk.cart.getWishlist();

The session-aware overloads resolve user_id automatically — no manual ID passing needed.

Framework-Specific Setup

Next.js

// lib/storefront.ts
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_" },
});

Accessor rules:

  • storefront.publicStorefront() — Root layouts, SSG, metadata, public reads
  • storefront.clientStorefront() — Client Components (throws on server)
  • await storefront.serverStorefront() — Server Components, Server Actions, Route Handlers (auto-reads cookies via next/headers)

See references/nextjs.md for full patterns and examples.

TanStack Start

// lib/storefront.ts
import { Environment } from "@commercengine/storefront";
import { createTanStackStartStorefront } from "@commercengine/storefront/tanstack-start";

export const storefrontConfig = {
  storeId: import.meta.env.VITE_STORE_ID,
  apiKey: import.meta.env.VITE_API_KEY,
  environment: Environment.Staging,
  tokenStorageOptions: { prefix: "myapp_" },
};

export const storefront = createTanStackStartStorefront(storefrontConfig);
// lib/storefront.server.ts (server-only module)
import { createTanStackStartServerStorefront } from "@commercengine/storefront/tanstack-start/server";
import { storefrontConfig } from "./storefront";

const factory = createTanStackStartServerStorefront(storefrontConfig);

export function serverStorefront() {
  return factory.serverStorefront();
}

Accessor rules:

  • storefront.publicStorefront() — Route loaders, pre-rendering, public reads
  • storefront.clientStorefront() — Client-side components (throws on server)
  • serverStorefront() — Server functions (from separate /server entry)

See references/tanstack-start.md for full patterns and examples.

Hosted Checkout + SSR

When using Hosted Checkout with an SSR framework, the Storefront SDK remains the session owner. Wire onTokensUpdated in the storefront config so checkout stays in sync on the client side:

// In storefront config (both frameworks)
onTokensUpdated: (accessToken, refreshToken) => {
  if (typeof window !== "undefined") {
    void import("@commercengine/checkout").then(({ getCheckout }) => {
      getCheckout().updateTokens(accessToken, refreshToken);
    });
  }
},

Then initialize checkout in the bootstrap/initializer component after storefront.bootstrap() completes. No provider wrapper needed — initCheckout is called once and useCheckout() works globally.

Callback signature difference: The SDK's onTokensUpdated receives two separate arguments (accessToken, refreshToken). Checkout's onTokensUpdated receives a single object ({ accessToken, refreshToken }). Do not mix these up.

useEffect(() => {
  const init = async () => {
    await storefront.bootstrap();

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

    initCheckout({
      storeId: /* ... */,
      apiKey: /* ... */,
      authMode: "provided",
      accessToken: accessToken ?? undefined,
      refreshToken: refreshToken ?? undefined,
      onTokensUpdated: ({ accessToken, refreshToken }) => {
        void sdk.setTokens(accessToken, refreshToken);
      },
    });
  };
  void init();
  return () => destroyCheckout();
}, []);

Common Pitfalls (All SSR Frameworks)

LevelIssueSolution
CRITICALUsing publicStorefront() for cart, auth, customer, order, or payment flowsUse the session accessor (serverStorefront() on server, clientStorefront() on client)
CRITICALUsing clientStorefront() on the serverUse the server accessor — clientStorefront() throws on the server
CRITICALUsing deprecated @commercengine/storefront-sdk-nextjsMigrate to @commercengine/storefront/nextjs with createNextjsStorefront()
HIGHMissing bootstrap component in root layoutMount a client component calling storefront.bootstrap() once in the root layout
HIGHBootstrapping anonymous auth in SSG/pre-render codeUse publicStorefront() instead — pre-render code must not create sessions
MEDIUMFetching session-aware data in the root layoutMove it into a request-aware boundary and use the server accessor

See Also

  • setup/ - Basic SDK installation and framework detection
  • ssr/ - Custom SSR bindings with @commercengine/ssr-utils (SvelteKit, Nuxt, Astro)
  • auth/ - Authentication flows
  • cart-checkout/ - Cart and Hosted Checkout patterns

Documentation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

ce-cart-checkout

No summary provided by upstream source.

Repository SourceNeeds Review
General

ce-auth

No summary provided by upstream source.

Repository SourceNeeds Review
General

ce-orders

No summary provided by upstream source.

Repository SourceNeeds Review
General

ce

No summary provided by upstream source.

Repository SourceNeeds Review