Skip to main content
This quickstart connects a Next.js App Router project directly to the public Storefront API. No BFF or browser API key is required.

Prerequisites

  • Node.js 20+
  • A published merchant slug and location ID
  • The exact browser origin for local, preview, and production deployments registered with Crave
  • A Stripe publishable key when you add checkout

1. Create the app and install the SDK

2. Configure the public origin

Create a .env.local file with public configuration only:
The hosted cravejs-storefront-template does not duplicate tenant selection in production environment variables. It derives the merchant from the controlled storefront host/subdomain and the location from the [locationId] route. Keep the prefixed merchant and location variables only for a standalone quickstart or local override. The hosted template also derives its canonical origin from the request host; set NEXT_PUBLIC_STOREFRONT_URL only when an explicit metadata override is required.
Do not put Crave private API keys, Stripe secret keys, provider credentials, cart capabilities, customer JWTs, or receipt capabilities in public environment variables.
The SDK requires an explicit baseUrl; it never derives an origin from a credential.

3. Create the browser client and scoped stores

The environment, merchant, and location scopes prevent one storefront’s capabilities from being reused by another. For signed-in customer flows, create the browser client only after Clerk is ready and add getAuthToken: () => getToken({ template: "crave-customer" }); keep getMerchantSlug so the SDK can send explicit tenant routing. Clerk owns the session and token refresh—do not copy the JWT into application storage. Capture a receipt capability from the URL fragment, remove the fragment immediately, and keep it with receiptTokenStore.set(receiptId, token) only while the receipt is needed.

4. Create a separate server client

Use the server client only for anonymous published merchant, location, menu, product, distance, order-time, and gratuity calls. It deliberately has no sessionStore or getAuthToken. Never import the browser client into a Server Component, Route Handler, or Server Action.

5. Start a cart and mutate it

The SDK stores the returned cart capability, sends it only in a header, updates the saved revision from ETag, and generates idempotency keys. A CART_CONFLICT is surfaced to your UI; reload the cart before asking the shopper to retry. Clear the guest capability after claim, deletion, expiry, or terminal checkout handling, let Clerk clear its session on logout, and clear receipt capabilities after terminal display.

6. Complete checkout safely

For a signed-in shopper who still has a guest-capability cart, opt into the customer context when discount validation or provider customer association needs it:
Omit includeCustomerContext for a guest checkout. A claimed cart already uses the customer JWT because its guest capability has been cleared. If the outcome is CUSTOMER_ACTION_REQUIRED, confirm its Stripe client secret using the exact returned connected account, then poll storefront.checkout.getOrderSubmissionOutcome(locationId, cart.id, outcome.attemptId). Treat only PAID as an online-payment success. Clear the cart session only after terminal handling.

7. Register every browser origin

Register the exact origin for local development, each stable preview, and production before testing browser calls. An origin includes the scheme, hostname, and any non-default port. Do not request a wildcard and do not send a cart capability or customer JWT while working around a CORS failure. See Deployment.

Next steps

Manage Cart

Add fulfillment, discounts, gratuity, and conflict handling.

Checkout Flow

Confirm payment and poll the authoritative order result.

Storefront SDK

Review the complete typed client surface.

Deployment

Configure exact origins and production environment variables.