Skip to main content
The root entry runs the same on a server as in a browser. Server rendering keeps your catalog indexable and lets the server and the browser share one cart through cookies.

One config, one client per request

A server serves many shoppers, so a client must be bound to this request’s cookies. Keep one long-lived config and bind a client per request with withStorage():
src/kit.server.ts
createRequestCookieStorage(source, options) reads cookies straight off a Request, a Headers object or a raw Cookie header string. With collect, every write is appended as a serialized Set-Cookie string for you to attach to the response. withStorage() returns a copy of the client bound to that storage.
Without per-request storage, the server forgets the cart. A client with no cookie accessor on a server resolves to memory storage, so cart.add() creates a new cart each time and checkout.start() without cartId hands off an empty cart, which is refused with a 400.

Reads versus writes

Cookies must not be httpOnly if browser code also reads the cart (the elements, useCart, the shared cart store). The default leaves httpOnly off.

Hono and Cloudflare Workers

src/index.ts
On Workers, read the key from the env binding (c.env.QUICKBUTIK_PUBLISHABLE_KEY) instead of process.env.

Express

Express has no Request object, so pass the raw Cookie header string as the source:
Deriving origin from the Host header is convenient for successUrl. For canonical URLs in SEO tags, use a fixed configured origin instead, because a canonical built from an attacker-supplied Host is a known SEO-poisoning vector.

Astro

Fetch in the frontmatter with a request-bound client, put the buildSeo() output in the layout <head>, and use the web components in a client <script> for the interactive parts:
src/pages/products/[slug].astro
Server-rendered HTML with the elements on top is the combination that keeps the catalog indexable and the cart interactive. serializeJsonLd() escapes every <, so merchant content can’t close the script tag. For a cart that works without JavaScript, post forms to an API endpoint that uses the Hono-style pattern above.

Build time: static params and sitemaps

products.listAll() pages through the whole catalog (bounded at 200 pages). It’s for build time and sitemaps, not for a request path:
Building a slug → id map at deploy time this way also avoids getBySlug() walking pages on a hot path. After that, look products up with products.get(id), which is a single request.

Bring your own storage adapter

Any object with kind, get, set and remove works as storage, and each method may return a promise. That covers a signed cookie, a Redis or KV session, or a test double:
More on cookies, adapters and how "auto" storage resolves: Storage & SSR.