# Vercel Shop documentation

> Documentation for Vercel Shop — an agent-native, fast-by-default Shopify storefront built on Next.js.

## When to use Vercel Shop

- Category: Commerce
- Audience: Storefront developers, AI agents extending storefronts
Common use cases:

- Build a Shopify storefront on Next.js
- Extend a storefront with AI agents and skills
- Serve storefront content to AI agents via content negotiation

## Documentation

---
title: Introduction
description: Learn about the Vercel Shop template and what it offers.
url: "https://shop-docs.labs.vercel.dev/docs"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: overview
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Vercel Shop gives you a production-ready Shopify storefront built with Next.js. Connect Shopify, run the storefront, then adapt it to your business with your team or a coding agent.

The template includes product pages, collections, search, and a cart backed by Shopify. Customer accounts and the shopping assistant are optional. Configure webhooks to refresh cached products and collections when you edit them in Shopify.

Start with [Getting Started](/docs/getting-started). For the design goals behind the template, read [Why Use This](/docs/why-use-this).

Set up Shopify, create a project, and run the storefront locally.

Extend and customize the storefront with Claude Code or Cursor.

Decide whether an owned Next.js storefront fits your team.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Why Use This
description: An owned Shopify storefront with Next.js, Vercel, and guidance for agent-assisted development.
url: "https://shop-docs.labs.vercel.dev/docs/why-use-this"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: overview
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Vercel Shop gives your team an owned storefront without making you rebuild the foundation of headless commerce.

Shopify manages your catalog and checkout. Next.js and Vercel provide the storefront framework and hosting. Product pages, search, collections, and a cart are included, so your team can focus on the shopping experience.

The code lives in your repository. You can inspect it, change it, and give coding agents clear project conventions for future work. That makes agent-assisted development practical without giving up control of the storefront.

Start with a working store, then invest in the choices only your team can make.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: "AEO & GEO"
description: How the storefront makes itself legible to AI answer engines and generative search, with built-in content negotiation, structured data, and discovery surfaces.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/aeo-geo"
docs_index: /llms.txt
lastUpdated: 2026-09-16
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

**Answer Engine Optimization (AEO)** and **Generative Engine Optimization (GEO)** help AI services understand and cite your storefront. The template provides machine-readable product and collection content without changing the pages shoppers visit.

## Make storefront content readable to AI

The home page, product, collection, and search URLs return structured Markdown when a client requests `text/markdown`. Normal browser requests continue to receive HTML from the same URLs. No setup is required.

The storefront also publishes `/llms.txt`, a concise index for AI agents. It links to search, up to 50 collections, the sitemap, crawl guidance, and the storefront's UCP profile rather than duplicating the full catalog. The [sitemap](/docs/anatomy/sitemap) remains the complete URL inventory.

Markdown explains a product; it does not sell it. Live per-variant availability, cart, and checkout for agents come from Shopify's Universal Commerce Protocol (UCP) endpoints, which the storefront forwards at `/.well-known/ucp` and `/api/ucp/mcp`. Product Markdown points agents there instead of repeating per-variant stock.

Markdown content uses the deployment's configured Shopify country and language. If you add [Shopify Markets](/docs/skills/enable-shopify-markets), make sure AI-readable content uses the same market as the corresponding shopper page.

## Included discovery surfaces

| Surface                          | Outcome                                                                        |
| -------------------------------- | ------------------------------------------------------------------------------ |
| Markdown responses               | Gives AI clients clean product, collection, and search content                 |
| Schema.org data                  | Describes products, breadcrumbs, and the organization                          |
| [Sitemap](/docs/anatomy/sitemap) | Lists storefront content for crawlers                                          |
| Crawl guidance                   | Points crawlers to the sitemap and blocks faceted collection URLs              |
| `/llms.txt`                      | Gives AI agents a concise storefront index                                     |
| Social metadata                  | Supplies route-specific titles, descriptions, images, prices, and availability |

Markdown responses include the facts needed to interpret each page:

- **Products** — brand, category, a one-line summary, price or price range with compare-at price, product-level availability, description, options, images, and links to the shopper page and UCP profile.
- **Collections** — description, active and available filters, products, pagination, image, and SEO metadata.
- **Search** — query, collection scope, filters, products, and pagination.

## What’s next

Keep `/llms.txt` concise and use the sitemap for exhaustive discovery. Common extensions include Markdown responses for additional page types, localized discovery content, and public metaobject content. Include only facts an agent needs to identify, compare, or link to that content.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Agent
description: Enable product discovery, store questions, and cart updates through conversation.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/agent"
docs_index: /llms.txt
lastUpdated: 2026-09-15
type: guide
prerequisites:
  - /docs/getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Shop Agent helps shoppers find products, choose options, ask store questions, and manage their cart through conversation.

It is **disabled by default**. The storefront works without it, and the chat control stays hidden until you enable it. Customer sign-in is not required.

## Enable Shop Agent

Shop Agent uses your storefront's existing Shopify connection. It runs on [Eve](https://eve.dev) with OpenAI GPT-5.6 Luna (Fast) through [Vercel AI Gateway](https://vercel.com/ai-gateway).

