Skip to main content
The @commercengine/storefront/astro package provides an Astro-specific wrapper around the core Storefront SDK. It gives you three accessors — publicStorefront(), serverStorefront(), and clientStorefront() — with automatic cookie-backed token management for SSR and hybrid rendering.

NPM Package

@commercengine/storefront

Astro integration for Commerce Engine Storefront SDK. Import from @commercengine/storefront/astro.

Installation

npm install @commercengine/storefront

Quick Start

1

Set Environment Variables

Add your store credentials to .env:
.env
PUBLIC_STORE_ID=your-store-id
PUBLIC_API_KEY=your-api-key
Astro uses the PUBLIC_ prefix for client-exposed env vars (accessed via import.meta.env).
2

Create the Storefront Config

A shared config imported by both client and server storefronts:
src/lib/storefront-config.ts
import { Environment } from "@commercengine/storefront";
import type { AstroStorefrontConfig } from "@commercengine/storefront/astro";

export const storefrontConfig: AstroStorefrontConfig = {
  storeId: import.meta.env.PUBLIC_STORE_ID,
  apiKey: import.meta.env.PUBLIC_API_KEY,
  environment: Environment.Staging, // or Environment.Production
  tokenStorageOptions: { prefix: "myapp_" },
};
3

Create the Client Storefront

src/lib/storefront.ts
import { createAstroStorefront } from "@commercengine/storefront/astro";
import { storefrontConfig } from "./storefront-config";

export const storefront = createAstroStorefront(storefrontConfig);
4

Create the Server Storefront

The server entry is isolated from the client entry to prevent server code from leaking into browser bundles.
src/lib/server-storefront.ts
import { createAstroServerStorefront } from "@commercengine/storefront/astro/server";
import { storefrontConfig } from "./storefront-config";

export const serverStorefront = createAstroServerStorefront(storefrontConfig);
Never import server-storefront.ts in client scripts or framework islands. Server storefront must only be used in .astro frontmatter, API routes, or middleware.
5

Bootstrap in Root Layout

src/layouts/Layout.astro
---
// src/layouts/Layout.astro
---

<html lang="en">
  <head><slot name="head" /></head>
  <body>
    <slot />

    <script type="module">
      import { storefront } from "../lib/storefront";

      const run = () => void storefront.bootstrap().catch(console.error);

      document.addEventListener("astro:page-load", run);
      run();
    </script>
  </body>
</html>
The astro:page-load listener is important because Astro’s ClientRouter runs bundled scripts once and then swaps pages on later navigations. Listening for this event ensures bootstrap runs after each navigation.

Accessor Rules

