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

# Astro

> First-class Astro wrapper with cookie-backed sessions, SSR pages, API routes, middleware, and static prerendering support.

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

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

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

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

    <Info>
      Astro uses the `PUBLIC_` prefix for client-exposed env vars (accessed via `import.meta.env`).
    </Info>
  </Step>

  <Step title="Create the Storefront Config">
    A shared config imported by both client and server storefronts:

    ```typescript src/lib/storefront-config.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    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_" },
    };
    ```
  </Step>

  <Step title="Create the Client Storefront">
    ```typescript src/lib/storefront.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import { createAstroStorefront } from "@commercengine/storefront/astro";
    import { storefrontConfig } from "./storefront-config";

    export const storefront = createAstroStorefront(storefrontConfig);
    ```
  </Step>

  <Step title="Create the Server Storefront">
    The server entry is isolated from the client entry to prevent server code from leaking into browser bundles.

    ```typescript src/lib/server-storefront.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import { createAstroServerStorefront } from "@commercengine/storefront/astro/server";
    import { storefrontConfig } from "./storefront-config";

    export const serverStorefront = createAstroServerStorefront(storefrontConfig);
    ```

    <Warning>
      Never import `server-storefront.ts` in client scripts or framework islands. Server storefront must only be used in `.astro` frontmatter, API routes, or middleware.
    </Warning>
  </Step>

  <Step title="Bootstrap in Root Layout">
    ```astro src/layouts/Layout.astro theme={"theme":{"light":"github-light","dark":"github-dark"}}
    ---
    // 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>
    ```

    <Tip>
      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.
    </Tip>
  </Step>
</Steps>

## Accessor Rules

Use the right accessor for each context:

| Context                                          | Accessor                                             | Import From                 |
| ------------------------------------------------ | ---------------------------------------------------- | --------------------------- |
| Frontmatter — public reads (catalog, categories) | `storefront.publicStorefront()`                      | `src/lib/storefront`        |
| Browser scripts / client islands                 | `storefront.clientStorefront()`                      | `src/lib/storefront`        |
| Client bootstrap                                 | `storefront.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` |
| Middleware                                       | `serverStorefront.serverStorefront(context.cookies)` | `src/lib/server-storefront` |

<Info>
  `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.
</Info>

## Key Patterns

### Public Reads in Frontmatter

```astro theme={"theme":{"light":"github-light","dark":"github-dark"}}
---
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

```astro src/pages/account.astro theme={"theme":{"light":"github-light","dark":"github-dark"}}
---
// 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)

```typescript src/pages/api/wishlist.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
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`:

```typescript src/middleware.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
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:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
// 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](/hosted-checkout/overview), follow this pattern to keep the storefront SDK session and checkout in sync.

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

    ```typescript src/lib/storefront-config.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    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);
          });
        }
      },
    };
    ```
  </Step>

  <Step title="Bootstrap + Init Checkout in Root Layout">
    ```astro src/layouts/Layout.astro theme={"theme":{"light":"github-light","dark":"github-dark"}}
    ---
    // 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>
    ```

    <Warning>
      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.
    </Warning>
  </Step>
</Steps>

## Common Pitfalls

| Level    | Issue                                                      | Solution                                                                               |
| -------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| CRITICAL | Using `publicStorefront()` for cart, auth, or order flows  | Use `serverStorefront(cookies)` on the server or `clientStorefront()` in the browser   |
| CRITICAL | Importing `server-storefront.ts` in client scripts         | Server storefront must only be used in `.astro` frontmatter, API routes, or middleware |
| HIGH     | Missing bootstrap in root layout                           | Add bootstrap script with `astro:page-load` listener                                   |
| HIGH     | Forgetting to pass `Astro.cookies` to `serverStorefront()` | Always pass `Astro.cookies` or `context.cookies` — there is no global cookie access    |
| HIGH     | Using `@commercengine/ssr-utils` directly                  | Use the first-party wrapper `@commercengine/storefront/astro` instead                  |
| MEDIUM   | Not listening for `astro:page-load` in bootstrap           | Without it, bootstrap won't re-run after `ClientRouter` page swaps                     |

## Best Practices

<CardGroup cols={2}>
  <Card title="Shared Config" icon="gear">
    Export `storefrontConfig` from `storefront-config.ts` and import it in both client and server storefront modules. Keep configuration in one place.
  </Card>

  <Card title="Server Boundaries" icon="shield-halved">
    Keep `server-storefront.ts` out of client scripts and islands. Only import it in `.astro` frontmatter, API routes, and middleware.
  </Card>

  <Card title="Public vs Session" icon="key">
    Use `publicStorefront()` for catalog reads in frontmatter and `serverStorefront(cookies)` for user-scoped data in SSR pages. Never use `clientStorefront()` on the server.
  </Card>

  <Card title="Middleware for Auth" icon="lock">
    Use Astro middleware with `serverStorefront(context.cookies)` for route protection. Inject user data into `context.locals` for downstream pages.
  </Card>
</CardGroup>

## Cross-References

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

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

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

  <Card title="Hosted Checkout" icon="cart-shopping" href="/hosted-checkout/overview">
    Hosted checkout integration guides for all frameworks.
  </Card>
</CardGroup>