1. Set `agent.isEnabled` to `true` in `lib/config/index.ts`. The chat button floats in the bottom corner; move it by rendering `AgentButton` elsewhere in the layout.
2. Configure AI Gateway access. Vercel deployments can use deployment identity authentication. For local development or other hosts without that authentication, set `AI_GATEWAY_API_KEY` in your server environment. Use `.env.local` for local values and keep the key server-only. See [Environment Variables](/docs/reference/env-vars#shop-agent).
3. Run `pnpm dev`, open Shop Agent, and ask for a product from your store. The reply should show product cards you can open or ask about.

Before allowing public traffic, review [privacy and usage limits](#privacy-and-usage-limits). On Vercel, deploy the storefront with the setting and Gateway access configured; you do not need a separate agent deployment.

### Local production

For a local production build, run `pnpm build:agent && pnpm build`. Then run `pnpm exec eve start --port 4274` and `pnpm start` in separate terminals. Use `pnpm dev` for ordinary development.

## Shopper experience

Shoppers can:

- Find products and collections, narrow results by color or size, and choose variants.
- Get product recommendations and answers about store policies.
- Add items, change quantities, remove items, and add an order note.

Product and policy answers use Shopify data. Keep your catalog and store policies up to date so shoppers can get useful answers.

### One cart, with the shopper in control

Chat shares the storefront's [cart](/docs/anatomy/cart). Confirmed changes show the updated cart in the conversation. The assistant cannot place orders or process payments; shoppers use the checkout button.

New messages and checkout wait while the assistant is working or confirming the cart in that browser tab. If confirmation fails, use **Refresh cart**. This checks the cart without repeating the change. Wait for the response before checking out in another tab.

A cart change may finish after a response is stopped or interrupted. Check the cart before asking again, especially after a timeout or connection failure.

### Continue a conversation or start over

The conversation and draft can be restored after navigation or reload in the same browser.

Use **Stop** to interrupt a response or **Clear chat** to start over. Clear waits for an active response to stop; if it cannot confirm that, it keeps the conversation for retry. Clearing chat does not empty the cart.

Long conversations are summarized automatically so they stay within the model's context. Conversations expire after 30 days; if one has expired, clear it to start a new one.

## Privacy and usage limits

Once enabled, chat is publicly accessible and messages can incur model charges. The template does not cap tokens per conversation; control cost where traffic enters instead. Add deployment-level rate limits and Gateway spending controls appropriate to your traffic.

Bot protection is **disabled by default**. For Vercel deployments, enable `botid.isEnabled` in [Shop Configuration](/docs/reference/shop-config) so automated traffic is rejected before it reaches the model. Bot protection does not replace access controls or spending limits.

Customer sign-in does not make chat history private. The default setup does not restrict a conversation to its original shopper: anyone with its conversation ID may be able to read or control it. Do not share conversation IDs. Add authenticated conversation ownership before using chat for private account or support data.

Eve stores conversations on the server, and conversation content is sent to the model provider. Clearing chat or conversation expiry does not guarantee deletion of those records. Configure retention and access policies, and explain to shoppers how their messages are handled.

The assistant receives a summary of cart items rather than gift-card recipient details. Personal information shoppers type into chat still reaches the model provider. Avoid requesting sensitive information that is not needed for shopping.

## What’s next

Adapt the tone, product presentation, and recommendations to your store. Add store-specific guidance while keeping Shopify as the source of prices and availability. Choose a model that balances response quality, speed, and cost.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Authentication
description: Enable Shopify customer sign-in, order history, profile details, and saved addresses.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/authentication"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The template includes opt-in customer authentication through Shopify Customer Accounts. Signed-in customers can manage their profile and addresses, review orders, and continue to checkout with their customer identity.

Authentication is disabled by default. The storefront remains guest-only until you complete the setup below.

## Configure customer accounts

Set `auth.isEnabled` to `true` in `lib/config/index.ts` and configure these environment variables:

```bash
CUSTOMER_ACCOUNT_SESSION_SECRET="your-session-secret-here"
SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_ID="shp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_SECRET="your-customer-account-client-secret-here"
```

Shopify provides the confidential client ID and client secret. Generate the session secret yourself:

```bash
openssl rand -base64 32
```

Keep both secrets server-only and identical across every instance of a deployment. Rotating the session secret signs out all customers. A build with authentication enabled fails when the session secret, client ID, or client secret is missing.

Customer Account OAuth requires a public HTTPS origin. For local testing, open the storefront through a public HTTPS tunnel.

In Shopify Admin:

1. Go to **Settings → Customer accounts → Edit**.
2. Choose **Customer accounts**, then save.
3. Install the **Headless** sales channel.
4. Go to **Sales channels → Headless → your storefront → Customer Account API**.
5. Copy the confidential client ID and client secret.
6. Add `https://YOUR_DOMAIN/account/authorize` as the callback URI.
7. Add `https://YOUR_DOMAIN/` as the logout URI.
8. Set `SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_ID` and `SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_SECRET` to the copied values.
9. Confirm `NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN` identifies the same store.

Shopify does not support wildcard callback or logout URIs. Register every production, preview, and tunnel origin that needs authentication. See [Environment Variables](/docs/reference/env-vars) for the full variable reference.

## Customer experience

Customers sign in at `/account/login` and sign out through `/account/logout`. The storefront provides:

- `/account/profile` for name and email details
- `/account/orders` and `/account/orders/[id]` for order history and details
- `/account/addresses` for creating, editing, deleting, and choosing a default address

Sessions use encrypted, HttpOnly cookies rather than browser-readable storage. Expired access tokens refresh through `/account/refresh`. Logout ends both the storefront session and the Shopify sign-in session.

When authentication is enabled, the cart follows the customer session. Signing in attaches the browser cart when possible, and checkout can carry the customer's saved details. Cart synchronization is best-effort; an authentication redirect still completes if cart synchronization fails.

Customer Account API data is personalized and is not publicly cached.

> **Security requirements:** Never expose access, refresh, or ID tokens to browser-readable storage. Keep logout as a same-origin `POST`.

If sign-in redirects fail, compare the deployed origin, callback URI, logout URI, store domain, client ID, and client secret character for character. Failed callbacks return to the storefront instead of restarting sign-in.

## What’s next

You can change the account navigation, add a dashboard, or expose more Customer Account API data such as store credit or subscriptions. Validate additions against the Customer Account API schema, which differs from the Storefront API, and keep private customer data behind authentication.

See [Proxy](/docs/anatomy/proxy) for the storefront's request-routing role in customer account URLs.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Cart
description: How shoppers add products, manage quantities and discounts, and continue to Shopify checkout.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/cart"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Shoppers use one Shopify cart across product pages, the cart drawer, and the full cart page. [Shop Agent](/docs/anatomy/agent) uses that same cart when enabled.

The standard cart is ready to use without additional setup. The storefront remembers it in the same browser for up to 14 days.

## Shopper experience

Shoppers can add products, change quantities, remove items, apply discount codes, and proceed to checkout. The cart drawer is available throughout the storefront, and `/cart` provides a full-page view.

Quantity changes appear immediately while Shopify confirms them. If Shopify rejects a change, the cart returns to its confirmed state and shows the relevant error. Prices and totals indicate when they are still updating. Errors for a particular item appear alongside it.

Checkout stays unavailable until pending cart changes are confirmed. If checkout cannot start, an inline message lets the shopper try again without rebuilding the cart.

The cart displays discounts and sale pricing confirmed by Shopify. Bundles remain grouped so shoppers can understand which products belong together. Some bundle items cannot be edited individually.

When [customer authentication](/docs/anatomy/authentication) is enabled, signing in can attach the browser cart to the customer so checkout can use their saved details. Customer sign-in is optional and disabled by default.

## Important limits

- Cart recovery depends on the browser retaining its cookie and Shopify still returning the cart. Clearing browser data can start a new cart sooner than 14 days.
- Shopify determines inventory, prices, discounts, and which changes are allowed. An item being in the cart does not guarantee it will remain available at checkout.
- A cart change may finish after a chat response is interrupted. Check the cart before repeating the request.
- Preserve gift-card recipient details and bundle information when customizing the cart so that checkout receives everything needed to fulfill the order.

If you need to attribute cart activity to a particular Headless storefront, set `NEXT_PUBLIC_SHOPIFY_STOREFRONT_ID`. See [Environment Variables](/docs/reference/env-vars#optional).

## What’s next

Add gift messages, personalization, or other order details to suit your store. Make sure that information reaches checkout, and keep Shopify responsible for prices and availability. Custom bundle flows should make clear which items shoppers can change together or separately.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Footer
description: The storefront footer - copyright, Shopify policy links, social links, and optional menu columns.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/footer"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The footer appears on every page. By default, it shows your store name and links to every policy configured in Shopify. Social links and menu columns are optional.

## Publish store policies

Add and maintain policies in Shopify Admin. The footer automatically links each configured policy using the deployment's configured Shopify language. Policies without content do not appear, so there is no separate list to maintain.

Supported policy pages include contact information, legal notice, privacy, refunds, shipping, terms of sale, and terms of service.

## Choose what else to show

- **Copyright** — uses the configured store name and omits the year by default.
- **Social profiles** — hidden until you add profile URLs. Supported services are Facebook, Instagram, X, YouTube, TikTok, Pinterest, LinkedIn, and GitHub.
- **Menu columns** — use the [`enable-shopify-menus`](/docs/skills/enable-shopify-menus) skill to add links from a Shopify footer menu.

## What’s next

Common additions include a Shopify-managed link menu, a newsletter signup, payment or trust marks, and market or language controls. Keep the footer focused on destinations shoppers may need from any page.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Translations
description: Customize storefront copy for one deployment, then add translated languages or Shopify Markets when needed.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/i18n"
docs_index: /llms.txt
lastUpdated: 2026-09-07
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The default storefront serves one deployment with clean URLs such as `/products/shoe`. It keeps copy alongside components rather than using a translation library. next-intl and language switching are optional upgrades.

## Customize storefront copy

Edit shopper-facing text in the component that displays it. The default storefront has no central translation catalog; labels, messages, and accessibility text are kept with the interface they describe.

After changing a quantity message, check zero, one, and multiple items. Include loading states, empty states, errors, and accessibility labels when reviewing copy.

## Set the deployment context

Set `shopConfig.localization` in `lib/config/index.ts` for the deployment:

```ts
localization: {
  country: "US",
  language: "EN",
  locale: "en-US",
},
```

Country and language configure Shopify requests. Locale controls display formatting, such as numbers and dates. Prices use the currency returned by Shopify; changing a formatting locale does not convert a price.

These settings do not translate the storefront's own labels. Edit copy separately, and publish translated product or editorial content in Shopify when needed. The default does not negotiate a shopper's locale or add locale prefixes to URLs.

## What’s next

Use [Enable i18n](/docs/skills/enable-i18n) to add next-intl, translated catalogs, and locale-prefixed URLs without changing the commerce country. Use [Enable Shopify Markets](/docs/skills/enable-shopify-markets) for regional commerce context and Shopify-controlled pricing. Both upgrades must preserve existing translations and routing in stores that are already localized.

Add a language to navigation only when its fixed copy, interactive messages, plurals, formatting, and error states are ready. A copy-language selector and a commerce-country selector solve different problems; changing language alone should not move a shopper into another market.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Navigation
description: The storefront navigation system - desktop and mobile menus, search, cart access, and optional Shopify-managed links.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/navigation"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The storefront provides desktop and mobile navigation, predictive search, and cart access. Links are static by default. Use [`/vercel-shop:enable-shopify-menus`](/docs/skills/enable-shopify-menus) when merchants need to manage them in Shopify.

## Help shoppers move through the store

On desktop, a sticky header shows the logo, primary links, search, and cart. On mobile, primary links move into a menu while search and cart remain in the header. The mobile menu closes after a shopper selects a link.

Search opens as a dialog. It shows suggestions and matching products as the shopper types, links directly to products, and offers a full results page. It closes after navigation, Escape, or an outside click. Set `search.isEnabled` to `false` in [Shop Configuration](/docs/reference/shop-config) to remove the search button and dialog; the `/search` results page stays available.

The cart badge shows the current item count and opens the cart.

## Choose how links are managed

The default storefront includes a single Shop link. This is suitable for a small catalog or a navigation structure managed in code.

Use the [`enable-shopify-menus`](/docs/skills/enable-shopify-menus) skill to let merchants manage desktop links, mobile links, and footer columns through Shopify Navigation. Menus can contain up to three levels.

An optional mobile bottom bar appears only when you add an action, such as an agent button.

## What’s next

Enable [Authentication](/docs/anatomy/authentication) to show the built-in customer-account link. Other additions include Shopify-managed menus, promotional bars, and richer desktop navigation for large catalogs. Keep search and cart access easy to reach on every screen size.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Product Card
description: "The shared tile that displays a product's image, title, price, and availability across the storefront."
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/product-card"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Product cards give shoppers a consistent way to recognize and compare products. They appear on the home page, collection and search pages, related-product sections, and agent results. A visual change affects every use.

## Information shoppers see

Each card links to the product and shows its image, title, and price. Products with a price range show both bounds. A single discounted price can show the original price and a discount badge; ranges do not show a discount because discounts may differ by variant.

When a shopper applies one color filter, collection and search cards use the image assigned to that color's first selectable variant and link to its selected options. Assign an image to each color variant in Shopify so filtered cards can show the matching merchandise. Cards fall back to the product image when the matching variant has no image.

Unavailable products keep their image visible under an out-of-stock label. Products without an image show a placeholder instead of a broken image. Loading, empty, and loaded states reserve the same square space so grids remain stable.

The featured style adds an “Assistant's pick” badge and stronger image treatment. Standard grids use the default style.

## Decide whether cards should support purchasing

The default card is for discovery. Shoppers open the product page to choose variants and add an item to the cart.

Products with required options usually need the full product page. Add purchasing controls to cards only when shoppers can choose safely from the information shown.

## What’s next

Common additions include alternate image proportions, a second image on hover, review summaries, variant swatches, and quick add. Keep cards easy to scan, and add purchasing controls only when shoppers have enough information to choose safely without opening the product page.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Proxy
description: Add routing rules without interrupting Shopify services or storefront pages.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/proxy"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The storefront's [Next.js proxy](https://nextjs.org/docs/app/getting-started/proxy), also called middleware, keeps Shopify's service routes separate from storefront pages and custom APIs. The default routing works without additional setup.

## What it handles

The proxy supports cart updates, search suggestions, Shopify API access, and Shopify-compatible commerce endpoints. This includes legacy cart URLs and Shopify's business profile at `/.well-known/ucp`.

Customer sign-in routes are available only when you enable [customer authentication](/docs/anatomy/authentication). Shop Agent is enabled separately and uses `/eve/v1/` for conversations.

Ordinary pages continue to Next.js. Home, product, collection-detail, and search pages can also return Markdown for clients that request it. See [Routes](/docs/reference/routes) for page URLs and service endpoints.

Custom API routes are not intercepted unless they match a route explicitly reserved for an integration.

## Add routing without creating conflicts

When extending `proxy.ts`, match only the routes an integration needs. Do not redirect every `/api/` request or claim all `/agent/` paths: Shopify and Shop Agent serve different endpoints.

Keep Shopify routing ahead of locale negotiation, experiments, and request-dependent redirects. A cart update or Shopify API request should not be redirected to a language-prefixed page.

Use Next.js configuration for fixed redirects, such as moving an old page to a new URL. Use the proxy when the destination depends on the incoming request.

## What’s next

Add locale routing with [Shopify Markets](/docs/skills/enable-shopify-markets) or [i18n](/docs/skills/enable-i18n), or introduce request-dependent experiments and redirects. Keep those rules scoped to storefront pages so cart, sign-in, and API requests continue to work.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Sitemap
description: A sitemap index and paged child sitemaps for storefront content managed in Shopify.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/sitemap"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The storefront publishes `/sitemap.xml` as the single discovery URL for products, collections, Shopify Pages, blogs, articles, and configured policies. Crawl guidance points search engines to this index.

## What the sitemap includes

| URL                            | Contents                                  |
| ------------------------------ | ----------------------------------------- |
| `/sitemap.xml`                 | Index of every child sitemap              |
| `/sitemap/static.xml`          | Home page and configured Shopify policies |
| `/sitemap/products-{n}.xml`    | Up to 250 products per page               |
| `/sitemap/collections-{n}.xml` | Up to 250 collections per page            |
| `/sitemap/pages-{n}.xml`       | Up to 250 Shopify Pages per page          |
| `/sitemap/blogs-{n}.xml`       | Up to 250 Shopify blogs per page          |
| `/sitemap/articles-{n}.xml`    | Up to 250 Shopify articles per page       |

Shopify determines how many pages each content type needs. The storefront lists every page in the index, so crawlers need only `/sitemap.xml`.

## Freshness limits

Product and collection webhooks refresh affected sitemap entries. Creating or deleting those resources also refreshes the relevant sitemap listing. See [Webhooks](/docs/anatomy/webhooks).

The template's webhook handler does not refresh Pages, blogs, articles, or policies. Their sitemap entries can remain cached until revalidation. If a publishing change must appear immediately, refresh the sitemap as well as the page content.

## What’s next

Shopify's sitemap API also supports metaobjects, but the template does not include them. Add that resource type only when metaobjects have public storefront URLs that search engines should discover. Stores with rich media may also add image or video sitemap data for content that search engines cannot discover reliably from the page itself.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Webhooks
description: Refresh supported storefront content when Shopify products, collections, or metaobjects change.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/webhooks"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Shopify webhooks make supported Admin changes appear on the storefront without waiting for cached data to expire. The template accepts Shopify notifications at `POST /api/webhooks/shopify` and refreshes the affected product, collection, recommendation, sitemap, or metaobject content.

Webhooks are disabled by default. Without `SHOPIFY_WEBHOOK_SECRET`, the endpoint returns `404` and Shopify changes appear only after cache expiry or a redeploy.

## Set up Shopify webhooks

1. Open **Shopify Admin → Settings → Notifications → Webhooks**.
2. Set the destination to `https://your-domain.com/api/webhooks/shopify`.
3. Choose **JSON** as the format.
4. Register the product, collection, and metaobject topics you need.
5. Copy the webhook signing secret into `SHOPIFY_WEBHOOK_SECRET` for that environment.

```bash
SHOPIFY_WEBHOOK_SECRET="your-webhook-secret-here"
```

Register these supported topics:

- `products/create`, `products/update`, and `products/delete`
- `collections/create`, `collections/update`, and `collections/delete`
- Metaobject create, update, and delete topics

You can register only the topics your store uses. An unregistered topic simply means those changes wait for normal cache expiry.

See [Environment Variables](/docs/reference/env-vars) for the full variable reference.

## Behavior and security

Every configured request must carry a valid Shopify signature. Missing, malformed, or incorrect signatures return `401`. The handler rejects the request before using its topic or payload.

Product notifications refresh the affected product wherever it appears, including recommendations and sitemap content. Product creation and deletion also refresh catalog membership.

Collection notifications refresh the affected collection and collection listings. Collection creation and deletion also refresh collection membership and sitemap content.

Metaobject notifications refresh metaobject-backed content only when your custom reads participate in the template's metaobject invalidation behavior. The base template does not read metaobjects by default.

## Limits

The included handler does not refresh inventory-level changes. Stock updates therefore appear at cache expiry unless another supported product webhook arrives or you add a live inventory strategy.

This handler does not refresh Online Store pages, blogs, articles, or store policies. Those edits wait for cache revalidation unless you arrange a separate refresh. See [Content pages](/docs/anatomy/pages/content) for publishing guidance.

Navigation menu edits are also outside this handler.

Malformed product or collection update payloads cannot identify a specific resource, so they do not refresh the full catalog. Valid Shopify deliveries include the expected identifiers.

After registering a topic, use **Send test notification** in Shopify Admin and confirm the delivery succeeds before relying on it for storefront freshness.

## What’s next

Add a topic only when Shopify offers a webhook for the resource and the storefront has a safe way to refresh the affected data. Prefer targeted refreshes for frequently changing catalog data. Use broader refreshes only when an event cannot be scoped or after an intentional bulk change.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: WebMCP
description: Experimental browser agent tools that expose storefront search and cart actions through the proposed WebMCP API.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/webmcp"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

WebMCP is an **experimental** browser API for exposing structured tools to AI agents running in or alongside a browser. The storefront can expose Shopify catalog search and cart actions through this API.

The integration is disabled by default. The proposed API may change or be removed, and unsupported browsers receive no fallback interface.

## Decide whether to enable it

Enable WebMCP only for active testing. When enabled in a supporting browser, Shopify registers storefront tools that agents can discover and call with structured inputs. Cart actions use the same cart the shopper sees.

Shopify defines and hosts the available tools. The template cannot select or change individual tools, and Shopify may update them independently.

WebMCP and Shopify analytics are separate choices. Enabling WebMCP does not enable Shopify page-view reporting.

## Test locally

For local testing, use a Chrome build that exposes the WebMCP testing flag:

1. Check that your Chrome build supports WebMCP testing.
2. Open `chrome://flags/#enable-webmcp-testing`.
3. Enable **WebMCP for testing**, then relaunch Chrome.
4. Set `browserAgents.webmcp.isEnabled` to `true` in `lib/config/index.ts`, then load the site.
5. Use a WebMCP inspector or a connected browser agent to list and invoke the registered tools.
6. Confirm that search returns structured catalog results and cart actions update the shopper's cart.

Browser support and origin-trial requirements can change. Check your browser's current WebMCP documentation before enabling it on a public deployment.

Leave the feature disabled for shoppers unless you accept the compatibility and stability limits of an experimental API.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Extending with Agents
description: Use coding agents like Claude Code, Cursor, and Codex to personalize and extend your storefront.
url: "https://shop-docs.labs.vercel.dev/docs/getting-started/extending-with-agents"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
prerequisites:
  - /docs/getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Use a coding agent to change the storefront while following the template's conventions and verification steps.

## Set up the agent

`create-vercel-shop` installs project plugins for Vercel Shop, Next.js, Vercel, and Shopify. To install those plugins in an existing project without downloading the template, run:

```bash
npx create-vercel-shop@latest --no-template
```

If the plugins are missing, install them directly:

```bash
npx plugins add vercel/shop --scope project --yes
npx plugins add vercel/vercel-plugin --scope project --yes
npx plugins add Shopify/shopify-ai-toolkit --scope project --yes
```

Use an agent that reads `AGENTS.md` and supports project skills, such as [Claude Code](https://claude.ai/code), [Cursor](https://cursor.sh), or [Codex](https://openai.com/index/introducing-codex/).

## Give the agent a focused task

State the outcome, placement, data source, constraints, and expected verification. For example:

```text
Add a size guide below the product description.
Read it from the product metafield custom.sizing_guide.
Hide the section when the metafield is empty.
Run the relevant checks and verify the product page in a browser.
```

Ask the agent to propose its plan before editing when the change spans several parts of the storefront. Include exact errors when asking it to fix a failure.

## Use storefront skills

Skills provide task-specific instructions and checks. In Claude Code, run a skill directly:

```bash
/vercel-shop:enable-shopify-markets
```

In Cursor or Codex, describe the outcome and reference the matching skill page. Use the [`update-shop` skill](/docs/skills/update-shop) when bringing an existing storefront up to date.

Source-backed storefront architecture and commerce behavior.

Multi-locale and multi-currency support.

## What’s next

Start with a contained storefront change, such as adding a product metafield, adjusting navigation, or changing a merchandising section. For repeated work, turn your preferred conventions and checks into a project skill so future agent changes follow the same approach.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Setup
description: Set up Shopify, create a project, and run Vercel Shop locally.
url: "https://shop-docs.labs.vercel.dev/docs/getting-started"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Connect a Shopify store and run Vercel Shop locally. Use Node.js 24, the version required by the template.

## 1. Get Shopify credentials

Install Shopify's **Headless** sales channel, create a storefront, and copy its **Storefront API access token**. Enable the required [Storefront API permissions](/docs/reference/storefront-api-permissions).

## 2. Create your project

```bash
npx create-vercel-shop@latest my-store
cd my-store
```

To deploy instead, use the Vercel flow:

<a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fshop%2Ftree%2Fmain%2Fapps%2Ftemplate&products=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22shopify%22%2C%22productSlug%22%3A%22shopify%22%2C%22protocol%22%3A%22other%22%7D%5D">
  <img src="https://vercel.com/button" alt="Deploy with Vercel" />
</a>

The deploy flow offers the Shopify integration. Before deploying, confirm that the required [environment variables](/docs/reference/env-vars) are configured.

## 3. Add your credentials

Create `.env.local` in the project root:

```bash
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN="your-store.myshopify.com"
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN="shpat_1234567890abcdefghijklmnopqrstuvwxyz"
```

See [Environment Variables](/docs/reference/env-vars) for optional settings.

## 4. Run the storefront

```bash
pnpm dev
```

Open [localhost:3000](http://localhost:3000) and confirm that your Shopify products appear.

For local HTTPS, install [Portless](https://portless.sh) with `pnpm add -g portless`, then run `pnpm dev:portless`. Use HTTPS when testing OAuth callbacks or secure cookies.

## Next steps

Configure the Shopify apps and sales channel used by the storefront.

Customize the storefront with coding agents and project skills.

Configure authentication, the shopping assistant, and other optional features.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Shopify Setup
description: "Configure Shopify's Headless channel, Search & Discovery, and optional storefront apps."
url: "https://shop-docs.labs.vercel.dev/docs/getting-started/shopify-setup"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
prerequisites:
  - /docs/getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Publish your catalog to the **Headless** sales channel first. Add the other Shopify apps only when you need their storefront features.

## Publish products to Headless

Install Shopify's **Headless** sales channel and create a storefront. Enable the required [Storefront API permissions](/docs/reference/storefront-api-permissions), then publish the products and collections you want to show to **Headless**. Content published only to **Online Store** does not appear in Vercel Shop.

To attribute server-side cart activity to this storefront, copy the storefront's numeric ID to `NEXT_PUBLIC_SHOPIFY_STOREFRONT_ID`.

## Configure filters and recommendations

Install [Shopify Search & Discovery](https://apps.shopify.com/search-and-discovery) to configure collection and search filters. You can also assign complementary products, which appear as “Pairs Well With” on product pages. Shopify supplies related product recommendations automatically.

## Publish fixed bundles

Install [Shopify Bundles](https://apps.shopify.com/shopify-bundles) if you sell fixed bundles. Publish the bundle and its component products to **Headless**; publishing only to **Online Store** is not enough.

## Next steps

Configure variants, swatches, recommendations, bundles, and product SEO.

See how Shopify concepts map to storefront routes and features.

Keep cached storefront data in sync with Shopify Admin changes.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Environment Variables
description: Required storefront credentials and optional settings for customer accounts, chat, and integrations.
url: "https://shop-docs.labs.vercel.dev/docs/reference/env-vars"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: reference
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Use `.env.local` for local development and your hosting platform's environment settings for deployments. Do not commit local credentials. Only browser-safe values belong in variables prefixed with `NEXT_PUBLIC_`.

## Required

These two values connect the storefront to your Shopify store.

| Variable                                      | Description                                                                                           |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN`            | Store domain, such as `your-store.myshopify.com`. Find it in **Settings → Domains** in Shopify admin. |
| `NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN` | Public, browser-safe Storefront API token from **Settings → Apps and sales channels → Headless**.     |

## Customer authentication

Customer sign-in is **disabled by default**. These values are required when you enable `auth.isEnabled` in [Shop Configuration](/docs/reference/shop-config). Follow [Authentication](/docs/anatomy/authentication) to configure Shopify and its callback URLs.

| Variable                                     | Description                                                                                   |
| -------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `CUSTOMER_ACCOUNT_SESSION_SECRET`            | App-generated secret for encrypting customer session cookies. Shopify does not supply it.     |
| `SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_ID`     | Client ID from **Sales channels → Headless → Customer Account API** in Shopify admin.         |
| `SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_SECRET` | Server-only client secret issued by Shopify for the confidential Customer Account API client. |

Generate `CUSTOMER_ACCOUNT_SESSION_SECRET` with `openssl rand -base64 32`. Keep the same value across all instances of a deployment. Rotating it signs out existing customers. Keep both the session secret and Shopify client secret server-only.

## Shop Agent

Shop Agent is **disabled by default** and needs no AI credentials while disabled. To enable it, follow [Shop Agent setup](/docs/anatomy/agent).

| Variable                | When to set it                                                                                                                                                                                |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AI_GATEWAY_API_KEY`    | Set a server-only Gateway key when running Shop Agent without Vercel deployment identity authentication, including local development without that authentication.                             |
| `UCP_AGENT_PROFILE_URL` | Override the public profile used for Shopify catalog search. Defaults to `/agent/ucp-profile.json` at `site.url`; local development and Vercel previews use Shopify's public example profile. |

## Optional

| Variable                            | When to set it                                                                                                      |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `DEBUG_SHOPIFY`                     | Set to `1` or `true` when troubleshooting Shopify API requests. Enables detailed operation and timing logs.         |
| `NEXT_PUBLIC_SHOPIFY_STOREFRONT_ID` | Set to the numeric ID of your Headless storefront when you need storefront-specific cart attribution.               |
| `SHOPIFY_API_VERSION`               | Override the Storefront API version. Defaults to `unstable`; this does not select the Customer Account API version. |
| `SHOPIFY_WEBHOOK_SECRET`            | Set when configuring Shopify webhooks. Used to verify signatures on `POST /api/webhooks/shopify`.                   |

## Platform-provided values

Vercel supplies `VERCEL_PROJECT_PRODUCTION_URL`, which sets the default production origin. For another host or a custom origin, set `site.url` in [Shop Configuration](/docs/reference/shop-config).

`V0_CALLBACK_URL` is supplied by v0 previews to disable image optimization there. You do not need to set it for ordinary development or deployment.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Routes
description: Storefront URLs, optional account and chat routes, and service endpoints.
url: "https://shop-docs.labs.vercel.dev/docs/reference/routes"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: reference
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Storefront pages use the paths below without a language prefix. Customer accounts and Shop Agent are optional and disabled by default.

## Pages

| Route                                 | Description                                               |
| ------------------------------------- | --------------------------------------------------------- |
| `/`                                   | Home page                                                 |
| `/products/[handle]`                  | Product details; option query parameters select a variant |
| `/collections`                        | Collection index                                          |
| `/collections/[handle]`               | Collection with filtering, sorting, and pagination        |
| `/collections/all`                    | All products                                              |
| `/search`                             | Product search results                                    |
| `/cart`                               | Full cart page                                            |
| `/blogs/[blogHandle]`                 | Shopify blog                                              |
| `/blogs/[blogHandle]/[articleHandle]` | Shopify article                                           |
| `/pages/[handle]`                     | Shopify page                                              |
| `/policies/[handle]`                  | Store policy                                              |

## Service endpoints

| Route                        | Description                                         |
| ---------------------------- | --------------------------------------------------- |
| `GET /api/cart`              | Read the current cart                               |
| `POST /api/cart`             | Create a cart or change its contents                |
| `GET /api/predictive-search` | Search suggestions                                  |
| `POST /api/ucp/mcp`          | UCP MCP proxy                                       |
| `POST /api/webhooks/shopify` | Receive signed Shopify webhooks for catalog updates |

See [Cart](/docs/anatomy/cart) for shopper behavior and [Environment Variables](/docs/reference/env-vars) for webhook configuration.

## Shop Agent

These endpoints are available only when `agent.isEnabled` is `true`. Follow [Shop Agent setup](/docs/anatomy/agent) before enabling them.

| Route                         | Description                                                          |
| ----------------------------- | -------------------------------------------------------------------- |
| `/eve/v1/*`                   | Conversation creation, messages, history, and controls               |
| `POST /api/agent/session`     | Prepare the browser cart for chat; accepts same-origin requests only |
| `GET /agent/ucp-profile.json` | Public profile for the assistant's Shopify catalog integration       |

Conversation endpoints are supplied by [Eve](https://eve.dev/docs). Enabling customer accounts does not make conversations private; see the [chat privacy requirements](/docs/anatomy/agent#privacy-and-usage-limits).

## Customer accounts

These routes require `auth.isEnabled` to be `true` and the [customer authentication credentials](/docs/reference/env-vars#customer-authentication). Account pages require sign-in. See [Authentication](/docs/anatomy/authentication) for setup and callback URLs.

| Route                    | Description                              |
| ------------------------ | ---------------------------------------- |
| `/account`               | Redirect to `/account/profile`           |
| `/account/profile`       | Customer profile                         |
| `/account/addresses`     | Saved addresses                          |
| `/account/orders`        | Order history                            |
| `/account/orders/[id]`   | Order details                            |
| `GET /account/login`     | Start customer sign-in                   |
| `GET /account/authorize` | Sign-in callback                         |
| `GET /account/refresh`   | Refresh the customer session             |
| `POST /account/logout`   | Sign out; requires a same-origin request |

## Discovery and Markdown

| Route                      | Description                                          |
| -------------------------- | ---------------------------------------------------- |
| `/robots.txt`              | Crawler rules                                        |
| `/sitemap.xml`             | Sitemap index                                        |
| `/sitemap/[shard]`         | Sitemap entries for a content group                  |
| `/llms.txt`                | Storefront guide for AI agents                       |
| `/md`                      | Home page in Markdown                                |
| `/md/products/[handle]`    | Product in Markdown                                  |
| `/md/collections/[handle]` | Collection in Markdown                               |
| `/md/search`               | Search results in Markdown                           |
| `/.well-known/ucp`         | Shopify's business profile for commerce integrations |

The home, product, collection-detail, and search URLs also return Markdown when requested with `Accept: text/markdown`. See [Proxy](/docs/anatomy/proxy) when adding custom routing or integrations.

## Variant URLs

Product options use one query parameter per option. The key is the option name in Shopify, and the value is the selected option value.

For example, `/products/your-product?Color=Blue&Size=XS` selects the blue, extra-small variant. Names and values are matched case-insensitively, so `?color=blue&size=xs` selects the same options. Without option parameters, the page uses the product's default variant.

Liquid-style links such as `/products/your-product?variant=123456789` redirect to the matching option-parameter URL. Selecting an option on the same product keeps unrelated query parameters, such as campaign tags.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Shop Configuration
description: "Choose optional storefront features and set your store's identity and locale."
url: "https://shop-docs.labs.vercel.dev/docs/reference/shop-config"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: reference
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Edit `lib/config/index.ts` to choose which features your storefront offers. Keep credentials in [environment variables](/docs/reference/env-vars) and manage catalog content in Shopify.

Customer accounts, Shop Agent, analytics, bot protection, and WebMCP are **disabled by default**. Enable only the features you intend to use and complete their setup before deploying.

## Settings

| Setting                               | Default              | Behavior                                                                         |
| ------------------------------------- | -------------------- | -------------------------------------------------------------------------------- |
| `agent.isEnabled`                     | `false`              | Enables Shop Agent for product discovery, store questions, and cart updates.     |
| `analytics.vercel.isEnabled`          | `false`              | Enables Vercel Web Analytics.                                                    |
| `analytics.speedInsights.isEnabled`   | `false`              | Enables Vercel Speed Insights.                                                   |
| `analytics.shopify.isEnabled`         | `false`              | Sends browsing and cart events to Shopify, subject to consent.                   |
| `analytics.shopify.consentMode`       | `default-banner`     | Chooses how shoppers give analytics consent; see below.                          |
| `auth.isEnabled`                      | `false`              | Enables customer sign-in and account pages.                                      |
| `botid.isEnabled`                     | `false`              | Enables Vercel BotID checks for Shop Agent requests.                             |
| `botid.checkLevel`                    | `basic`              | Chooses `basic` or `deepAnalysis` bot detection.                                 |
| `browserAgents.webmcp.isEnabled`      | `false`              | Registers Shopify catalog and cart tools with WebMCP in supported browsers.      |
| `localization.country`                | `"US"`               | Sets the country used for Shopify pricing and availability.                      |
| `localization.language`               | `"EN"`               | Requests Shopify content in this language.                                       |
| `localization.locale`                 | `"en-US"`            | Sets display formatting; does not translate interface text or select a currency. |
| `pdp.bundles.isEnabled`               | `true`               | Shows bundle relationships when Shopify provides them.                           |
| `pdp.buyWithShop.isEnabled`           | `true`               | Shows Buy with Shop.                                                             |
| `pdp.complementaryProducts.isEnabled` | `true`               | Shows complementary products when Shopify provides them.                         |
| `pdp.quantityPicker.isEnabled`        | `true`               | Shows the product quantity picker.                                               |
| `pdp.relatedProducts.isEnabled`       | `true`               | Shows related products when Shopify provides them.                               |
| `search.isEnabled`                    | `true`               | Shows the header search button and predictive search dialog.                     |
| `site.name`                           | `"Vercel Shop"`      | Sets the storefront name.                                                        |
| `site.url`                            | Deployment-dependent | Sets the public origin used for canonical URLs, metadata, and discovery links.   |

## Before enabling optional features

**Shop Agent** requires AI Gateway access in addition to the storefront's Shopify connection. Customer sign-in is not required. Follow [Shop Agent setup](/docs/anatomy/agent), including its privacy and spending safeguards, before enabling public traffic.

**Customer accounts** require a Shopify client ID, Shopify client secret, and a separate app-generated session secret. Follow [Authentication](/docs/anatomy/authentication) to configure those values and register callback URLs before enabling `auth.isEnabled`.

**Bot protection** can be enabled separately for Shop Agent on Vercel. Review [BotID pricing](https://vercel.com/docs/botid#pricing) before choosing `deepAnalysis`. It does not make chat history private or cap AI spending.

## Store identity and locale

On Vercel, `site.url` uses the project's production domain. Elsewhere it defaults to `http://localhost:3000`; set it to your public storefront origin before deploying.

Country and language control the Shopify context. The formatting locale controls how values are displayed. Changing these settings does not translate storefront interface text, and currency comes from Shopify's responses. See [Shopify Markets](/docs/skills/enable-shopify-markets) or [i18n](/docs/skills/enable-i18n) when adding localized storefronts.

## Shopify consent modes

These modes apply when Shopify analytics is enabled.

| Value            | Behavior                                                                                     |
| ---------------- | -------------------------------------------------------------------------------------------- |
| `default-banner` | Uses Shopify's hosted privacy banner where consent is required.                              |
| `custom-banner`  | Requires you to provide a consent interface connected to Shopify's Customer Privacy API.     |
| `no-banner`      | Shows no consent interface. Where consent is required, analytics events cannot be delivered. |

Only expose the public Storefront API token to the browser. Never use a private token for Shopify analytics or consent requests.

## Related configuration

Manage navigation and footer menus with [Shopify Menus](/docs/skills/enable-shopify-menus).

[WebMCP](/docs/anatomy/webmcp) is independent of Shopify analytics. Enabling one does not enable the other.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Storefront API Permissions
description: Shopify Storefront API scopes for the default storefront and custom data.
url: "https://shop-docs.labs.vercel.dev/docs/reference/storefront-api-permissions"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: reference
prerequisites:
  - /docs/getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

After [Getting Started](/docs/getting-started), open **Settings → Apps and sales channels → Headless → your storefront** in Shopify admin and grant these Storefront API permissions to the token.

## Required

| Scope                                   | Enables                                            |
| --------------------------------------- | -------------------------------------------------- |
| `unauthenticated_read_product_listings` | Products, collections, search, and recommendations |
| `unauthenticated_read_product_tags`     | Product tags requested by the storefront           |
| `unauthenticated_write_checkouts`       | Cart operations and checkout                       |
| `unauthenticated_read_content`          | Pages, blogs, and articles                         |

## Optional

Grant optional scopes only when your storefront queries the related data.

| Scope                                    | Enables                                                                                                 |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `unauthenticated_read_product_inventory` | Inventory quantities, such as `quantityAvailable`; not required for the default availability indicators |
| `unauthenticated_read_metaobjects`       | Custom content stored in metaobjects                                                                    |

Customer sign-in uses separate Customer Account API permissions. See [Authentication](/docs/anatomy/authentication) for account setup and [Shopify's scope reference](https://shopify.dev/docs/api/usage/access-scopes) for additional permissions.

See [Environment Variables](/docs/reference/env-vars) for token configuration and [Storefront API](/docs/reference/storefront-api) for query validation and runtime behavior.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Storefront API
description: "How the template calls Shopify's Storefront GraphQL API - the fetch client, query patterns, caching, and error handling."
url: "https://shop-docs.labs.vercel.dev/docs/reference/storefront-api"
docs_index: /llms.txt
lastUpdated: 2026-09-14
type: reference
prerequisites:
  - /docs/getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The template uses the framework-agnostic [Shopify Hydrogen](https://www.npmjs.com/package/@shopify/hydrogen) preview SDK for Storefront GraphQL API queries and mutations. It runs on Next.js, not the Hydrogen React Router framework. Next.js owns page routing, rendering, and public-data caching; Hydrogen supplies Shopify API clients and commerce handlers.

Configure the store domain, public token, and API version with the variables in [Environment Variables](/docs/reference/env-vars). Grant the scopes in [Storefront API Permissions](/docs/reference/storefront-api-permissions).

The Customer Account API uses a separate endpoint and schema. Enabling authentication requires `SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_ID`, `SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_SECRET`, and a separate app-generated `CUSTOMER_ACCOUNT_SESSION_SECRET` for encrypted session cookies. Keep both secrets server-only. Its documents use `gql` from `@shopify/hydrogen/customer-account` and are validated separately from Storefront documents.

## Write and validate operations

Define each operation with Hydrogen's `gql()` tag and pass dynamic values as GraphQL variables. Do not interpolate runtime values or conditional fields into the document. Compose shared selections by passing fragment documents as the second argument.

Result and variable types are inferred from the document, so the request wrapper returns typed data without a hand-written response type. Include Shopify's `@inContext` directive when localized pricing or content depends on country and language. The wrapper defaults `$country` and `$language` to `shopConfig.localization.country` and `shopConfig.localization.language`. The separate `shopConfig.localization.locale` controls display formatting; it does not select a market or currency. Currency comes from Shopify.

Omit the operation's `locale` option to use the deployment settings. For an explicit commerce context, pass a `{ country, language }` object, not a formatting locale string. Keep that object in cached operation arguments when results vary by market or language:

```ts
import { gql } from "@shopify/hydrogen";

const GET_PRODUCT_QUERY = gql(`#graphql
  query getProduct(
    $handle: String!
    $country: CountryCode
    $language: LanguageCode
  ) @inContext(country: $country, language: $language) {
    productByHandle(handle: $handle) {
      id
      title
    }
  }
`);

const response = await storefront.request(GET_PRODUCT_QUERY, {
  locale: { country: "US", language: "EN" },
  variables: { handle },
});
```

Validate current field names, arguments, and types with Shopify AI Toolkit. Then use the [`shopify-graphql-reference` skill](/docs/skills/shopify-graphql-reference) for template integration conventions. The skill is not a schema source.

## Caching

Public-data operations use Next.js Cache Components and cache tags; the Hydrogen client does not replace that cache layer. Choose caching by data sensitivity and behavior:

| Data or operation                                          | Cache contract                                                |
| ---------------------------------------------------------- | ------------------------------------------------------------- |
| Public product, collection, menu, or content data          | Cache and invalidate when Shopify changes                     |
| Public results that vary by filters, search, or pagination | Share only when the cache key includes every varying input    |
| Cart, session, authorization, or customer data             | Keep request-scoped or private; never place in a public cache |
| Mutations                                                  | Do not cache                                                  |

Configure `POST /api/webhooks/shopify` as described in [Webhooks](/docs/anatomy/webhooks) to refresh cached products and collections after edits. The handler supports product, collection, and metaobject topics; other content needs its own invalidation strategy or cache expiry.

## Mutations

Storefront cart forms submit to Hydrogen's `POST /api/cart` handler through the Next.js proxy, not to cart Server Actions. Mutations are uncached and return the updated cart for Hydrogen's client store to reconcile. Cart updates do not invalidate the public catalog cache.

The optional shopping assistant uses the same handlers on the server. After a successful tool result, the browser refreshes the shared cart store. See [Cart](/docs/anatomy/cart) for shopper behavior and limits.

Never cache cart IDs, customer data, session data, or authenticated responses in a shared public cache.

## Errors and missing resources

Storefront requests throw on transport failures and timeouts. The request wrapper returns GraphQL errors alongside data; read operations check the result with `assertStorefrontOk`. This check throws when no data is returned and logs a warning when errors accompany partial data.

A missing resource is not an API failure. Read operations return `undefined`, `null`, or an empty list when the requested resource does not exist.

Set `DEBUG_SHOPIFY=true` to include structured Storefront and Customer Account API operation timings in server logs. Warnings and errors remain enabled when debug logging is off.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Troubleshooting
description: Solutions to common setup and runtime issues.
url: "https://shop-docs.labs.vercel.dev/docs/reference/troubleshooting"
docs_index: /llms.txt
lastUpdated: 2026-09-14
type: troubleshooting
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Products do not appear

**Symptom:** Product pages or listings are empty.

**Check:** In Shopify admin, confirm the Storefront token has the scopes in [Storefront API Permissions](/docs/reference/storefront-api-permissions). Confirm each product is active and published to the **Headless** sales channel.

**Fix:** Grant the missing scopes and publish the products to Headless. Publishing to **Online Store** alone is not enough.

**Expected result:** Published products appear in listings and product routes resolve.

## A bundle returns 404

**Symptom:** A bundle is active in Shopify admin but its storefront route returns `404`.

**Check:** Open the bundle product's **Publishing** settings. Confirm the bundle and its component products are published to the **Headless** sales channel.

**Fix:** Publish the bundle and its component products to Headless. If the route still returns `404`, confirm that a product webhook for the bundle reaches the storefront to invalidate the cached response.

**Expected result:** The bundle route loads and its component relationships appear.

## The cart does not persist

**Symptom:** The cart becomes empty after navigation or reload.

**Check:** In browser developer tools, open **Application → Cookies** and confirm the `cart` cookie exists for the current domain. It should have a 14-day expiry.

**Fix:** Use one consistent storefront domain and remove any deployment or proxy setting that rewrites the cookie domain.

**Expected result:** The cart survives navigation and reloads on the same domain. The cookie has a 14-day lifetime, but it does not guarantee that Shopify will retain the cart or that the browser will keep the cookie.

## Variant selection does not update the product

**Symptom:** Selecting an option changes the URL but not the price or purchase controls.

**Check:** Confirm the URL contains one query parameter per option, such as `?Color=Blue&Size=XS`. Option names and values are matched case-insensitively. Confirm the selected combination exists in Shopify.

**Fix:** Correct links to preserve all selected option parameters. If a valid combination still fails, validate the product and variant queries against the Storefront API schema.

**Expected result:** The URL, selected variant, price, availability, and purchase controls update together.

## Images do not load

**Symptom:** Shopify product images are broken or rejected by Next.js Image.

**Check:** Confirm `next.config.ts` allows `cdn.shopify.com` in `images.remotePatterns`.

**Fix:** Add the Shopify CDN hostname:

```ts
images: {
  remotePatterns: [{ hostname: "cdn.shopify.com" }],
},
```

**Expected result:** Shopify CDN images render through Next.js Image.

## A Storefront request fails with GraphQL errors

**Symptom:** `assertStorefrontOk()` throws, or the response reports an invalid GraphQL field, argument, type, or enum value.

**Check:** Validate the operation against the live Storefront API schema for the configured `SHOPIFY_API_VERSION` using Shopify AI Toolkit; typecheck does not catch invalid fields.

**Fix:** Correct the operation and load the affected route again.

If Shopify AI Toolkit is unavailable, install the project plugin:

```bash
npx plugins add Shopify/shopify-ai-toolkit --scope project --yes
```

Use the [`shopify-graphql-reference` skill](/docs/skills/shopify-graphql-reference) only for template integration conventions.

**Expected result:** The route renders and the Storefront response carries no `errors`.

## Shopify changes do not appear

**Symptom:** Updated products or collections still show old content.

**Check:** Confirm Shopify webhooks are configured and signed with `SHOPIFY_WEBHOOK_SECRET`.

**Fix:** Point the required Shopify webhooks to `POST /api/webhooks/shopify`. Follow [Webhooks](/docs/anatomy/webhooks) for setup. After a product or collection edit, check that Shopify reports a successful delivery and that the endpoint returns a nonempty `tagsInvalidated` list.

**Expected result:** Shopify changes invalidate the affected cached content and appear without a redeploy.

## Locale or currency does not change

**Symptom:** The storefront keeps one language or currency after a regional selection.

**Check:** The default serves one deployment using `shopConfig.localization` in `lib/config/index.ts`. Storefront copy, display formatting, and Shopify commerce context are separate. A formatting locale does not translate labels or convert prices.

**Fix:** Edit [storefront copy](/docs/anatomy/i18n) for a single-language deployment, or use [Enable i18n](/docs/skills/enable-i18n) for translated UI and locale URLs. For regional commerce, run [`/vercel-shop:enable-shopify-markets`](/docs/skills/enable-shopify-markets) and choose locale-prefixed, cookie-based, or per-domain routing.

**Expected result:** Copy changes affect labels. Markets changes send the selected commerce context to Shopify; currency comes from Shopify's response, not a storefront currency map.

## A coding agent cannot find project context

**Symptom:** A coding agent misses project commands or shop-specific guidance.

**Check:** Confirm your agent reads the project's `AGENTS.md` and recognizes the installed Vercel Shop, Vercel, and Shopify plugins. Plugin support and configuration depend on the agent.

**Fix:** From the project root, run:

```bash
npx create-vercel-shop@latest --no-template
```

If needed, install the plugins individually:

```bash
npx plugins add vercel/shop --scope project --yes
npx plugins add vercel/vercel-plugin --scope project --yes
npx plugins add Shopify/shopify-ai-toolkit --scope project --yes
```

The `--no-template` command installs plugins; it does not restore a missing `AGENTS.md`. Restore that file from your project's version control if it was removed.

**Expected result:** Your agent lists the installed plugins and can read the project guidance.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Shopify Integration
description: How the template maps to Shopify concepts - menus, products, collections, and cache invalidation via webhooks.
url: "https://shop-docs.labs.vercel.dev/docs/shopify"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: reference
prerequisites:
  - /docs/getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Use this page to find where Shopify content appears in the storefront. For credentials and permissions, see [Getting Started](/docs/getting-started) and [Storefront API Permissions](/docs/reference/storefront-api-permissions).

| Shopify content    | Storefront location                                             | Setup or behavior                                                                                    |
| ------------------ | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Products           | `/products/[handle]`                                            | [Product setup](/docs/shopify/pdp) and [PDP anatomy](/docs/anatomy/pages/pdp)                        |
| Collections        | `/collections/[handle]`                                         | [PLP anatomy](/docs/anatomy/pages/plp)                                                               |
| Pages              | `/pages/[handle]`                                               | [Content pages](/docs/anatomy/pages/content)                                                         |
| Blogs and articles | `/blogs/[blogHandle]` and `/blogs/[blogHandle]/[articleHandle]` | [Content pages](/docs/anatomy/pages/content)                                                         |
| Policies           | `/policies/[handle]`                                            | [Footer](/docs/anatomy/footer)                                                                       |
| Search             | `/search`                                                       | [PLP and search anatomy](/docs/anatomy/pages/plp)                                                    |
| Cart               | `/cart` and cart overlay                                        | [Cart](/docs/anatomy/cart)                                                                           |
| Menus              | Navigation and footer                                           | [Navigation](/docs/anatomy/navigation) and [Enable Shopify menus](/docs/skills/enable-shopify-menus) |

Navigation uses the template's configured links by default. To manage navigation and footer links in Shopify Admin, follow [Enable Shopify menus](/docs/skills/enable-shopify-menus).

Configure [Shopify webhooks](/docs/anatomy/webhooks) so product and collection edits appear promptly. Without webhooks, cached content remains until its cache lifetime expires.

For Storefront API behavior, see [Storefront API](/docs/reference/storefront-api).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Product Setup
description: Shopify product configuration for the Product Detail Page - variants, swatches, recommendations, bundles, and SEO.
url: "https://shop-docs.labs.vercel.dev/docs/shopify/pdp"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: reference
prerequisites:
  - /docs/anatomy/pages/pdp
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Configure a Shopify product so shoppers can find it, choose a variant, and add it to the cart. For storefront behavior, see [Anatomy → PDP](/docs/anatomy/pages/pdp).

## Publish the product

Add a title, description, featured image, and at least one variant. Assign the product to a collection if it should appear on a collection page, then publish it to the **Headless** sales channel.

Open its storefront URL and confirm that the product loads and an available variant can be added to the cart.

## Add option swatches

Configure color or image swatches for the product's option values in Shopify Admin. The storefront displays those swatches when available. Color options can also use variant images; other options use text buttons. A single-value option is shown as a label rather than a picker.

## Configure recommendations

Shopify provides related products automatically. To show complementary products as “Pairs Well With,” install [Shopify Search & Discovery](https://apps.shopify.com/search-and-discovery) and assign recommendations under **Product recommendations**. The section stays hidden when no complementary products are assigned.

## Publish a fixed bundle

Create the bundle with [Shopify Bundles](https://apps.shopify.com/shopify-bundles), then publish it to **Headless**. A bundle available only on **Online Store** does not appear in Vercel Shop.

Publish the component products to Headless as well. If the bundle still does not appear, see [Troubleshooting → A bundle returns 404](/docs/reference/troubleshooting#a-bundle-returns-404).

## Set search and sharing details

Set the product's search engine title and description in Shopify Admin when the defaults are not suitable. Otherwise, the storefront uses the product title and description.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Build with Vercel Shop
description: Build or adapt Shopify storefronts with source-backed Vercel Shop patterns.
url: "https://shop-docs.labs.vercel.dev/docs/skills/build-shop"
docs_index: /llms.txt
lastUpdated: 2026-09-15
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## How to use

```bash
/vercel-shop:build-shop
```

Use this skill to build or adapt home, collection, search, product, cart, and account pages while preserving storefront behavior.

<div className="pb-6" />

# Build with Vercel Shop

Build the shop around commerce truth first: what product or collection is being shown, what the customer has selected, what can be bought now, what price is authoritative, and what state belongs in the URL, Shopify, the server cache, or the browser. Use Vercel Shop's template as the reference implementation, but adapt the behavior to the current storefront instead of copying files blindly.

## Ground the work in source

1. Read the project's `AGENTS.md`, the target route, its view contract, and every operation it calls.
2. If the Vercel Shop template source is available, inspect it before relying on this prose:
   - Template rules: `apps/template/AGENTS.md`
   - Route/layout shell: `apps/template/app/layout.tsx`, `apps/template/app/page.tsx`
   - Shopify boundary: `apps/template/lib/shopify/storefront/server.ts`, `apps/template/lib/shopify/operations/`, `apps/template/lib/shopify/transforms/`, `apps/template/lib/product/types.ts`, `apps/template/lib/customer/types.ts`
   - Next.js cache boundary: `apps/template/lib/<domain>/server.ts` cached `get*` wrappers around the uncached `fetch*` operations
   - Shared UI layout primitives: `apps/template/components/ui/container.tsx`, `apps/template/components/ui/page.tsx`, `apps/template/components/ui/sections.tsx`
   - Public source fallback: [apps/template source](https://github.com/vercel/shop/tree/main/apps/template)
3. Use the docs to orient before editing unfamiliar areas:
   - [Architecture docs](https://vercel.shop/docs/anatomy)
   - [Route reference](https://vercel.shop/docs/reference/routes)
   - [Shopify integration docs](https://vercel.shop/docs/shopify)
   - [API boundary docs](https://vercel.shop/docs/reference/storefront-api)
4. Read the local Next.js documentation under `node_modules/next/dist/docs/` for any API being changed. This template may use newer Cache Components and prefetch behavior than model knowledge.
5. Preserve existing cache directives, promises, invalidation tags, auth gates, redirects, metadata, and `notFound()` behavior unless the task explicitly changes the domain behavior.

## Hydrogen ownership

The template uses the framework-agnostic Hydrogen preview SDK, not Hydrogen's React Router application framework. Next.js owns routing, rendering, public-data caching, and invalidation. Hydrogen owns commerce state and protocols; the template supplies presentation and framework adapters. Use Hydrogen's cart provider/forms/handlers rather than rebuilding cart state or hydration. Cart types are handler-derived; catalog/account presentation uses domain transforms. Read the installed SDK guidance and verify adapter behavior before changing these boundaries.

## Organize code by domain and execution context

Organize `lib/` by domain, then use only the context files that domain needs:

- `index.ts` contains universal implementation safe for server and client imports; it is never a re-export barrel.
- `server.ts` contains server-side implementation; keep it out of client import graphs.
- `client.ts` is a `"use client"` boundary for browser interaction, not an HTTP transport wrapper.
- `action.ts` is a `"use server"` entry point whose exports use a verb plus `Action` suffix.
- `types.ts` owns named shared contracts; consumers import types directly rather than through `server.ts` or `action.ts`.

Subdivide large domains with the same context filenames. Put Shopify transports in their API domain's `server.ts` and pure transforms in `index.ts`, even when all current callers run on the server. Do not add descriptive sibling files, flat root implementation modules, empty entry points, forwarding exports, or barrels.

Keep storefront models in their owning domain's `types.ts`, such as `lib/product/types.ts`, `lib/collections/types.ts`, and `lib/customer/types.ts`. Shared use does not change ownership: primitives live in `lib/money/types.ts`, `lib/media/types.ts`, and `lib/pagination/types.ts`. Types-only domains need no `index.ts` or re-export barrel. Keep SDK-specific contracts under `lib/shopify/` and handler-derived cart contracts in `lib/cart/types.ts`. Component props stay with their component; small private implementation types may stay local. Generated artifacts keep generator-owned paths and names.

## Copy and deployment defaults

The default storefront uses inline component copy and reusable content functions in `lib/content/index.ts`. Keep copy server-first, pass primitive labels to UI primitives and client leaves, and keep interactive copy in its consuming leaf and import shared content functions only when needed. Do not add a custom `t()` parser, a full browser catalog, or next-intl for a single deployment.

`shopConfig.localization` explicitly sets `{ country: "US", language: "EN", locale: "en-US" }`; country/language configure commerce requests and locale configures formatting. Currency comes from Shopify. Clean URLs and one deployment are the default; `lib/i18n/` and a Server Component locale resolver are absent. Operation locale/cache inputs may still be intentional. Preserve existing next-intl, catalogs, localized routes, and custom commerce behavior in upgraded installations. Use `enable-i18n` for copy/routing or `enable-shopify-markets` for regional commerce rather than coupling the two implicitly.

## Shop Agent boundaries

Shop Agent, customer authentication, and BotID are disabled by default. Preserve the installation's feature settings; a storefront redesign or localization change must not enable them implicitly.

For assistant changes, read the installed Eve docs starting at `node_modules/eve/docs/README.md`, then only the guide relevant to the task. Preserve Next.js deployment through `withEve` and the conditional plugin list passed to `withShopConfig` in `next.config.ts`.

Eve owns `/eve/v1/*`. Next.js prepares the browser cart through `/api/agent/session`; do not add another chat route or internal commerce HTTP bridge. Tool schemas and execution belong in `agent/tools/`, shared execution helpers in `agent/lib/`, and browser state, presentation helpers, and shared result types in `lib/agent/`. Keep Next.js request/cache APIs out of Eve's runtime imports. Use shared uncached catalog operations and Hydrogen cart handlers directly, with cart identity bound from the browser cookie rather than model arguments.

Preserve confirmed-mutation signals and Hydrogen reconciliation; restored messages must not replay cart writes or reopen the drawer. Do not claim exactly-once writes, conversation ownership, or server-history deletion on Clear. For conversation changes, check multiple search turns, visible results, and cumulative usage. For session controls, check restoration, Stop/Clear recovery, and failures. Mitigate abuse and cost with BotID, rate limits, and Gateway spending controls rather than per-session token caps. Distinguish browser/live-store evidence from mocks and build checks.

## Route the work

Read `references/rendering-architecture.md` for every route. Then read only the route references that apply:

- Commerce behavior and end-to-end flows: `references/commerce-flows.md`
- Home and marketing routes: `references/home.md`
- Collection and search routes: `references/plp-search.md`
- Product routes: `references/pdp.md`
- Cart and authenticated account routes: `references/cart-account.md`
- Cart provider, bootstrap, optimistic state, and mutations: `references/cart-provider.md`

## Model the route

Write down the route as five layers before editing, using commerce nouns instead of generic component names:

1. **Route orchestration** — resolve handles, locale, selected options, filters, pagination, redirects, metadata, auth, and real 404s.
2. **Data operation** — fetch Shopify data, transform provider types into shop domain types, and own cache scope, tags, and invalidation names.
3. **Server composition** — render the stable catalog shell first and place independent sections like recommendations, facets, reviews, and account/cart details behind granular boundaries.
4. **Client island** — own only selection, optimistic intent, browser APIs, effects, and event handlers: variant pickers, galleries, filter controls, carts, and forms.
5. **Mutation boundary** — use server actions for app-owned mutations and Hydrogen's proxy handlers for browser cart mutations. Return canonical Shopify state and warnings; invalidate public domain caches only when applicable. Let Hydrogen reconcile cart state without a parallel optimistic reducer.

For each layer's full ownership boundary, see the responsibility table in `references/rendering-architecture.md`. For every dependency, classify it as cacheable shell content, request-time shared content, request-time personalized content, or client-only state. Draw an edge only when one result genuinely requires another. Start all other work concurrently.

## Storefront gotchas

- Product selection must round-trip through direct visits, links, refreshes, and client navigation, then drive media, price, availability, add-to-cart payloads, and canonical links.
- Collection/search URLs are the source of truth for browsing. Empty results, unavailable facets, and back/forward navigation are not optional states.
- Cart UI must keep confirmed Shopify state separate from pending optimistic intent so concurrent mutations, bootstrap, errors, and warnings cannot double-apply or disappear.
- Customer, account, cart identity, and authorization state must never leak into shared product, collection, menu, page, or recommendation caches.
- Missing images, deleted products, sold-out variants, empty collections, partial Shopify failures, and disabled auth are normal storefront cases. Represent them deliberately.

## Keep the Shopify boundary explicit

Use the installed Shopify AI Toolkit for Shopify API documentation, schema facts, operation design, and validation. Invoke its API-specific skill before adding or changing Storefront or Customer Account GraphQL. For metafields or metaobjects, use its custom-data skill first.

Use `/vercel-shop:shopify-graphql-reference` only after Shopify validation to apply Vercel Shop conventions: operation placement, domain transforms, cache role, locale flow, invalidation, and route integration. Never treat this architecture skill as a substitute for authoritative Shopify validation.

## Prevent blocking

1. Keep route identity, stable headings, primary media, selected price/availability, and likely LCP content in the earliest shell the data contract permits.
2. Resolve route identity early only when it is required for correctness or shell coherence.
3. Pass request-dependent promises downward unawaited. Resolve them in the smallest component that needs them and place Suspense there.
4. Use sibling boundaries for independent work. Do not put the whole page behind the slowest Shopify request.
5. Give every visible fallback the same outer geometry as its resolved content.
6. Keep components server-rendered by default. Do not move reads into client effects or internal HTTP endpoints.
7. Keep cache policy in the data layer and personalized data out of shared caches.
8. Treat prefetching as a traffic-versus-latency decision, not a default fix for blocking architecture.

## Use a browser feedback loop

For visible storefront changes, close the loop in a real browser before calling the work done. Use the agent's built-in browser when available; otherwise use the standalone `agent-browser` CLI.

1. Start the local storefront and open each affected route: `agent-browser open http://localhost:3000/...`.
2. Capture the current state: `agent-browser wait --load networkidle`, `agent-browser snapshot`, and `agent-browser screenshot --full`.
3. Exercise the commerce interaction through the UI: variant selection, filtering, pagination, add/update/remove cart, auth redirects, or the changed control.
4. Re-snapshot after every navigation or DOM change before reusing element refs.
5. Repeat on mobile and desktop viewports when layout, media, overlays, filters, cart, or primary actions changed.
6. Iterate on the implementation until the browser outcome matches the intended commerce behavior. If `agent-browser` is unavailable, say so and report the fallback checks used.

## Verify the architecture

Run the static hotspot scan from the skill directory:

```bash
node scripts/audit-storefront.mjs <storefront-root>
```

Treat its output as review prompts, not measurements. Then:

1. Run targeted lint, typecheck, tests, and affected flows available in the current environment.
2. Test direct visits, client navigations, back/forward navigation, and URL round-trips for the routes changed.
3. Verify that non-critical Shopify work does not delay the shell, primary media, selected price/availability, or primary interaction.
4. Run every affected flow in `references/commerce-flows.md`, including failure, empty, sold-out, and missing-media states.
5. Inspect failed requests, layout shifts, LCP discovery, hydration, and interaction behavior when relevant.
6. Require a production build only when build, prerendering, caching, bundling, deployment behavior, or release readiness is in scope.
7. Use bundle analysis or deployed field data only for an explicit performance investigation when those tools and data are available. Do not request them as routine completion work or claim measured improvement from code inspection.

Report the final layer boundaries, blocking dependencies, cache ownership, and remaining risks.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Analytics
description: Add Vercel Analytics, Vercel Speed Insights, and Google Tag Manager to the storefront.
url: "https://shop-docs.labs.vercel.dev/docs/skills/enable-analytics"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## How to use

```bash
/vercel-shop:enable-analytics
```

<div className="pb-6" />

# Enable Analytics

The current storefront includes support for Vercel Web Analytics and Vercel Speed Insights, with each integration disabled by default in `lib/config/index.ts`. This skill enables or adds those integrations and can also add Google Tag Manager using the recommended integration.

## Before you start

Ask the user two questions in order:

### 1. Do you need to add or change Vercel Analytics and/or Vercel Speed Insights?

- **Enable both** — page views, custom events, and Core Web Vitals
- **Analytics only** — page view and custom event tracking via `@vercel/analytics`
- **Speed Insights only** — Core Web Vitals monitoring via `@vercel/speed-insights`
- **Neither** — keep both integrations disabled

### 2. Do you want Google Tag Manager?

If yes, ask for the GTM container ID (e.g. `GTM-XXXXXX`). This will be stored in the `NEXT_PUBLIC_GTM_ID` environment variable.

Wait for the user to answer both questions before proceeding.

***

## Part A: Vercel Analytics and Speed Insights

If the storefront has `analytics` configuration in `lib/config/index.ts`, enable only the selected integrations. If the user selected neither, keep both integration gates disabled and skip the remaining steps in this section.

```ts
analytics: {
  shopify: { consentMode: "default-banner", isEnabled: false },
  speedInsights: { isEnabled: false },
  vercel: { isEnabled: false },
},
```

The `shopify` gate is separate: it controls Shopify storefront analytics, which the storefront already ships. Leave it alone unless the user asked about Shopify analytics, and see Part D.

### A1. Install dependencies

For older storefronts without the integrations, install only the packages the user selected:

```bash
# Both
pnpm add @vercel/analytics @vercel/speed-insights

# Analytics only
pnpm add @vercel/analytics

# Speed Insights only
pnpm add @vercel/speed-insights
```

For older storefronts, create or update the root analytics component described below. Each library handles its own client-side behavior internally.

***

## Part B: Google Tag Manager

Skip this section if the user did not want GTM.

### B1. Install dependency

```bash
pnpm add @next/third-parties
```

### B2. Add environment variable

Add to `.env.example`:

```
# Google Tag Manager (optional)
NEXT_PUBLIC_GTM_ID="GTM-XXXXXX"
```

Set the actual value in `.env.local` or in the Vercel dashboard under Environment Variables.

### B3. Add GTM to `components/analytics/index.tsx`

Import `GoogleTagManager` from `@next/third-parties/google`. Read `NEXT_PUBLIC_GTM_ID` in the analytics component and render `<GoogleTagManager gtmId={gtmId} />` only when the value exists. If the storefront extends `lib/config/index.ts` with a GTM integration gate, apply that gate inside the same component.

***

## Part C: Root analytics integration

### C1. Update `components/analytics/index.tsx`

Extend the existing root analytics component. Do not create a sibling `components/analytics.tsx`, which would shadow the directory import. Preserve `ShopifyScriptsTracker`, its shop data, and the existing consent integration while adding the selected providers:

```tsx
import { GoogleTagManager } from "@next/third-parties/google";
import { Analytics } from "@vercel/analytics/next";
import { SpeedInsights } from "@vercel/speed-insights/next";

import { shopConfig } from "@/lib/config";
import { getShopAnalytics } from "@/lib/analytics/server";

import { ShopifyScriptsTracker } from "./shopify-client";

export async function AnalyticsComponents() {
  const gtmId = process.env.NEXT_PUBLIC_GTM_ID;

  return (
    <>
      {shopConfig.analytics.vercel.isEnabled ? <Analytics /> : null}
      {shopConfig.analytics.speedInsights.isEnabled ? <SpeedInsights /> : null}
      {gtmId ? <GoogleTagManager gtmId={gtmId} /> : null}
      <ShopifyScriptsTracker
        shop={await getShopAnalytics({})}
        storefrontId={process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ID ?? ""}
      />
    </>
  );
}
```

Remove imports for integrations the storefront does not support.

### C2. Update `app/layout.tsx`

Render the root analytics component near the end of `<body>`. The default has inline component copy, not a translation provider; in an already localized installation, preserve its scoped translation-provider boundaries:

```tsx
import { AnalyticsComponents } from "@/components/analytics";
```

```tsx
<body ...>
  {/* ... existing layout content and scoped providers ... */}
  <Suspense>
    <AnalyticsComponents />
  </Suspense>
</body>
```

Do not add next-intl or a root message catalog for analytics. In a customized localized storefront, preserve any required commerce locale prop and existing scoped providers; do not pass the complete catalog to a root `NextIntlClientProvider`. The root component remains mounted as the extension point for current and future analytics providers. Provider gates stay inside it so disabled integrations are not mounted.

## Part D: Shopify storefront analytics

Only make changes here if the user asked about Shopify analytics.

The storefront sends page, product, collection, search, cart-view, and confirmed cart-change events through Hydrogen's analytics bus. It is disabled by default and requires no credentials beyond the Storefront API variables the storefront already needs. To turn Shopify's built-in analytics destination on, set `analytics.shopify.isEnabled` to `true` in `lib/config/index.ts`.

Consent mode is set by `analytics.shopify.consentMode` in `lib/config/index.ts` and defaults to `default-banner`, which renders Shopify's hosted privacy banner for visitors in regions that require consent. Use `custom-banner` when the storefront supplies its own consent UI. Do not ship `no-banner` in production unless consent is handled elsewhere, because visitors in those regions can never grant consent and their events are dropped.

Third-party analytics can subscribe through the same destination API, so consent gating and buffered replay remain centralized. Register destinations with `addAnalyticsDestination()` from `lib/analytics/client.ts`; do not publish cart-change events manually.

## Guardrails

- Keep root analytics providers and their gates in `components/analytics/index.tsx`; preserve the existing Shopify scripts and consent integration.
- Always mount `<AnalyticsComponents />` from the root layout, even when every provider is disabled.
- The GTM container ID must come from `NEXT_PUBLIC_GTM_ID`, never hardcoded. The provider renders nothing if the env var is missing.
- Use `@next/third-parties/google` for GTM, not a manual `<script>` tag. The Next.js component handles script loading and performance optimization.
- Import paths: use `@vercel/analytics/next` and `@vercel/speed-insights/next` (the `/next` subpath), not the root package exports.
- Add `NEXT_PUBLIC_GTM_ID` to `.env.example` with a placeholder value so other developers know the variable exists.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: i18n
description: Locale-prefixed URLs and per-locale message catalogs via next-intl, without Shopify Markets.
url: "https://shop-docs.labs.vercel.dev/docs/skills/enable-i18n"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## How to use

```bash
/vercel-shop:enable-i18n
```

<div className="pb-6" />

# Enable i18n (next-intl, no Markets)

Add next-intl so the storefront serves locale-prefixed URLs (`/en-US/products/foo`), loads per-locale message catalogs, and exposes a copy-language switcher. The default is one deployment with clean URLs, inline component copy with reusable functions in `lib/content/index.ts`, and `shopConfig.localization = { country: "US", language: "EN", locale: "en-US" }`. There is no next-intl dependency, message catalog, `lib/i18n/` directory, or `lib/params/server.ts` locale resolver to reuse in a fresh template.

> **Use `enable-shopify-markets` instead** for regional commerce. This skill translates storefront copy and adds routing; it must not infer a commerce country from a copy locale or mutate cart buyer country when language changes. Keep Shopify country/language configuration explicit, preserve intentional operation locale/cache inputs, and always display currency from Shopify responses.

## Inspect and preserve the installation

Read scoped `AGENTS.md`, `package.json`, `next.config.ts`, `lib/config/index.ts`, `lib/content/index.ts`, routes, components, layout, proxy, and any existing localization files. Trace copy consumers, formatting, SEO, markdown, cart, auth, and chat boundaries before editing.

Choose the migration path from evidence:

- **Fresh simplified template:** introduce next-intl, catalogs, request config, routing, and the locale resolver using the steps below.
- **Already localized or customized:** preserve its next-intl version/configuration, catalogs, translations, rich text, providers, supported locales, domains, prefixes, locale cookies, redirects, and commerce behavior. Fill only missing pieces. Do not replace existing catalogs with template English, move routes twice, or reset the locale list to these examples. If the requested routing conflicts with existing public URLs, obtain a migration decision before changing them.
- **Mixed migration:** inventory inline copy, content functions, and catalogs. Convert only unmigrated consumers and retain modules still in use. Do not delete working translations or collapse locale-sensitive commerce/cache arguments.

Confirm the default copy locale and supported copy locales with the user. In a noninteractive run, report any unresolved choice and stop rather than choosing a routing or translation policy.

## Introduce next-intl and migrate copy

1. On a fresh installation, run `pnpm add next-intl` from the storefront root. Read the installed next-intl plugin/request/routing APIs and local Next.js guides before wiring them. If next-intl already exists, preserve its compatible version rather than reinstalling blindly.
2. Create `lib/i18n/request/server.ts` as described below, then create the plugin with `createNextIntlPlugin` from `next-intl/plugin` and the explicit request-config path. In the current template, add that plugin to the list passed to `withShopConfig(nextConfig, plugins)`; preserve the conditional `withBotId` and `withEve` entries and their existing order. Do not pass the exported async config factory to a plugin that expects a config object. Preserve customized wrapper composition, rewrites, redirects, and Cache Components settings, and do not enable optional features as part of localization.
3. Inventory inline JSX text, labels in component configuration, template literals, and reusable functions in `lib/content/index.ts`. Create the default catalog from the storefront's actual customized copy, not a template snapshot. Convert functions to equivalent ICU messages with the same parameter names, zero/one/many behavior, number formatting, rich text, and accessibility labels. Do not serialize functions into JSON or build a custom `t()` parser.
4. Create catalogs and explicit loaders for each approved locale. Keep keys and interpolation arguments aligned. Do not present an English fallback as a completed translation; agree on any temporary fallback before enabling that locale publicly.
5. Replace inline server copy and content function calls with `getTranslations()` from `next-intl/server`. Pass translated primitive labels to client leaves when possible. For interactive plurals/interpolation, wrap only the relevant leaf in a Server Component's `NextIntlClientProvider` with the namespaces it uses, then use `useTranslations()` there. Never pass the full catalog from the root layout, and never pass ordinary copy functions across the server/client boundary. Keep `components/ui/` copy-agnostic.
6. Cover error boundaries, not-found screens, metadata, email/contact text, and dynamic announcements as well as visible page headings. Components outside a provider need resolved labels or an explicitly scoped provider. Keep a minimal fallback for global errors that cannot access locale context.
7. Set `<html lang>` and UI number/date formatting from the validated copy locale. Leave `shopConfig.localization.country` and `.language` as deployment commerce settings unless Shopify content translation is explicitly requested and validated. A copy locale such as `fr-FR` does not by itself mean shipping/pricing country `FR`.
8. After all consumers are migrated and checked, remove only unused content functions. Preserve custom copy and existing catalogs. Update the installation's `AGENTS.md` to require aligned locale catalogs and scoped providers now that it is localized.

## Create the locale source of truth

On a fresh installation, create `lib/i18n/index.ts` with the user's approved locales. On an existing installation, extend its current source of truth instead. Routing, sitemap, alternates, and the switcher must read the same list. This list describes copy/routing locales, not a locale-to-currency or commerce-country map.

Example only; replace with the approved list and seed the default from the deployment's formatting locale when appropriate:

```ts
import type { Locale } from "./types";

export const locales = ["en-US", "fr-FR"] as const;
export const defaultLocale: Locale = "en-US";
export const enabledLocales: readonly Locale[] = locales;

export function isEnabledLocale(value: string): value is Locale {
  return enabledLocales.some((locale) => locale === value);
}
```

Define the shared `Locale` contract in `lib/i18n/types.ts`; import it directly wherever it is needed:

```ts
import type { locales } from "./index";

export type Locale = (typeof locales)[number];
```

Use domain/context files for new modules: universal routing configuration in `lib/i18n/routing/index.ts`, client navigation in `lib/i18n/navigation/client.ts`, server request configuration in `lib/i18n/request/server.ts`, and the root-param resolver in `lib/params/server.ts`. Do not add barrels or forwarding exports. Preserve working paths in an existing customized installation rather than renaming them solely to match these examples.

Validate route params, action inputs, and request payloads against this list. Retain any existing resolver and fallback policy rather than resetting it.

## What this skill turns on

1. `lib/i18n/routing/index.ts` and `lib/i18n/navigation/client.ts` (next-intl)
2. Route segment `app/[locale]/` containing every page
3. `proxy.ts` middleware running `next-intl/middleware`
4. `lib/params/server.ts` `getLocale()` reading from `next/root-params`
5. A new next-intl plugin wrapper, catalogs, and `lib/i18n/request/server.ts` loading messages by resolved locale
6. Locale-prefixed canonicals + hreflang alternates in `lib/seo/index.ts`
7. Sitemap entries per locale
8. `next.config.ts` rewrites/redirects on `/:locale/*` sources
9. `app/(unlocalized)/page.tsx` fallback redirect to default locale
10. `generateStaticParams` on the root layout
11. Add or adapt a copy-language selector without introducing a currency selector

## Cache Components compatibility — read this first

The template runs with `cacheComponents: true` (Next.js 16). That changes a few things this skill needs to handle correctly. Skipping any of these will produce build errors that look unrelated:

### A. There must be no `app/layout.tsx` above `app/[locale]/`

For `[locale]` to be recognized as a root param, the dynamic segment must be the root layout. After Step 2, the file at `app/layout.tsx` should be gone (moved into `app/[locale]/layout.tsx`). If both exist, `rootParams.locale()` returns `undefined`.

### B. `setRequestLocale` is not used

next-intl docs sometimes show `setRequestLocale(locale)` calls in layouts/pages. **Don't add them under cacheComponents.** That helper writes to a request-scoped store and forces dynamic rendering — it defeats the cache. The rootParams + request-config pattern below makes it unnecessary because the resolved locale is already a cache key.

### C. Don't swap `next/link` to next-intl's `<Link>`

The straightforward instinct is to replace every `import Link from "next/link"` with `import { Link } from "@/lib/i18n/navigation/client"`. **Don't.** next-intl's Link reads request context (locale) on render; in a server-component tree under cacheComponents, that triggers:

```
Error: Route "/[locale]/..." accessed [...] which is not defined in the `unstable_samples` of `instant`.
```

or a generic "blocking route" prerender failure.

**Do this instead:** keep `next/link` and pass explicitly locale-prefixed hrefs from a Server Component using its validated locale. Middleware can redirect legacy unprefixed paths, but those redirects may negotiate a different locale and must not be the only mechanism keeping navigation in the selected language.

For Server Component redirects, use `next/navigation` and an explicitly prefixed path: `` `/$\{await getLocale()\}/account/login` ``. `next/root-params` is not available in Server Actions or Route Handlers: receive and validate locale at those boundaries instead. Do not rely on middleware language detection to preserve the current URL locale; prefer explicit prefixed hrefs passed from the server for ordinary links.

### D. `instant` samples need `locale` in `params`

Any route that exports `instant` (currently: products `[handle]`, collections `[handle]`, search) needs `locale` added to every sample, or the build fails:

```
Error: Route "/[locale]/products/[handle]" accessed root param "locale"
       which is not defined in the `unstable_samples` of `instant`.
```

Fix:

```ts
export const instant = {
  unstable_samples: [
    {
      params: { locale: "en-US", handle: "__placeholder__" }, // ← add locale
      searchParams: { variant: "1" },
      cookies: [{ name: "shopify_cartId", value: null }],
    },
  ],
};
```

### E. `instant` samples need `headers` declarations if any layout-level server component reads `headers()`

This is easy to forget. If you (or a downstream skill) adds a server component to the layout that calls `headers()` — e.g. a "Shipping to \{postal}" bar reading `x-vercel-ip-postal-code` — every `instant` sample in the app must declare the headers it might access:

```ts
unstable_samples: [
  {
    params: { locale: "en-US", handle: "__placeholder__" },
    searchParams: { variant: "1" },
    cookies: [{ name: "shopify_cartId", value: null }],
    headers: [["x-vercel-ip-postal-code", null]], // ← add this
  },
],
```

`null` means "header may be absent." If you forget, the build error is explicit:

```
Error: Route "..." accessed header "x-vercel-ip-postal-code" which is not
       defined in the `unstable_samples` of `instant`. Add it to the
       sample's `headers` array, or `["...", null]` if it should be absent.
```

### F. Keep server redirects outside client navigation

Do not import `lib/i18n/navigation/client.ts` into a server auth gate. Use `next/navigation`'s `redirect` (which returns `never`) and prefix the locale yourself:

```ts
import { redirect } from "next/navigation";
import { getLocale } from "@/lib/params/server";

if (!session) redirect(`/${await getLocale()}/account/login`);
return session; // OK, narrowed
```

## Step-by-step

### Step 1: Routing config

Create `lib/i18n/routing/index.ts`:

```ts
import { defineRouting } from "next-intl/routing";
import { defaultLocale, enabledLocales } from "@/lib/i18n";

export const routing = defineRouting({
  locales: enabledLocales, // pulled from lib/i18n/index.ts — never hardcode
  defaultLocale,
  localePrefix: "always",
});
```

Create `lib/i18n/navigation/client.ts`:

```ts
"use client";

import { createNavigation } from "next-intl/navigation";
import { routing } from "@/lib/i18n/routing";

export const { Link, redirect, usePathname, useRouter } = createNavigation(routing);
```

> Per "Cache Components compatibility C" above, `Link` here is mostly used by the locale switcher / programmatic routing in client components — not as a wholesale replacement for `next/link`.

### Step 2: Move routes under `app/[locale]/`

Move every route file from `app/` into `app/[locale]/`:

- `app/layout.tsx` → `app/[locale]/layout.tsx` (becomes the root layout for the locale segment). **Delete the original `app/layout.tsx` after the move** — see compatibility A above; both files cannot coexist.
- `app/page.tsx`, `app/error.tsx`, `app/not-found.tsx` → `app/[locale]/...`
- `app/account/`, `app/cart/`, `app/collections/`, `app/pages/`, `app/policies/`, `app/products/`, `app/search/` → `app/[locale]/...`

**Stay at `app/`:** `api/`, `agent/`, `md/`, `sitemap.xml/`, `sitemap/`, `robots.ts`, `global-error.tsx`, `globals.css`, `favicon.ico`. Include blogs and any custom storefront pages in the localized route audit; do not limit the move to the example list.

In the moved layout, fix `import "./globals.css"` → `import "../globals.css"`.

Update every `PageProps<"/foo">` and `LayoutProps<"/foo">` generic to include the locale segment: `PageProps<"/[locale]/products/[handle]">`, `LayoutProps<"/[locale]">`, etc.

### Step 3: Create `lib/params/server.ts` for Server Component root params

This is a new module on the simplified baseline. In a customized installation, preserve unrelated helpers and extend its existing resolver. Route Handlers use their route context or validated request inputs; Server Actions receive a validated locale argument, not this getter.

```ts
import { notFound } from "next/navigation";
import { locale as rootLocale } from "next/root-params";
import { locales } from "@/lib/i18n";
import type { Locale } from "@/lib/i18n/types";

export async function getLocale(): Promise<Locale> {
  const current = await rootLocale();
  if (!current || !locales.includes(current as Locale)) notFound();
  return current as Locale;
}
```

### Step 4: `lib/i18n/request/server.ts` loads messages by resolved locale

```ts
import { hasLocale } from "next-intl";
import { getRequestConfig } from "next-intl/server";
import { getLocale } from "@/lib/params/server";
import type enMessages from "@/lib/i18n/messages/en.json";
import { routing } from "@/lib/i18n/routing";

const messageLoaders: Record<string, () => Promise<{ default: typeof enMessages }>> = {
  "en-US": () => import("@/lib/i18n/messages/en.json"),
  "fr-FR": () => import("@/lib/i18n/messages/fr.json"),
};

// We intentionally do NOT destructure `{ locale }` from the callback args.
// next-intl populates that arg from the `x-next-intl-locale` request header,
// and reading request headers from inside a cached tree forces the route
// dynamic — every `instant` sample then needs an explicit
// `headers: [["x-next-intl-locale", null]]` declaration. Going straight to
// `getLocale()` (which reads `next/root-params`) keeps the lookup cacheable.
export default getRequestConfig(async () => {
  const requested = await getLocale();
  const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;
  const loader = messageLoaders[locale];
  const messages = (await loader()).default as typeof enMessages;
  return { locale, messages };
});
```

### Step 5: Extend `proxy.ts`

Compose next-intl after the existing Shopify route dispatch. `handleShopifyRoutes()` returns `null` synchronously when Hydrogen does not own the pathname, so check that result before locale routing without awaiting it:

```ts
const handleI18n = createMiddleware(routing);

// Keep the existing imports and add NextRequest as a runtime import.
export async function proxy(request: NextRequest): Promise<Response> {
  const requestContext = createCustomerRequestContext(request);
  const shopifyRoute = handleShopifyRoutes({
    // Preserve the template's handlers, session manager, and storefront client.
    request,
    requestContext,
  });
  if (shopifyRoute) return shopifyRoute;

  const i18nRequest = new NextRequest(request, {
    headers: requestContext.getForwardedRequestHeaders(),
  });
  const response = handleI18n(i18nRequest);
  requestContext.applyResponseHeaders(response.headers);
  if (!response.ok) return response;

  const rewriteHeader = response.headers.get("x-middleware-rewrite");
  if (!rewriteHeader) return response;

  const rewriteTarget = new URL(rewriteHeader, request.url);
  const [, ...segments] = rewriteTarget.pathname.split("/");
  const normalized = new URL(`/${segments.filter(Boolean).join("/")}`, request.url);
  normalized.search = rewriteTarget.search;
  return NextResponse.rewrite(normalized, { headers: response.headers });
}
```

Preserve the template's Shopify-owned API and protocol matchers, then add locale-prefixed Shopify endpoints now that locale routing is enabled:

```ts
export const config = {
  matcher: [
    // Keep every matcher already present in the template.
    "/:locale([a-zA-Z]{2}(?:-[a-zA-Z]{2})?)/agent/:action(handoff|buyer-claims).:format",
    "/:locale([a-zA-Z]{2}(?:-[a-zA-Z]{2})?)/cart.:format(js|json)",
    "/:locale([a-zA-Z]{2}(?:-[a-zA-Z]{2})?)/cart/:operation(add|update|change|clear).:format(js|json)",
  ],
};
```

Do not replace the explicit entries with `/api/:path*`: downstream applications must be able to add Route Handlers such as `/api/webhooks` or `/api/custom` without sending them through Shopify dispatch or locale middleware. If a new Hydrogen feature claims another reserved route, add that exact route family.

Keep Eve's `/eve/v1/` and `/_eve_internal/` routes outside Shopify dispatch and locale negotiation. Keep `/api/agent/session` and `/agent/ucp-profile.json` unlocalized. If Shop Agent is enabled, carry and validate the copy locale explicitly for conversation context and navigation outputs without changing the deployment's Shopify country/language or allowing client context to select a cart. Keep Next.js request/cache APIs out of Eve's runtime imports.

The file is `proxy.ts` (Next.js 16 convention), not `middleware.ts`.

### Step 6: Internal hrefs — keep `next/link`

Per the cache-components note above, **leave existing `next/link` imports alone** and pass locale-prefixed hrefs from the server. Inspect product cards, menus, breadcrumbs, search, cart, and pagination so navigation retains the selected language without a negotiation redirect. Use next-intl's client navigation in the locale switcher when needed, preserving the resource and query parameters. Reuse existing localized link helpers in customized installations.

For programmatic redirects in server code, use `next/navigation`'s `redirect`:

```ts
redirect(`/${await getLocale()}/account/login`);
```

### Step 7: `lib/seo/index.ts` — locale-aware canonicals + hreflang alternates

Keep this module universal: callers resolve and validate the locale on the server, then pass it explicitly. Do not import the server root-param resolver into `index.ts`.

```ts
import { defaultLocale, enabledLocales } from "@/lib/i18n";
import type { Locale } from "@/lib/i18n/types";

function withLocalePath(locale: string, pathname: string): string {
  const normalized = normalizePath(pathname);
  return normalized === "/" ? `/${locale}` : `/${locale}${normalized}`;
}

export function buildAlternates({
  locale,
  pathname,
  searchParams,
}: {
  locale: Locale;
  pathname: string;
  searchParams?: SearchParamsInput;
}): Metadata["alternates"] {
  const canonical = buildCanonicalPath(withLocalePath(locale, pathname), searchParams);

  const languages: Record<string, string> = {};
  for (const candidate of enabledLocales) {
    languages[candidate] = buildCanonicalPath(withLocalePath(candidate, pathname), searchParams);
  }
  languages["x-default"] = buildCanonicalPath(
    withLocalePath(defaultLocale, pathname),
    searchParams,
  );

  return { canonical, languages };
}
```

Update every caller to pass its validated locale. Server Components can call `getLocale()` from `lib/params/server.ts`; Route Handlers and Server Actions must validate their own inputs.

### Step 8: Sitemap per-locale entries

Edit `app/sitemap/[shard]/route.ts`. For every resource, emit one `<url>` per enabled locale and add `<xhtml:link rel="alternate" hreflang="..." href="..." />` siblings inside each `<url>` pointing at the other locale variants. Add `xmlns:xhtml="http://www.w3.org/1999/xhtml"` to the `<urlset>` opening tag.

```ts
import { enabledLocales } from "@/lib/i18n";

function localizePath(locale: string, pathname: string): string {
  if (pathname === "/") return `/${locale}`;
  return `/${locale}${pathname.startsWith("/") ? pathname : `/${pathname}`}`;
}

// Inside renderShard(): for each item, for each locale, emit a <url> with
// a <loc> at the localized path and an <xhtml:link> per other locale.
```

`app/sitemap.xml/route.ts` (the index) doesn't need locale handling — it only lists shard URLs, which stay locale-agnostic.

### Step 9: `next.config.ts` rewrites/redirects on `/:locale/*`

Existing markdown content-negotiation rewrites must move their `source` from `/products/:handle` to `/:locale/products/:handle`, etc. Destinations stay at `/md/products/:handle`, `/md/collections/:handle`, and `/md/search`. Inspect the existing handlers before forwarding locale; introduce and validate a copy-locale input where needed rather than assuming they already read it. Keep their deployment commerce context unchanged. Adapt existing redirects to locale-prefixed sources without restoring obsolete rules from an older template.

### Step 10: `app/(unlocalized)/page.tsx` fallback

```ts
import { permanentRedirect } from "next/navigation";
import { defaultLocale } from "@/lib/i18n";

export default function UnlocalizedRoot(): never {
  permanentRedirect(`/${defaultLocale}`);
}
```

This is a defensive fallback; with `localePrefix: "always"` middleware should already redirect `/`.

### Step 11: `generateStaticParams` on the locale layout

```ts
import { locales } from "@/lib/i18n";

export const generateStaticParams = async () => {
  return locales.map((locale) => ({ locale }));
};
```

### Step 12: Patch `instant` samples

Walk every route file that exports `instant` and add `locale` to each sample's `params`:

```ts
params: { locale: "en-US", handle: "__placeholder__" }
```

If any layout-level server component (e.g. a shipping/postal banner, geo-aware nav) reads `headers()`, also add a `headers` array to every sample:

```ts
headers: [["x-vercel-ip-postal-code", null]];
```

(See "Cache Components compatibility D/E" at the top.)

### Step 13: Add or adapt the language selector

Inspect the current navigation, including any Shopify-menu customization. The simplified template does not ship a dormant `LocaleCurrencySelector` to re-enable. Add a leaf language selector, or preserve and extend an existing one. Keep the current resource and query parameters when switching. A copy-language switch must not change cart country or invent a currency choice.

## Verifying

After applying:

```bash
pnpm lint
pnpm build
pnpm dev
# In another terminal, replace locale/handle with actual supported values:
curl -I http://localhost:3000/
curl -I http://localhost:3000/products/actual-handle
curl http://localhost:3000/sitemap.xml
curl http://localhost:3000/sitemap/products-1.xml
curl http://localhost:3000/en-US
```

Smoke-test checklist:

- [ ] Lint and build pass; restart dev after route moves so route types regenerate
- [ ] Default copy matches the pre-migration storefront, including custom text
- [ ] Every enabled catalog has matching keys and arguments; zero/one/many, interpolation, errors, and accessibility labels render correctly
- [ ] Client leaves receive only needed namespaces or primitive labels; no copy functions cross the RSC boundary
- [ ] Copy-language switching preserves Shopify country, cart identity, and currency behavior
- [ ] Existing localized installations retain translations, public URLs, providers, and custom commerce behavior
- [ ] API, OAuth, markdown, cart, and chat boundaries do not call the Server Component root-param getter
- [ ] Report which fresh and existing-installation migration paths were actually exercised; lint/build alone do not prove migration parity
- [ ] Bare `/` redirects to default locale
- [ ] Each enabled locale serves 200 at its prefix
- [ ] `<html lang>` matches the URL's locale segment
- [ ] Sitemap emits one entry per locale per page
- [ ] Canonical + hreflang alternates appear in page metadata
- [ ] Internal `next/link` hrefs preserve the selected locale; legacy unprefixed public URLs still redirect correctly

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Shopify Markets
description: Add regional locale routing and localized commerce with Shopify Markets.
url: "https://shop-docs.labs.vercel.dev/docs/skills/enable-shopify-markets"
docs_index: /llms.txt
lastUpdated: 2026-09-14
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## How to use

```bash
/vercel-shop:enable-shopify-markets
```

<div className="pb-6" />

# Enable Shopify Markets

Add multi-region commerce to the Vercel Shop template. The fresh baseline is one deployment with clean URLs, inline component copy and reusable functions in `lib/content/index.ts`, and explicit `shopConfig.localization = { country: "US", language: "EN", locale: "en-US" }`. It does not include next-intl, catalogs, `lib/i18n/`, or a `lib/params/server.ts` locale resolver.

Copy translation and commerce market selection are separate concerns. This skill's regional-locale strategy deliberately combines them: a validated `en-US`, `en-CA`, or `fr-CA` selects translated copy plus Shopify country/language context. Confirm that coupling fits the store before adopting it. Do not infer a shopper's commerce country merely from a copy locale introduced by `enable-i18n`. Preserve existing installations that intentionally separate language and country; do not force them into this example model.

For a new regional-locale implementation, do not invent a separate market key, redundant market-to-locale map, currency map, or `/market/locale` route. Existing custom market models need an explicit migration decision, not automatic removal.

Examples of valid public routing:

- Locale sub-path: `/fr-CA/products/shoe`
- Invisible cookie routing: `/products/shoe` for every locale
- Per-domain: `example.ca/fr-CA/products/shoe` or a single locale on `example.fr/products/shoe`

Never generate redundant paths such as `/ca/fr-CA/products/shoe`.

## Before editing

Read the current versions of:

- `package.json`, `lib/config/index.ts`, `lib/content/index.ts`, and inline copy throughout routes and components
- Existing `lib/i18n/`, catalogs, next-intl plugin/providers, locale resolvers, and localized routes if present; these do not exist on the fresh baseline
- `next.config.ts` and any existing `proxy.ts`
- Every Storefront API operation and cache wrapper
- Cart creation, cart actions, and buyer identity updates
- Customer Account auth context
- Agent/chat request payload and tools
- SEO, robots, sitemap, and markdown content-negotiation routes

The template uses Next.js 16 with Cache Components. Read the installed Next.js routing/proxy/root-param docs. Install next-intl only when missing, then read its installed plugin, request-config, and routing APIs before editing.

Classify the installation before proceeding:

- **Fresh simplified template:** follow the next-intl installation and copy-to-catalog migration in `enable-i18n`, then use this skill's selected routing strategy and commerce propagation instead of its copy-only rules.
- **Already localized/customized:** preserve existing catalogs, translations, copy functions not yet migrated, scoped providers, public URLs, domains, cookies, redirects, and commerce/cache contracts. Extend only missing functionality; do not reinstall next-intl blindly, reset supported locales, replace custom copy with template English, or move routes twice.
- **Copy-only i18n already enabled:** keep its translations and routing. Identify the existing fixed commerce country/language before introducing regional selection. Obtain approval for any new coupling or changed public URLs.

Inventory mixed copy/catalog consumers and retain modules still in use. Preserve intentional operation locale/cache inputs even where presentation does not use them. In a noninteractive run, stop and report unresolved routing or market-model choices rather than choosing for the user.

For any Shopify GraphQL edit, use Shopify AI Toolkit to confirm current API facts and validate the complete operation. Then follow `shopify-graphql-reference` for this template's placement, transforms, cache role, locale flow, and invalidation.

## 1. Ask for routing and locales

If the user has not already decided, ask which strategy they want:

1. **Locale sub-path** — one regional locale segment, such as `/en-US/...` and `/fr-CA/...`.
2. **Invisible cookie** — all public paths stay clean; next-intl internally rewrites `/products/...` to `/[locale]/products/...` using the locale cookie.
3. **Per-domain** — domains select the available/default locale context; paths may be prefixed only when a domain supports multiple languages.

For sub-path routing, ask whether the default locale should be:

- `as-needed`: `/products/...` for the default and `/fr-CA/products/...` for others
- `always`: `/en-US/products/...` and `/fr-CA/products/...`

Then ask for the default locale and all enabled locales. Require regional BCP 47 tags with both language and region. For example:

```text
en-US, en-CA, fr-CA, de-DE
```

Do not accept bare language tags such as `en` or `fr` for Markets mode. Do not ask for a separate market identifier or currency.

Before choosing invisible cookie routing, state its SEO tradeoff: every locale shares one canonical URL, so search engines and shared links cannot target a specific cookie-selected version. Use locale sub-paths or per-domain URLs when each localized version must be independently indexed.

## 2. Make regional locales the source of truth

For a fresh regional-locale implementation, create `lib/i18n/index.ts` with the approved list and seed the initial context from `shopConfig.localization`. If its explicit country/language and formatting locale disagree, resolve that deliberately instead of deriving a different market silently. For an existing implementation, extend its current source of truth without resetting values.

The following is an example of the opted-in regional-locale model, not a replacement for every store's custom configuration:

```ts
import type { Locale } from "./types";

export const locales = ["en-US", "en-CA", "fr-CA"] as const;

export const defaultLocale: Locale = "en-US";
export const enabledLocales: readonly Locale[] = locales;
export const localeSwitchingEnabled = enabledLocales.length > 1;
export const LOCALE_COOKIE_NAME = "NEXT_LOCALE";

export function isEnabledLocale(value: string): value is Locale {
  return enabledLocales.some((locale) => locale === value);
}
```

Define `Locale` in `lib/i18n/types.ts` as `type Locale = (typeof locales)[number]` with a type-only import of `locales` from `./index`, and import the contract directly from `types.ts`. Use `lib/i18n/routing/index.ts` for universal routing configuration, `lib/i18n/navigation/client.ts` for client navigation, `lib/i18n/request/server.ts` for request configuration, `lib/i18n/action.ts` for locale-switch actions, and `lib/params/server.ts` for the Server Component root-param resolver. Do not add barrels or forwarding exports. Preserve working module paths in customized installations rather than renaming them solely to match these examples.

Create boundary validation such as `isEnabledLocale` on the fresh baseline; retain existing `isEnabledLocale` / `resolveLocale` behavior where present. Reject unsupported request values instead of casting arbitrary strings to `Locale`. For the approved regional-locale strategy, derive Shopify context from the validated locale:

```ts
import type { CommerceLocale } from "@/lib/config/types";

export function getCountryCode(locale: Locale): CommerceLocale["country"] {
  const country = new Intl.Locale(locale).region;
  if (country === "US" || country === "CA") return country;
  throw new Error(`Unsupported commerce country: ${country}`);
}

export function getLanguageCode(locale: Locale): CommerceLocale["language"] {
  const language = new Intl.Locale(locale).language.toUpperCase();
  if (language === "EN" || language === "FR") return language;
  throw new Error(`Unsupported commerce language: ${language}`);
}

export function getCommerceLocale(locale: Locale): CommerceLocale {
  return { country: getCountryCode(locale), language: getLanguageCode(locale) };
}
```

These guards cover only the example locale list above. Extend them deliberately for the store's approved countries and languages; reject unsupported values rather than asserting them into Shopify types. Pass `getCommerceLocale(locale)` to operation `locale` options.

Prefer `Locale` over `string` for internal routing locale parameters. Request bodies, cookies, headers, route params, and query params remain untrusted strings until validated. Validate the country/language conversion against Shopify's supported values with Shopify AI Toolkit; do not assume uppercasing every BCP 47 language produces a valid Shopify language code.

The transport's operation `locale` option is a `CommerceLocale` object (`{ country, language }` from `lib/config/types.ts`), not a BCP 47 string. Convert once at the validated commerce boundary and propagate that object through existing operation/cache inputs. Keep the copy/routing `Locale` string distinct. For an older customized transport, adapt its actual signature instead of replacing it wholesale.

### Currency rule

Currency always comes from Shopify's localized response (`MoneyV2.currencyCode`, cart cost, product prices, etc.). Never infer currency from locale and never add `localeCurrency`, `marketCurrency`, or a locale-to-currency lookup.

When UI outside a price object needs a currency code, pass one from fetched Shopify data. If Shopify returns no product or cart from which to derive it, omit currency-specific UI rather than guessing.

## 3. Install next-intl and migrate copy

Use the `enable-i18n` section "Introduce next-intl and migrate copy" for the full migration. On a fresh storefront, run `pnpm add next-intl`, create its request config, and compose `createNextIntlPlugin` from `next-intl/plugin` around the existing Next config with the explicit request-config path. Use the existing `withShopConfig` plugin list when present, preserving conditional `withBotId` and `withEve` entries, their order, and the installation's feature settings. Preserve other wrappers, rewrites, and Cache Components settings. Do not apply copy-only commerce rules or force `localePrefix: "always"` when using this Markets skill.

Build the initial catalog from the installation's actual inline copy, component configuration labels, and reusable functions in `lib/content/index.ts`. Convert typed interpolation and plural functions to equivalent ICU messages, preserving parameters, zero/one/many behavior, rich text, errors, and accessibility labels. Do not serialize functions or write a custom `t()` parser. Preserve already translated/customized catalogs rather than regenerating them from the template.

Use server `getTranslations()` and primitive labels by default. Only interactive leaves that need runtime messages use `useTranslations()` under narrowly scoped `NextIntlClientProvider` boundaries. Do not ship full catalogs at the root or pass copy functions across the RSC boundary. Include metadata, error/not-found screens, and formatting in the migration. Remove shared content functions only after all their consumers have migrated; update the installation's `AGENTS.md` for its now-localized architecture.

Add an explicit message loader and catalog for every enabled copy locale. Reuse a language catalog only intentionally; for example, `en-US` and `en-CA` may share `en.json` while Shopify still receives distinct country contexts. Commerce context does not translate storefront labels automatically.

Validate every generated JSON file and keep keys and interpolation arguments aligned. Agree on temporary fallbacks before publishing a locale with incomplete copy; never silently label fallback English as a complete translation.

## 4. Configure routing

Create `lib/i18n/routing/index.ts` and use `enabledLocales` directly. There is no market mapping layer.

### Locale sub-path

```ts
import { defineRouting } from "next-intl/routing";

import { defaultLocale, enabledLocales, LOCALE_COOKIE_NAME } from "@/lib/i18n";

export const routing = defineRouting({
  defaultLocale,
  localeCookie: { name: LOCALE_COOKIE_NAME, sameSite: "lax" },
  localePrefix: "as-needed", // or "always"
  locales: enabledLocales,
});
```

Use full regional locale prefixes. One segment is enough.

### Invisible cookie routing

```ts
import { defineRouting } from "next-intl/routing";

import { defaultLocale, enabledLocales, LOCALE_COOKIE_NAME } from "@/lib/i18n";

export const routing = defineRouting({
  alternateLinks: false,
  defaultLocale,
  localeCookie: { name: LOCALE_COOKIE_NAME, sameSite: "lax" },
  localeDetection: true,
  localePrefix: "never",
  locales: enabledLocales,
});
```

`localePrefix: "never"` keeps the locale segment internal. On a request for `/products/shoe`, next-intl resolves the cookie (or first-visit language preference/default), then rewrites internally to a route such as `/fr-CA/products/shoe`. The browser URL stays `/products/shoe`.

Do not implement a second custom market rewrite on top of this. The internal `[locale]` segment is an implementation detail, not a public URL.

### Per-domain routing

```ts
export const routing = defineRouting({
  defaultLocale,
  domains: [
    { defaultLocale: "en-US", domain: "example.com", locales: ["en-US"] },
    { defaultLocale: "en-CA", domain: "example.ca", locales: ["en-CA", "fr-CA"] },
  ],
  localeCookie: { name: LOCALE_COOKIE_NAME, sameSite: "lax" },
  localePrefix: "as-needed",
  locales: enabledLocales,
});
```

The domain configuration is routing configuration, not a separate commerce market model. Shopify country and language still derive from the resolved regional locale.

Create `lib/i18n/navigation/client.ts` only for components that explicitly switch locales:

```ts
"use client";

import { createNavigation } from "next-intl/navigation";

import { routing } from "@/lib/i18n/routing";

export const { usePathname, useRouter } = createNavigation(routing);
```

Do not replace every `next/link` import in the Server Component tree. Pass explicit strategy-aware hrefs from the server so ordinary links preserve the selected locale without request-context reads in link components. Keep public paths clean in invisible-cookie mode.

## 5. Move page routes under `app/[locale]/`

Move the root layout and all localized pages under `app/[locale]/`. The locale layout must be the root layout; do not leave `app/layout.tsx` above it.

Move:

- `app/layout.tsx` to `app/[locale]/layout.tsx`
- Page routes such as home, products, collections, search, cart, and account into `app/[locale]/...`

Keep these unlocalized at `app/`:

- `api/`
- the assistant's public profile under `agent/`
- markdown route handlers under `md/`
- `robots.ts`
- `sitemap.xml/` and `sitemap/`
- `globals.css`, `global-error.tsx`, and static metadata files

Update typed route generics to include `[locale]`, fix the moved `globals.css` import, and add locale values to every `instant.unstable_samples[].params` object. Export `generateStaticParams` from the locale root layout using `enabledLocales.map((locale) => ({ locale }))`; Cache Components requires at least one root-param value. Preserve any existing generation logic and restart dev after route moves to regenerate root-param types.

Do not call `setRequestLocale` with Cache Components. Resolve locale through the root param so locale becomes an explicit route/cache input.

## 6. Resolve locale from the root param

Create `lib/params/server.ts` on the fresh baseline, or extend the existing resolver without overwriting unrelated helpers. This getter is for Server Components only:

```ts
import { notFound } from "next/navigation";
import { locale as rootLocale } from "next/root-params";

import { isEnabledLocale } from "@/lib/i18n";
import type { Locale } from "@/lib/i18n/types";

export async function getLocale(): Promise<Locale> {
  const value = await rootLocale();
  if (!value || !isEnabledLocale(value)) notFound();
  return value;
}
```

Create or update `lib/i18n/request/server.ts` to call `getLocale()` and load the matching messages. Do not resolve locale by reading cookies or request headers from a cached component. The proxy owns request negotiation; React receives the validated internal route param. Set `<html lang>` and UI formatting from that locale.

Do not call this root-param getter from Route Handlers or Server Actions: use route context or explicit request/action inputs and validate them at the boundary. Keep API, OAuth, markdown, and chat handling outside the Server Component request config.

## 7. Extend the proxy

Compose next-intl after the existing Shopify route dispatch. `handleShopifyRoutes()` returns `null` synchronously when Hydrogen does not own the pathname, so return its promise when present and run locale negotiation only after it declines the request:

```ts
const handleI18n = createMiddleware(routing);

// Keep the existing imports and add NextRequest as a runtime import.
export async function proxy(request: NextRequest): Promise<Response> {
  const requestContext = createCustomerRequestContext(request);
  const shopifyRoute = handleShopifyRoutes({
    // Preserve the template's handlers, session manager, and storefront client.
    request,
    requestContext,
  });
  if (shopifyRoute) return shopifyRoute;

  const i18nRequest = new NextRequest(request, {
    headers: requestContext.getForwardedRequestHeaders(),
  });
  const response = handleI18n(i18nRequest);
  requestContext.applyResponseHeaders(response.headers);
  return response;
}
```

Preserve the complete Shopify options from the current proxy rather than copying the abbreviated example literally. Keep its existing Shopify-owned API and protocol matchers, then add locale-prefixed Shopify endpoints now that locale routing is enabled:

```ts
export const config = {
  matcher: [
    // Keep every matcher already present in the template.
    "/:locale([a-zA-Z]{2}(?:-[a-zA-Z]{2})?)/agent/:action(handoff|buyer-claims).:format",
    "/:locale([a-zA-Z]{2}(?:-[a-zA-Z]{2})?)/cart.:format(js|json)",
    "/:locale([a-zA-Z]{2}(?:-[a-zA-Z]{2})?)/cart/:operation(add|update|change|clear).:format(js|json)",
  ],
};
```

Never widen the matcher to `/api/:path*`: application Route Handlers such as `/api/webhooks/shopify`, `/api/agent/session`, and `/api/custom` remain owned by Next unless explicitly reserved for Hydrogen. Keep Eve's `/eve/v1/` and `/_eve_internal/` routes outside locale and Shopify proxy handling. Add exact route families when a new Shopify integration requires proxy handling.

For invisible cookie routing, direct public locale-prefixed URLs should canonicalize back to the clean path. next-intl's `never` mode handles this; do not expose the internal rewrite destination in links, metadata, or redirects.

## 8. Propagate locale through commerce

Audit definitions and real callers. Under the opted-in regional-locale model, localized Storefront API operations receive validated locale/context, derive country and language at the commerce boundary, and use Shopify's validated `@inContext(country: $country, language: $language)`. Inspect existing locale and cache inputs before adding arguments. Preserve an explicitly separated country/language model rather than repurposing its copy locale.

This includes:

- products, collections, search, recommendations, and complementary products
- navigation menus and any megamenu added by `enable-shopify-menus`
- cart creation and cart reads that depend on buyer country
- sitemap and markdown catalog/product output
- agent tools and Storefront MCP calls

Cached functions must receive `locale` explicitly. Never read the locale cookie, `headers()`, or `cookies()` inside a `"use cache"` function. The locale argument naturally separates cache entries; do not add a parallel market cache key.

Keep locale defaults only at compatibility boundaries where the base single-locale template needs them. Once a route has resolved locale, pass it explicitly rather than silently defaulting deeper in the stack.

### Menus

Inspect `getMenu` and its callers; where needed, extend `getMenu({ handle })` to receive the validated commerce context, add localized Storefront context to the validated query, and update every caller. Without this, navigation remains pinned to the default market.

### Customer Account auth

Pass the active validated commerce context into the Shopify/Hydrogen request context instead of pinning it to `shopConfig.localization` or an older `defaultLocale`. Preserve locale across login, authorize, refresh, and logout return URLs. Validate any locale carried through OAuth state or URL params.

### Chat and agent API

Shop Agent is disabled by default. Preserve that setting; enabling Markets must not enable chat. When chat is enabled, inspect `agent/channels/eve.ts`, `agent/tools/`, `agent/lib/`, and the Shopify connections rather than adding an app-owned chat route or commerce HTTP bridge.

Eve's session routes live outside `[locale]`, and invisible URLs do not reveal locale in the referer. Carry the selected locale explicitly, validate it against the installed locale allowlist during cart setup and in Eve's channel, and pass the validated commerce context to the direct Shopify tools and connections. Client context can describe the page, but must not select an arbitrary cart or an unsupported locale. Do not infer locale from URL segments or fall back unconditionally to `defaultLocale`.

Agent tools, Shopify connections, product context, cart creation, and navigation outputs must use that validated locale. Keep Next.js request and cache APIs in the storefront; Eve uses the shared uncached Shopify catalog operations and Hydrogen handlers.

### Markdown negotiation

After the proxy rewrite, localized page routes have an internal `/:locale/...` path even in invisible mode. Update content-negotiation rewrites so the locale reaches unlocalized `app/md/...` handlers as a validated query/header value. Preserve `?variant=` and search parameters.

## 9. Switch locale and synchronize cart country

Switching language within the same country must not mutate buyer identity:

- `fr-CA` to `en-CA`: set locale; no cart country update
- `en-CA` to `en-US`: set locale and update buyer country to `US`

Validate both locales in a server action and read the cart ID from the shared cookie, never from client input. When the country changes, confirm the existing cart's buyer-country update before saving the locale cookie. On failure, keep the current locale and show the error. Refresh Hydrogen's cart after a country change so prices and checkout use the confirmed cart; do not invalidate public cache tags.

The selector remains a leaf Client Component.

- **Sub-path/per-domain:** call the action, then use next-intl's client router to replace the current pathname with `{ locale: nextLocale }`.
- **Invisible cookie:** call the action, then call `router.refresh()`. The pathname must not change.

Do not offer a separate currency selector unless the store has a Shopify-backed currency choice independent of country. Display currency from cart/product responses.

## 10. Strategy-specific SEO and sitemap behavior

### Locale sub-path or per-domain

Each locale has a distinct indexable URL:

- Canonical points to the current locale URL.
- Emit `hreflang` alternates for enabled locale URLs plus `x-default`.
- Emit one sitemap URL per locale with matching XHTML alternates.
- Build URLs from the selected routing strategy; do not assume every strategy uses `/${locale}`.

### Invisible cookie

All variants share one public URL:

- Canonical is the clean public path.
- Do not emit fake locale-specific `hreflang` URLs.
- Emit one sitemap URL per resource, not one per locale.
- Keep `alternateLinks: false` in next-intl routing.
- Treat localization as personalization. Shared links and crawlers without the user's cookie receive negotiated/default content.

Never put internal `/[locale]/...` rewrite targets into metadata or sitemap XML.

## 11. Verification

Run focused checks from `apps/template`:

```bash
pnpm lint
pnpm build
```

Then run the app and verify the selected strategy.

### All strategies

- Default-language copy matches the pre-migration installation, including custom text, interpolation, plurals, errors, and accessibility labels.
- Every enabled catalog has aligned keys/arguments and scoped client providers; fallback text is not mistaken for completed translation.
- Existing localized installations retain translations, custom routing, and their approved commerce model.
- Report whether the fresh-baseline migration and an existing-localized migration were actually exercised; static checks cannot prove either path's parity.
- Every enabled regional locale produces the expected Shopify country/language context.
- Product and cart currency codes come from Shopify responses.
- Public caches separate localized products, prices, and menus; carts remain uncached.
- `fr-CA` to `en-CA` does not update buyer country.
- `en-CA` to `en-US` updates buyer country and confirms the refreshed cart.
- Chat/agent operations receive the explicit validated locale.
- Markdown responses use the same locale as HTML responses.
- Variant and filter query parameters survive switching and rewrites.

### Locale sub-path

- Locale URLs serve directly with no `/market/locale` nesting.
- Default-prefix behavior matches `always` or `as-needed`.
- Canonical, hreflang, and sitemap URLs are locale-specific.

### Invisible cookie

```bash
curl -I http://localhost:3000/products/example
curl -I --cookie "NEXT_LOCALE=fr-CA" http://localhost:3000/products/example
```

- Both requests keep `/products/example` as the public URL.
- The cookie-selected response has `<html lang="fr-CA">` and Shopify country `CA` context.
- Locale switching changes content/cart context without changing the address bar.
- `/fr-CA/products/example` does not remain a public canonical URL.
- Metadata and sitemap contain only clean public paths and no locale alternates.

### Per-domain

- Each configured host resolves its allowed/default locales.
- Cross-domain locale switching uses the correct host.
- Canonical and hreflang URLs contain the production domains.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Shopify Menus
description: Replace the hardcoded nav and footer menus with Shopify-powered menus.
url: "https://shop-docs.labs.vercel.dev/docs/skills/enable-shopify-menus"
docs_index: /llms.txt
lastUpdated: 2026-09-13
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## How to use

```bash
/vercel-shop:enable-shopify-menus
```

<div className="pb-6" />

# Enable Shopify Menus

By default, the storefront's nav and footer render hardcoded fallback items: an inline `const items: MenuItem[] = [...]` in `Nav` (`components/nav/index.tsx`) and an empty `const items: MenuItem[] = []` in `Footer` (`components/footer/index.tsx`). The components themselves (`QuickLinks`, `MobileMenu`, `Footer`'s `FooterMenu`) already consume `MenuItem[]` in Shopify shape and render up to three levels of nesting. This skill swaps that inline default to a live Shopify menu fetched by handle, keeping the inline items as the fallback if the menu is missing or empty.

## Before you start

Ask the user two questions in order:

### 1. Which menus do you want to fetch from Shopify?

- **Nav menu** — replaces the hardcoded items used by the desktop quick links and mobile sheet.
- **Footer menu** — replaces the hardcoded footer columns.
- **Both**

### 2. What are the Shopify menu handles?

Ask for each selected menu. Defaults: `main-menu` for nav, `footer` for footer.

Wait for the user to answer before proceeding.

***

## Part A: Enable Shopify nav menu

Skip this section if the user did not select the nav menu.

Edit `components/nav/index.tsx`. Change the signature to `export async function Nav()` (preserving any existing props in a customized storefront), add the `getMenu` import, and swap the data source:

```tsx
import { getMenu } from "@/lib/menu/server";
```

Inside `Nav`, keep the existing inline default and swap the data source so the Shopify menu takes precedence:

```tsx
const defaultItems: MenuItem[] = [
  { id: "default-nav-shop", title: "Shop", url: "/collections/all", type: "HTTP", items: [] },
];
const menu = await getMenu({ handle: "NAV_HANDLE" });
const items = menu?.items ?? defaultItems;
```

Replace `"NAV_HANDLE"` with the handle the user provided. `QuickLinks` and `MobileMenu` already accept `MenuItem[]`, so no other component changes are needed. Keep the inline default as the fallback.

***

## Part B: Enable Shopify footer menu

Skip this section if the user did not select the footer menu.

Edit `components/footer/index.tsx`. Make the component async, add the `getMenu` import, and swap the data source:

```tsx
import { getMenu } from "@/lib/menu/server";
```

Change the signature to `async` and replace `const items: MenuItem[] = [];` with:

```tsx
const menu = await getMenu({ handle: "FOOTER_HANDLE" });
const items = menu?.items ?? [];
```

Replace `"FOOTER_HANDLE"` with the handle the user provided. `FooterMenu` already accepts `MenuItem[]`. The footer's default is empty (no columns), so falling back to `[]` simply hides the section when the Shopify menu is missing or empty.

Inspect the `Footer` callsite and `getMenu` operation instead of assuming a locale prop exists. The default uses explicit `shopConfig.localization` commerce settings and inline component copy; no `getLocale()` helper is needed. In an already localized or Markets-enabled store, preserve and pass its validated commerce context through menu operations and cache inputs. Keep existing cache directives and Suspense boundaries; do not change rendering policy just to enable a menu.

***

## Guardrails

- New fallback labels belong alongside the consuming domain component; preserve catalogs and translated fallbacks in an already localized installation. Shopify menu translations and storefront fallback copy are separate.
- `getMenu()` in `lib/menu/server.ts` already handles caching (`"use cache: remote"`, `cacheTag("menus")`) around the uncached `fetchMenu()` operation in `lib/shopify/operations/menu/server.ts`, which handles URL transformation. Do not duplicate that logic.
- Always preserve the inline fallback so a missing or empty Shopify menu doesn't leave the user with a blank nav or footer.
- External links (URLs starting with `http`) are handled by the existing `MenuLink` helpers in each component — no change needed.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Extending with Skills
description: Agent-ready skills for extending your Shopify storefront.
url: "https://shop-docs.labs.vercel.dev/docs/skills"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: overview
prerequisites:
  - /docs/getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Skills are agent-ready guides that transform your storefront. Run them with Claude Code or follow along manually.

Build or adapt Shopify storefronts with source-backed Vercel Shop patterns.

Audit drift and roll newer template changes into an existing storefront.

Apply Vercel Shop conventions after Shopify AI Toolkit validates an operation.

Add regional locale routing and Shopify-localized commerce context.

Replace hardcoded nav and footer with Shopify-powered menus, plus optional megamenu.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Shopify GraphQL Integration
description: "Integrate Shopify-validated GraphQL with Vercel Shop's operations, domain types, cache roles, and invalidation."
url: "https://shop-docs.labs.vercel.dev/docs/skills/shopify-graphql-reference"
docs_index: /llms.txt
lastUpdated: 2026-09-14
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## How to use

Use Shopify AI Toolkit's API-specific skill first, then run:

```bash
/vercel-shop:shopify-graphql-reference
```

<div className="pb-6" />

# Integrate Shopify GraphQL

Treat Shopify AI Toolkit as authoritative for Shopify documentation, schemas, fields, arguments, enum values, API versions, operation design, and validation. This skill owns only the Vercel Shop integration layer.

Read `references/REFERENCE.md` before editing.

## Delegate Shopify facts first

1. Choose the Shopify API:
   - Use Shopify AI Toolkit's Storefront GraphQL skill for catalog, search, menus, cart, and public metaobject reads.
   - Use its Customer skill for profiles, orders, addresses, and other authenticated customer data.
   - Use its custom-data skill first for metafields or metaobjects.
2. Search current Shopify documentation and validate the complete operation with that skill.
3. Do not copy schema snapshots, field catalogs, or generated API reference into this plugin.
4. If Shopify AI Toolkit is unavailable, use official Shopify documentation and validation tooling; do not guess.

## Apply the Vercel Shop conventions

1. Inspect the consuming route before choosing cache behavior. Classify the read as static-shell content, request-time shared content, or private/request-scoped data.
2. Add the operation to `lib/shopify/operations/<resource>/server.ts` in the closest resource domain as an uncached `fetch*` function with no `next/cache` import. Put the cache directive, `cacheLife`, and `cacheTag` calls in a `get*` wrapper in the consuming domain's `lib/<domain>/server.ts` (for example `lib/product/server.ts`, `lib/collections/server.ts`, `lib/policies/server.ts`). Keep pure transforms in `lib/shopify/transforms/<resource>/index.ts`, named shared contracts in the owning domain's `types.ts`, and reusable fragment documents in their resource domain. Import implementations and types directly; do not add barrels or forwarding exports.
3. Wrap documents in Hydrogen's `gql()` with a leading `#graphql` comment, keep them static, and use variables for dynamic values. Inference supplies types; Shopify AI Toolkit validates fields and arguments.
4. Reuse the smallest existing fragment that fits by passing it in the `gql(source, [FRAGMENT])` list. Extend a shared fragment only when multiple operations need the same selection.
5. Pass `locale` to `storefront.request` when Shopify localizes the result; never add `country` or `language` to `variables`.
6. Derive raw Shopify types from fragment documents with `ResultOf<typeof FRAGMENT>` under `lib/shopify/**`; transform catalog/account data into domain types before presentation. Cart types intentionally derive from Hydrogen handlers through `lib/cart/types.ts`.
7. Preserve cache tags and the existing webhook invalidation hierarchy. Do not cache mutations.
8. Never place carts in the Next.js data cache; cart reads are memoized per request via `getCart`, so cart mutations need no cache invalidation step.

## Revalidate both boundaries

1. Validate the final GraphQL document again with Shopify AI Toolkit after fragment and variable changes.
2. Run local lint, typecheck, focused tests, and affected commerce flows; run a build when the changed surface requires it. Exercise the operation against the store once; typecheck does not catch invalid fields.
3. Report separately:
   - Shopify validation performed;
   - Vercel Shop cache and route role;
   - domain transform and invalidation changes.

Do not present this skill as a Shopify schema source. Shopify AI Toolkit determines what Shopify supports; this skill determines how validated operations fit Vercel Shop.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Update Vercel Shop
description: Audit drift, plan a change-level upgrade, and apply newer template updates to an existing storefront.
url: "https://shop-docs.labs.vercel.dev/docs/skills/update-shop"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## How to use

```bash
/vercel-shop:update-shop
```

The skill audits how far a storefront has drifted from the current template, plans an upgrade from the template rollout log entry by entry, and — when asked to apply — carries selected changes into the project with per-entry validation, recording decisions so future runs stay incremental.

<div className="pb-6" />

# Update Vercel Shop

Bring an existing Vercel Shop project up to date with the template by reasoning about individual rollout entries, never by diffing against a template version. Downstream storefronts often adopt only part of the template, so every decision must be validated against the current codebase.

## Pick a mode

Infer the mode from the user's request:

- **Audit** — report how far the project has drifted. Read-only; stop after the audit phase.
- **Plan** — produce a change-level upgrade plan. Read-only; stop after the plan phase.
- **Apply** — plan, confirm the selection with the user, then apply and validate. Use this when the user asks to "update" or "upgrade" the shop.

## Read these inputs

1. `.vercel-shop/bootstrap.json` in the project root — original `templateVersion` and `scaffoldedAt`
2. `.vercel-shop/rollout-state.json` in the project root — decisions recorded by earlier runs of this skill (may not exist)
3. The current rollout log and template version, fetched from upstream (see below)
4. `AGENTS.md` and `.claude/settings.json` in the project if present
5. The current project structure and any files named by matching rollout entries

If `.vercel-shop/bootstrap.json` is missing, say the project predates plugin bootstrap metadata and continue with a best-effort heuristic audit.

### Fetch the current rollout log

The rollout log is maintained upstream in `github.com/vercel/shop`. The plugin bundles a copy of `template-rollout-log/` and `template-version.json`, but it is only as fresh as the installed plugin, so prefer the upstream copy:

```bash
dir=$(mktemp -d)
curl -fsSL https://codeload.github.com/vercel/shop/tar.gz/refs/heads/main |
  tar -xz --strip-components=3 -C "$dir" \
    shop-main/packages/plugin/template-rollout-log \
    shop-main/packages/plugin/template-version.json
```

Read every markdown entry in the extracted `template-rollout-log/` except `README.md`. If the fetch fails (offline or restricted network), fall back to the copies bundled with this plugin and say in the report that the log may be stale until the plugin is updated.

## Phase 1 — audit

Compare the scaffold metadata against the current recommended template version and check for structural drift:

- missing `.vercel-shop/bootstrap.json`
- missing `AGENTS.md`
- missing project-scoped plugin config in `.claude/settings.json`
- legacy local skill files such as `.agents/skills/` or a legacy `.claude/skills` symlink
- obvious divergence from the expected Vercel Shop structure such as missing `lib/shopify/` or `components/`

Report the original scaffold version, the scaffold timestamp, the current recommended template version, and a short note on overall drift. If the scaffold version matches the current version, say so explicitly. In audit mode, stop here.

## Phase 2 — plan

Build the candidate list from the rollout log:

1. If bootstrap metadata includes `scaffoldedAt`, treat entries with a newer `introducedOn` as the primary candidates. Versions are only hints.
2. Add older entries that still look applicable from the current project state.
3. Drop entries that `.vercel-shop/rollout-state.json` already records as adopted, skipped, or not applicable — unless the user asks to revisit them.

For each remaining entry, decide one of:

- **Adopt now** — clearly applicable and mechanical enough to apply confidently
- **Review manually** — applicable but touches heavily customized code
- **Already present** — the project has the change (adopted independently or scaffolded with it)
- **Not applicable** — the entry's `appliesTo` or preconditions don't match this project

Validate each decision against the current codebase. Do not assume a change is missing just because the scaffold is old, and do not invent upgrade work when no entries apply — say so explicitly. Call out uncertainty when the project has heavily diverged from template conventions.

The simplified single-deployment template's inline copy and reusable content functions and explicit `shopConfig.localization` are defaults for new storefronts, not a mandate to remove localization from existing stores. Preserve next-intl, catalogs, translations, scoped providers, locale routing, custom market selection, and intentional operation/cache locale inputs. Do not classify these customizations as obsolete drift or replace them with template English. Any requested simplification must separately assess copy, formatting, commerce country/language, public URLs, and migration parity; currency remains Shopify-owned. Rollout entries are currently paused, so their absence is not evidence that every newer template behavior is already installed.

Present the plan grouped by decision. In plan mode, stop here and do not edit files.

## Phase 3 — apply

Confirm with the user which entries to apply before editing anything. Then, for each selected entry, one at a time:

1. Re-read the entry's Summary, `paths`, and Apply when / Safe to skip when sections.
2. Apply the change in the project's own idiom — re-implement the behavior described by the entry rather than copying template files over customized code. Use the entry's `relatedSkills` when listed.
3. Run the entry's Validation steps before moving to the next entry.
4. Keep each entry's edits an isolated, reviewable unit. If the project uses git and the user wants commits, suggest one commit per `changeKey`.

If an entry's validation fails, stop, report the failure, and ask whether to fix forward, skip the entry, or revert its edits before continuing.

## Phase 4 — record and report

Record every decision in `.vercel-shop/rollout-state.json` so future runs don't re-litigate settled entries. Keep decisions keyed by `changeKey`:

```json
{
  "decisions": {
    "storefront-typed-client": {
      "decision": "adopted",
      "decidedOn": "2026-07-06"
    },
    "pdp-metafields-specs": {
      "decision": "skipped",
      "decidedOn": "2026-07-06",
      "note": "custom PDP replaced the specs section"
    }
  }
}
```

Use `adopted`, `skipped`, `not-applicable`, or `already-present` as decision values. Do not modify `.vercel-shop/bootstrap.json` — `scaffoldedAt` must keep describing the original scaffold.

Finish with a concise report: what was applied, what was skipped and why, validation results, and any entries deferred for manual review.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Content Pages
description: Publish Shopify-managed Pages, policies, blogs, and articles on your storefront.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/pages/content"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Use Shopify's built-in content tools to publish editorial and policy pages without adding another content management system.

- Shopify Pages appear at `/pages/[handle]`.
- Store policies appear at `/policies/[handle]`.
- Blogs appear at `/blogs/[blogHandle]`, with articles at `/blogs/[blogHandle]/[articleHandle]`.

Unknown handles return a not-found page. There is no combined `/blogs` page, so navigation should link to a specific blog such as `/blogs/news`.

## Publish content in Shopify

Manage Pages and blog posts in Shopify Admin, and manage store policies in Shopify settings. The default storefront requests this content using the deployment's configured Shopify country and language. These settings are separate from the storefront's own [copy](/docs/anatomy/i18n).

Shopify Pages include a title, rich-text body, and SEO fields. Blogs list recent articles with dates, excerpts, and optional images. Articles include the title, author, date, image, and body. Policy URLs exist only when the corresponding policy has content, and the [footer](/docs/anatomy/footer) links to every configured policy.

Search and social metadata use Shopify's SEO fields when available. Sitemaps include Pages, blogs, articles, and configured policies. See [Sitemap](/docs/anatomy/sitemap).

## Plan for delayed updates

The template's webhook handler does not refresh Pages, policies, blogs, or articles. Edits can remain cached until the content is revalidated. If a change must appear immediately, arrange a cache refresh as part of publishing. See [Webhooks](/docs/anatomy/webhooks) for the content types the handler supports.

Verify every published or edited page in the storefront, including its language, metadata, navigation link, and sitemap entry.

## Know when Shopify content is enough

Shopify Pages and blogs work well for standard rich text, policy content, and news. Consider a dedicated content management system only when editors need structured layouts, custom fields, or preview workflows that Shopify does not provide.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Home
description: The storefront landing page with an introductory message and a product grid.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/pages/home"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The home page introduces the store and gives shoppers a direct path into the catalog. By default, it shows a fixed headline followed by up to eight products.

## Choose the opening message

The headline and description are fixed storefront copy. Keep them short and use them to explain what the store sells or why a shopper should continue.

The message appears before catalog data finishes loading, so shoppers can understand the page even when product loading is slow.

## Choose the products to feature

The default grid shows the first eight products from the same relevance-ranked catalog used by `/collections/all`. It is not a hand-picked or best-selling list. The section links to `/collections/all` and uses the standard [product card](/docs/anatomy/product-card), with two columns on mobile and four on desktop.

Use a specific Shopify collection when merchants need direct control over featured products. Additional sections can highlight new arrivals, best sellers, or promotions, but keep the page focused on the next action you want shoppers to take.

## What’s next

Common additions include a hero image or video, merchant-curated collections, new-arrival or best-selling sections, and promotional content between product groups. Keep each section focused on a clear next action for shoppers.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Pages
description: The core page types that make up your storefront.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/pages"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: overview
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

These guides explain the shopper outcome, default behavior, important choices, and limits for each core storefront page.

Introduce the store and direct shoppers into the catalog.

Help shoppers choose a variant and complete a purchase.

Help shoppers browse, filter, sort, and search the catalog.

Publish localized Pages, policies, blogs, and articles from Shopify.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Product Detail Page (PDP)
description: A single product page with variants, media, purchase controls, and product recommendations.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/pages/pdp"
docs_index: /llms.txt
lastUpdated: 2026-09-08
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Product Detail Page (PDP) gives shoppers the information and controls needed to choose a product variant and purchase it.

## Help shoppers choose the right variant

Product pages show the title, price, media, description, and available options. Option choices update the page URL, so shoppers can share a selected color, size, or other variant and use browser back and forward controls.

Subscription options can appear while a variant choice is being resolved, but purchase controls remain disabled until the selected variant is confirmed. Out-of-stock variants remain disabled.

When colors have distinct media, the gallery leads with the selected color's image. Desktop shoppers get a grid and lightbox; mobile shoppers get a swipeable gallery.

Product updates are cached and refreshed by Shopify webhooks. See [Webhooks](/docs/anatomy/webhooks).

## Included purchase features

- **Variant picker** — shows available colors, sizes, and other options.
- **Add to cart** — adds the selected variant without waiting for the full cart response.
- **Quantity** — accepts values from 1 through 99.
- **Subscriptions** — shows subscription options configured in Shopify.
- **Gift cards** — collects recipient details and keeps gifts for different recipients separate in the cart.
- **Buy with Shop** — starts Shopify checkout.
- **Bundles** — shows included items and other bundles containing the product.
- **Complementary products** — presents merchant-curated add-ons.
- **Related products** — presents algorithmic recommendations.
- **Structured data** — describes the product and breadcrumbs to search engines.

Quantity, Buy with Shop, bundles, complementary products, and related products are enabled by default. Disable sections that do not fit the catalog or purchase journey.

## What’s next

Common additions include reviews, detailed material or care information, size guides, virtual try-on, and restock waitlists. These features require another data source or service. Add them only when they help shoppers make a purchase decision that the default product data cannot support.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Product Listing Page (PLP)
description: Collection and search results pages with filtering, sorting, and infinite scroll.
url: "https://shop-docs.labs.vercel.dev/docs/anatomy/pages/plp"
docs_index: /llms.txt
lastUpdated: 2026-09-07
type: guide
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Product Listing Page (PLP) helps shoppers narrow a catalog. Collection pages use `/collections/[handle]`; query results use `/search`. Both provide the same product grid, filters, sorting, and continuous loading.

## Help shoppers refine results

Collection pages show the collection title, description, and image. Search pages focus on the shopper's query. Product results remain live rather than cached so pagination stays consistent while the catalog or ranking changes.

Filters and sorting are reflected in the URL, so shoppers can share a refined view, use browser history, and return to the same selection. Collection and search pages use the same browsing controls, with sort choices suited to each page.

## Included browsing tools

- **Faceted filters** — size, color, vendor, product type, tags, and price.
- **Swatches** — color or image choices when Shopify provides swatch presentation data.
- **Sorting** — Shopify's supported collection sorts. Search omits name and date because Shopify search does not support them.
- **Infinite scroll** — loads up to 40 products at a time as the shopper moves down the page and guards against duplicates when rankings change. To change the batch size for collection and search listings, set `PRODUCTS_PER_PAGE` in `lib/collections/index.ts`.
- **Toolbar** — collection and search pages share filter access, sorting, and a matching loading layout. Search also shows a result count.
- **Predictive search** — shows suggestions and products from the navigation search dialog.
- **Structured data** — describes collection pages to search engines.

These features are enabled for every storefront; there are no configuration toggles for them.

## What’s next

Common additions include merchant-curated collection ordering, promotional tiles within the grid, saved filters, and catalog-specific filter groups. Keep filter and sort choices in the URL so refined views remain shareable.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)