Use the right accessor for each context:
ContextAccessorImport From
Frontmatter — public reads (catalog, categories)storefront.publicStorefront()src/lib/storefront
Browser scripts / client islandsstorefront.clientStorefront()src/lib/storefront
Client bootstrapstorefront.bootstrap()src/lib/storefront
SSR pages (.astro with output: "server")serverStorefront.serverStorefront(Astro.cookies)src/lib/server-storefront
API routes (src/pages/api/*.ts)serverStorefront.serverStorefront(context.cookies)src/lib/server-storefront
MiddlewareserverStorefront.serverStorefront(context.cookies)src/lib/server-storefront
serverStorefront() requires passing Astro.cookies (or context.cookies) because Astro has no global request context. This call is synchronous — no await needed. clientStorefront() throws if called on the server.

Key Patterns

Public Reads in Frontmatter

---
import { storefront } from "../lib/storefront";

const sdk = storefront.publicStorefront();
const { data } = await sdk.catalog.listCategories();
const categories = data?.categories ?? [];
---

<nav>
  {categories.map((category) => <a href={`/category/${category.slug}`}>{category.name}</a>)}
</nav>

Session-Aware Server Page

src/pages/account.astro
---
// src/pages/account.astro (SSR)
import { serverStorefront } from "../lib/server-storefront";

const sdk = serverStorefront.serverStorefront(Astro.cookies);
const { data, error } = await sdk.customer.getCustomer();

if (error) return Astro.redirect("/login");
---

<h1>Welcome, {data?.customer?.first_name}</h1>

API Routes (Session-Bound)

src/pages/api/wishlist.ts
import type { APIRoute } from "astro";
import { serverStorefront } from "../../lib/server-storefront";

export const GET: APIRoute = async ({ cookies }) => {
  const sdk = serverStorefront.serverStorefront(cookies);
  const { data, error } = await sdk.cart.getWishlist();

  if (error) return new Response(JSON.stringify({ error: error.message }), { status: 500 });
  return new Response(JSON.stringify(data));
};

export const POST: APIRoute = async ({ cookies, request }) => {
  const body = await request.json();
  const sdk = serverStorefront.serverStorefront(cookies);
  const { data, error } = await sdk.cart.addToWishlist({
    product_id: body.productId,
    variant_id: body.variantId ?? null,
  });

  if (error) return new Response(JSON.stringify({ error: error.message }), { status: 500 });
  return new Response(JSON.stringify(data));
};

Middleware

Use Astro middleware for auth guards and injecting session data into context.locals:
src/middleware.ts
import { defineMiddleware } from "astro:middleware";
import { serverStorefront } from "./lib/server-storefront";

export const onRequest = defineMiddleware(async (context, next) => {
  const sdk = serverStorefront.serverStorefront(context.cookies);
  const userId = await sdk.session.peekUserId();

  if (context.url.pathname.startsWith("/account") && !userId) {
    return context.redirect("/login");
  }

  context.locals.userId = userId;
  return next();
});

Client-Side Fetching (After Hydration)

Once the app is hydrated, use the client accessors directly in scripts or framework islands:
// In a <script> or framework component
import { storefront } from "../lib/storefront";

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

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

Hosted Checkout + Astro

Astro does not need a dedicated checkout binding — use the root @commercengine/checkout helpers. If you use Commerce Engine Hosted Checkout, follow this pattern to keep the storefront SDK session and checkout in sync.
1

Add onTokensUpdated to Storefront Config

This callback forwards token updates from the storefront SDK to the checkout iframe.
src/lib/storefront-config.ts
import { Environment } from "@commercengine/storefront";
import type { AstroStorefrontConfig } from "@commercengine/storefront/astro";

export const storefrontConfig: AstroStorefrontConfig = {
  storeId: import.meta.env.PUBLIC_STORE_ID,
  apiKey: import.meta.env.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);
      });
    }
  },
};
2

Bootstrap + Init Checkout in Root Layout

src/layouts/Layout.astro
---
// src/layouts/Layout.astro
---

<html lang="en">
  <head><slot name="head" /></head>
  <body>
    <slot />

    <script type="module">
      import { storefront } from "../lib/storefront";
      import { initCheckout } from "@commercengine/checkout";

      async function init() {
        await storefront.bootstrap();

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

        initCheckout({
          storeId: import.meta.env.PUBLIC_STORE_ID,
          apiKey: import.meta.env.PUBLIC_API_KEY,
          authMode: "provided",
          accessToken: accessToken ?? undefined,
          refreshToken: refreshToken ?? undefined,
          onTokensUpdated: ({ accessToken, refreshToken }) => {
            void sdk.setTokens(accessToken, refreshToken);
          },
        });
      }

      document.addEventListener("astro:page-load", () => void init());
      void init();
    </script>
  </body>
</html>
Install @commercengine/checkout separately if you use hosted checkout. The authMode: "provided" setting tells checkout to use your storefront SDK tokens instead of managing its own.

Common Pitfalls

LevelIssueSolution
CRITICALUsing publicStorefront() for cart, auth, or order flowsUse serverStorefront(cookies) on the server or clientStorefront() in the browser
CRITICALImporting server-storefront.ts in client scriptsServer storefront must only be used in .astro frontmatter, API routes, or middleware
HIGHMissing bootstrap in root layoutAdd bootstrap script with astro:page-load listener
HIGHForgetting to pass Astro.cookies to serverStorefront()Always pass Astro.cookies or context.cookies — there is no global cookie access
HIGHUsing @commercengine/ssr-utils directlyUse the first-party wrapper @commercengine/storefront/astro instead
MEDIUMNot listening for astro:page-load in bootstrapWithout it, bootstrap won’t re-run after ClientRouter page swaps

Best Practices

Shared Config

Export storefrontConfig from storefront-config.ts and import it in both client and server storefront modules. Keep configuration in one place.

Server Boundaries

Keep server-storefront.ts out of client scripts and islands. Only import it in .astro frontmatter, API routes, and middleware.

Public vs Session

Use publicStorefront() for catalog reads in frontmatter and serverStorefront(cookies) for user-scoped data in SSR pages. Never use clientStorefront() on the server.

Middleware for Auth

Use Astro middleware with serverStorefront(context.cookies) for route protection. Inject user data into context.locals for downstream pages.

Cross-References

SDK Installation

Core SDK setup and framework detection guide.

Token Management

Full token lifecycle, cookie hydration, and returning-user flow.

Authentication Guide

OTP, email, and social login patterns for storefronts.

Hosted Checkout

Hosted checkout integration guides for all frameworks.