# 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: "What Vercel Shop is, what you get out of the box, and how it fits Shopify's new Hydrogen."
url: "https://vercel.shop/docs"
docs_index: /llms.txt
lastUpdated: 2026-10-18
type: overview
---

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

Vercel Shop is an enterprise-ready headless Shopify storefront built with Next.js. Shopify runs your catalog, checkout, and payments. Vercel Shop gives you the storefront in front of them: product pages, category pages, search, and a cart, all backed by the Shopify Storefront API and ready to customize. It's the Next.js storefront template for Shopify's new Hydrogen.

<a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fshop&project-name=shop&repository-name=shop&demo-title=Vercel+Shop&demo-url=https%3A%2F%2Ftemplate.vercel.shop&stores=%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 button creates a new Shopify store and a Vercel project. After deploying, follow
> [Deploy with a new Shopify store](/docs/getting-started#deploy-with-a-new-shopify-store) to claim
> the store and add products. To use a store you already have, see [Use an existing Shopify
> store](/docs/getting-started#use-an-existing-shopify-store).

Vercel Shop is open source. Extend it directly with your team or with coding agents. Vercel and Shopify maintain skills that package common workflows, giving agents a repeatable, best-practice-driven way to implement new features and functionality on your site. It deploys to Vercel and uses Vercel's agentic infrastructure and best practices to power the fastest headless storefront experience on Shopify.

## What you get out of the box

Going headless usually means rebuilding the same foundation before you can ship. Vercel Shop includes that foundation out of the box:

- Product, category, and search pages backed by the Shopify Storefront API.
- Blog, page, and policy content from Shopify.
- A server-backed cart and checkout handoff to Shopify.
- Navigation and footer, with optional Shopify-managed menus.
- SEO, sitemaps, and structured data for product and collection pages.
- Machine-readable content for LLMs. See [AEO and GEO](/docs/anatomy/aeo-geo).
- Webhook-driven revalidation, so edits in Shopify Admin refresh the storefront.
- Optional customer accounts and an AI shopping assistant powered by Vercel's agentic framework [eve](https://eve.dev).

## Enterprise-ready

- **Scale.** Static generation, streaming, and caching on Vercel handles traffic spikes and large catalogs.
- **Security.** Shopify owns checkout and payment data, so card details never touch your storefront. Customer sessions are encrypted, and API secrets stay server-side. A managed firewall and DDoS mitigation protect the site on Vercel, and you can optionally turn on [BotID](https://vercel.com/docs/botid) for invisible bot detection in your [shop configuration](/docs/reference/shop-config).
- **Composability.** The storefront is a standard Next.js app that talks to Shopify's APIs. Bring your own CMS, search, or analytics, and add custom routes, pages, and integrations alongside the commerce foundation. You compose the stack around your needs in your own codebase, with no proprietary framework to work around.

## Built for agentic development

Every project includes the Shop skills and `AGENTS.md` conventions, so coding agents extend the storefront with best practices in mind. Skills carry task-specific instructions and checks, and Shopify AI Toolkit adds current Shopify API documentation and validation. See [Extending with Agents](/docs/getting-started/extending-with-agents).

## Start with the template

Create your project from the template with create-next-app. It ships the commerce foundation and the Shop skills, with room to build on top. Head to [Getting Started](/docs/getting-started) to create your project.

See the template running at [template.vercel.shop](https://template.vercel.shop).

## Part of the new Hydrogen

Vercel is a design partner on the rebuild of [Shopify Hydrogen](https://vercel.com/blog/vercel-and-shopify-are-rebuilding-hydrogen), Shopify's storefront toolkit, redesigned with the Next.js team to be framework-agnostic and runtime-agnostic. Vercel Shop is the Next.js template for Shopify and Hydrogen going forward, and adopts the new Hydrogen as part of that work.

## Why teams choose Vercel Shop

- Open source and maintained by Vercel. Track upstream improvements and roll them into your storefront with the [update-shop skill](/docs/skills/update-shop).
- Vercel builds and maintains Next.js, so the framework and the template move forward together.
- Shopify features arrive as maintained skills, so a coding agent adds them the same way every time.
- The storefront is yours. Extend it in your own codebase instead of waiting on a vendor roadmap.

#### [Getting Started](/docs/getting-started)

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

#### [Extending with Agents](/docs/getting-started/extending-with-agents)

Extend and customize the storefront with Claude Code or Cursor.

---

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://vercel.shop/docs/anatomy/aeo-geo"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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` or appends `.md` to the URL, such as `/products/[handle].md`. The home page's Markdown URL is `/index.md`. Normal browser requests continue to receive HTML from the same URLs. No setup is required.

Markdown responses send `X-Robots-Tag: noindex` and a canonical link to the HTML page, so search engines index the shopper page rather than its Markdown copy.

The storefront also publishes `/llms.txt`, a concise guide for AI agents. It summarizes the store with `site.description` from the [shop config](/docs/reference/shop-config) and explains how to buy for a shopper through Shopify's UCP endpoints. It links to store policies, up to 50 collections, the sitemap, and crawl guidance rather than duplicating the full catalog. The [sitemap](/docs/anatomy/sitemap) remains the complete URL inventory.

Absolute URLs in `/llms.txt`, Markdown, the sitemap, and crawl guidance use `site.url`. If you deploy outside Vercel, set it to your public storefront origin.

Markdown explains what a product or collection is; it does not list or sell inventory. Catalog search, 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`. Markdown responses point agents there instead of repeating product listings or 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               | Describes products and collections and points AI clients to UCP                                                |
| Schema.org data                  | Describes products, collections, breadcrumbs, the organization, and site search                                |
| [Sitemap](/docs/anatomy/sitemap) | Lists storefront content for crawlers                                                                          |
| Crawl guidance                   | Points crawlers to the sitemap and blocks cart, account, login, search, and sorted or filtered collection URLs |
| `/llms.txt`                      | Gives AI agents a concise storefront index                                                                     |
| Social metadata                  | Supplies route-specific titles, descriptions, images, prices, and availability                                 |

Markdown responses start with frontmatter listing the page title, description, and canonical URL, plus when Shopify last updated the product or collection. They 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** — title, description, image, and links to the shopper page and UCP catalog search. Collection contents change often, so products are not listed.
- **Search** — the query and links to the shopper results page and UCP catalog search.

## 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://vercel.shop/docs/anatomy/agent"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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 the `openai/gpt-6-luna-fast` model 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.

In production, product search identifies the assistant to Shopify through `/agent/ucp-profile.json` on your storefront, so keep that URL publicly reachable.

### Local production

For a local production build, run `pnpm build:agent && pnpm build`, then run `pnpm start`. 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 or options such as size and color, remove items, apply or remove discount codes they provide, 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. Conversations have no cost cap, so control cost where traffic enters. 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://vercel.shop/docs/anatomy/authentication"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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"
NEXT_PUBLIC_SHOPIFY_SHOP_ID="123456789"
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. Use the numeric shop ID for `NEXT_PUBLIC_SHOPIFY_SHOP_ID`, not the Headless storefront ID. Every build requires the shop ID; enabling authentication also requires the session secret, client ID, and client secret.

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

> **Local HTTPS with Portless:**
>
> For local HTTPS, install [Portless](https://portless.sh) with `pnpm add -g portless`, then run
> `pnpm dev:portless`. Use it to test secure cookies; sign-in still needs the 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. Set `NEXT_PUBLIC_SHOPIFY_SHOP_ID` to the shop ID shown in Shopify's default customer accounts URL.
10. 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.

## 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://vercel.shop/docs/anatomy/cart"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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://vercel.shop/docs/anatomy/footer"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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://vercel.shop/docs/anatomy/i18n"
docs_index: /llms.txt
lastUpdated: 2026-10-14
---

> 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://vercel.shop/docs/anatomy/navigation"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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 the [`enable-shopify-menus` skill](/docs/skills/enable-shopify-menus) when merchants need to manage them in Shopify.

## Help shoppers move through the store

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 recently viewed products before the shopper types, then suggestions and matching products as the shopper types. It 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 to all products. 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.

Shopify does not notify the storefront when a menu changes, so menus stay cached until they are refreshed. The skill adds a protected `POST /api/revalidate/menus` endpoint; call it with `MENUS_REVALIDATE_SECRET` after editing menus in Shopify admin.

A floating action bar appears only when it contains an action, such as the 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://vercel.shop/docs/anatomy/product-card"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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.

## Decide whether cards should support purchasing

On large screens, cards on the home, collection, and search pages offer quick shop for in-stock products: shoppers choose options and add the product to the cart without leaving the grid. Gift cards, smaller screens, related products, and agent results lead to the product page.

Quick shop suits products that shoppers can choose from options and price alone. If shoppers need size guides, detailed media, or other product page content to choose safely, remove quick shop so cards lead straight to the product page.

## What’s next

Common additions include alternate image proportions, a second image on hover, review summaries, and variant swatches. Keep cards easy to scan.

---

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://vercel.shop/docs/anatomy/proxy"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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 serves cart updates at `/api/cart` and search suggestions at `/api/predictive-search`. It also hands these Shopify paths to Shopify before any storefront page renders:

- `/api/mcp` and `/api/ucp/mcp`, Shopify's agent endpoints
- `/.well-known/ucp`, Shopify's business profile
- `/api/<version>/graphql.json`, Storefront API requests
- `/checkout`, which redirects to Shopify checkout for the current cart
- Cart permalinks such as `/cart/<variant-id>:<quantity>` and buy permalinks under `/buy/`
- Legacy cart URLs such as `/cart.js` and `/cart/add.js`

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. Requests under `/eve/` skip the proxy, so proxy rules never apply to Shop Agent 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 and other requests that Shopify does not own continue to Next.js unchanged.

## 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. Do not redirect `/api/agent/session` or `/agent/ucp-profile.json`; Shop Agent uses them to prepare the shopper's cart and to identify itself to Shopify.

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.

## Shopify redirects on missing pages

Set `redirects.shopifyNotFound.isEnabled` to `true` in `lib/config/index.ts` to enable Hydrogen's built-in redirects for missing pages. It is disabled by default.

Saved redirects require Shopify lookups, and the response may retain a `404` status. Use Next.js redirects when a permanent HTTP redirect is required.

## 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://vercel.shop/docs/anatomy/sitemap"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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`.

Sitemap URLs use `site.url` from the [shop config](/docs/reference/shop-config). If you deploy outside Vercel, set it to your public storefront origin.

## 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://vercel.shop/docs/anatomy/webhooks"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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`. Edits appear when cached content refreshes or the site is redeployed; cache eviction can make them appear sooner.

## 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`
- `metaobjects/create`, `metaobjects/update`, and `metaobjects/delete`

You can register only the topics your store uses. Changes without a registered topic have no event-driven refresh.

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 shown for the default variant can lag until the product cache refreshes; selecting a variant reads its current availability from Shopify.

This handler does not refresh Online Store pages, blogs, articles, or store policies. Those edits appear when cached content refreshes 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://vercel.shop/docs/anatomy/webmcp"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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 loads Shopify's browser scripts, including consent, but 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 `shopify.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 fx, Claude Code, Cursor, and Codex to personalize and extend your storefront.
url: "https://vercel.shop/docs/getting-started/extending-with-agents"
docs_index: /llms.txt
lastUpdated: 2026-10-14
type: guide
prerequisites:
  - /docs/getting-started
---

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

Coding agents can build and extend your storefront. Every project includes the Shop skills and `AGENTS.md` conventions, so an agent works with the same patterns and verification steps your team uses.

## Set up the agent

New projects include the Shop skills in `.agents/skills/`. Claude Code reads them through the `.claude/skills` link in the same project. To add the Shop skills to an existing storefront, run this from its root:

```bash
pnpm dlx skills add vercel/shop --skill '*' --yes
```

Also install these for your agent:

- **Vercel plugin** for Vercel platform guidance and skills:

  ```bash
  npx plugins add vercel/vercel-plugin
  ```

- **Shopify AI Toolkit** for current Shopify documentation, API schemas, and operation validation. The Shop skills rely on it before adding or changing Shopify GraphQL. Follow [Shopify's install instructions](https://github.com/Shopify/shopify-ai-toolkit#install) for your agent.

Use an agent that reads `AGENTS.md` and supports project skills, such as [fx](https://fx.sh), [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. Ask your agent to use a skill by name, such as `enable-shopify-markets`, or run it as a slash command in Claude Code:

```text
/enable-shopify-markets
```

You can also describe the outcome and let the agent pick the matching skill. Use the [update-shop skill](/docs/skills/update-shop) when bringing an existing storefront up to date.

| Skill                                                                 | What it does                                                                             | Shopify docs                                                                       |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| [Initialize Vercel Shop](/docs/skills/init-vercel-shop)               | Create a storefront from the template and connect a Shopify store.                       |                                                                                    |
| [Build with Vercel Shop](/docs/skills/build-shop)                     | Build or adapt storefront routes, commerce data, and rendering with template patterns.   |                                                                                    |
| [Update Vercel Shop](/docs/skills/update-shop)                        | Compare a storefront with the current template and apply the updates you choose.         |                                                                                    |
| [Shopify GraphQL integration](/docs/skills/shopify-graphql-reference) | Add Shopify-validated Storefront or Customer Account GraphQL using template conventions. | [Storefront API](https://shopify.dev/docs/api/storefront)                          |
| [Enable analytics](/docs/skills/enable-analytics)                     | Add Vercel Web Analytics, Speed Insights, and Google Tag Manager.                        |                                                                                    |
| [Enable i18n](/docs/skills/enable-i18n)                               | Add locale-prefixed URLs and per-locale copy with next-intl, without Shopify Markets.    |                                                                                    |
| [Enable Shopify Markets](/docs/skills/enable-shopify-markets)         | Add multi-region locales, currency, and localized commerce context.                      | [Markets](https://help.shopify.com/en/manual/markets)                              |
| [Enable Shopify menus](/docs/skills/enable-shopify-menus)             | Replace the hardcoded nav and footer with Shopify-managed menus.                         | [Menus and links](https://help.shopify.com/en/manual/online-store/menus-and-links) |

## 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 Environment
description: Set up Shopify, create a project, and run or deploy Vercel Shop.
url: "https://vercel.shop/docs/getting-started"
docs_index: /llms.txt
lastUpdated: 2026-10-14
type: guide
---

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

Start with a new Shopify store using the Deploy button, or connect an existing store and run Vercel Shop locally.

## Deploy with a new Shopify store

Vercel creates the repository, creates the store through the [Vercel Marketplace Shopify integration](https://vercel.com/marketplace/shopify), and sets the required environment variables for you.

<a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fshop&project-name=shop&repository-name=shop&demo-title=Vercel+Shop&demo-url=https%3A%2F%2Ftemplate.vercel.shop&stores=%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>

1. Click **Deploy**, create the Shopify store in the deploy screen, and finish the deployment.
2. In the Vercel dashboard, open the project and click **Connections** in the sidebar.
3. Open **Shopify** and claim the store you created.
4. In Shopify admin, create or import your products.
5. Publish the products to the **Vercel Storefronts** sales channel.
6. Redeploy the project from the Vercel dashboard.

The first deployment runs before the store has products, so the homepage shows no products until you redeploy.

To show later product, collection, and content changes without redeploying, set up [webhooks](/docs/anatomy/webhooks).

## Use an existing Shopify store

### Prerequisites

- Node.js 24
- [pnpm](https://pnpm.io) 12 for installing dependencies and running scripts
- A Shopify store

### 1. Configure Shopify store

Install Shopify's [Headless sales channel](https://apps.shopify.com/headless). In your Shopify admin, under **Sales channels**, click **Headless**, create a storefront, and copy its **public access token**. Grant the required [Storefront API permissions](/docs/reference/storefront-api-permissions). For the full walkthrough, see [Setup Shopify](/docs/getting-started/shopify-setup).

> **Stores from the Vercel Marketplace:**
>
> If you created your store through the [Vercel Marketplace Shopify
> integration](https://vercel.com/marketplace/shopify), you have the **Vercel Storefronts** channel
> instead of **Headless**. Manage the storefront and its access token there.

### 2. Create your project

```bash
pnpm create next-app@latest my-store --example https://github.com/vercel/shop
cd my-store
```

The project includes the Shop skills for your coding agent in `.agents/skills/`. See [Extending with Agents](/docs/getting-started/extending-with-agents).

### 3. Add your credentials

Copy the example environment file:

```bash
cp .env.example .env.local
```

Set the three required values in `.env.local`:

```bash
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN="your-store.myshopify.com"
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN="your-storefront-access-token-here"
NEXT_PUBLIC_SHOPIFY_SHOP_ID="123456789"
```

Find `NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN` under [Settings → Domains](https://help.shopify.com/en/manual/domains) in Shopify admin. Use the public access token from your [Headless channel storefront](https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/manage-headless-channels) for `NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN`. The shop ID is the number in your store's customer accounts URL, `https://shopify.com/123456789/account`.

> **Customer login needs more setup:**
>
> Customer accounts require additional credentials and configuration. See
> [Authentication](/docs/anatomy/authentication) before enabling sign-in.

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.

If no products appear, you may still need to publish products to the Headless channel. See [publishing products to your storefront](/docs/getting-started/shopify-setup#publish-products-to-your-storefront).

> **Local HTTPS with Portless:**
>
> For local HTTPS, install [Portless](https://portless.sh) with `pnpm add -g portless`, then run
> `pnpm dev:portless`. Use it to test secure cookies. Customer sign-in needs a public HTTPS tunnel;
> see [Authentication](/docs/anatomy/authentication).

### 5. Deploy to Vercel

When you're ready to deploy, commit and push your changes to your Git repository. Then import the project at [vercel.com/new](https://vercel.com/new), or use the CLI:

```bash
pnpm add -g vercel
vercel
```

Add the required [environment variables](/docs/reference/env-vars) to the Vercel project before your first production deploy. Stores created through the [Vercel Marketplace Shopify integration](https://vercel.com/marketplace/shopify) have their Shopify credentials configured for you.

To refresh the storefront when you edit products, collections, or content in Shopify, set `SHOPIFY_WEBHOOK_SECRET` and configure [webhooks](/docs/anatomy/webhooks).

## Related links

#### [Setup Shopify](/docs/getting-started/shopify-setup)

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

#### [Extending with Agents](/docs/getting-started/extending-with-agents)

Customize the storefront with coding agents and project skills.

#### [Environment Variables](/docs/reference/env-vars)

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: Setup Shopify
description: Install and configure the Shopify Headless channel and the apps your storefront needs.
url: "https://vercel.shop/docs/getting-started/shopify-setup"
docs_index: /llms.txt
lastUpdated: 2026-10-14
type: guide
prerequisites:
  - /docs/getting-started
---

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

All stores require the Shopify [Headless sales channel](https://apps.shopify.com/headless). Install and set it up first. Install the other Shopify apps only when your store needs their storefront features.

> **Stores from the Vercel Marketplace:**
>
> If you created your store through the [Vercel Marketplace Shopify
> integration](https://vercel.com/marketplace/shopify), you have the **Vercel Storefronts** channel
> instead of **Headless**. Manage the storefront and its access token there.

## Publish products to your Storefront

Install Shopify's [Headless sales channel](https://apps.shopify.com/headless). In your Shopify admin, open the **Headless** app under **Sales channels**, then click **Add storefront** to create a storefront.

Grant the storefront's Storefront API token the required permissions:

| Scope                                   | Enables                                                          |
| --------------------------------------- | ---------------------------------------------------------------- |
| `unauthenticated_read_product_listings` | Products, collections, search, and recommendations               |
| `unauthenticated_read_selling_plans`    | Subscription and selling-plan options on products and cart lines |
| `unauthenticated_write_checkouts`       | Cart operations and checkout                                     |
| `unauthenticated_read_content`          | Pages, blogs, and articles                                       |

For optional scopes and details, see [Storefront API permissions](/docs/reference/storefront-api-permissions).

Publish the products and collections you want to show to the **Headless** channel. See Shopify's guide [here](https://help.shopify.com/en/manual/products/add-update-products#product-availability). Content published only to the **Online Store** channel does not appear in your storefront.

## Set the storefront ID to enable server-side analytics

Set the `NEXT_PUBLIC_SHOPIFY_STOREFRONT_ID` environment variable to attribute server-side cart activity to this storefront. Find the numeric ID in the storefront's URL in the Headless app, after `/headless/`. For example, `https://admin.shopify.com/store/your-store/headless/330461` uses the ID `330461`. Server-side attribution gives you more reliable analytics and funnel metrics that do not depend on the shopper's browser.

## Configure search, 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.

## Optional Shopify features and apps

> **Enable more with skills:**
>
> Use [skills](/docs/skills) to enable additional Shopify features on your store with a coding
> agent.

### Enable kitting and bundling

Install [Shopify Bundles](https://apps.shopify.com/shopify-bundles) if you sell fixed bundles. Publish the bundle and its component products to **Headless**. The storefront displays bundle relationships when `pdp.bundles.isEnabled` is on in your [shop configuration](/docs/reference/shop-config), which is the default.

### Enable customer login

> **Customer login needs more setup:**
>
> Customer accounts require additional credentials and configuration. See
> [Authentication](/docs/anatomy/authentication) before enabling sign-in.

## Related links

#### [Product Setup](/docs/shopify/pdp)

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

#### [Shopify Integration](/docs/shopify)

See how Shopify concepts map to storefront routes and features.

#### [Webhooks](/docs/anatomy/webhooks)

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://vercel.shop/docs/reference/env-vars"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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 three values identify your Shopify store and connect the storefront.

| 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. In Shopify admin, under **Sales channels**, click **Headless** and select your storefront. |
| `NEXT_PUBLIC_SHOPIFY_SHOP_ID`                 | Numeric Shopify shop ID, distinct from the Headless storefront ID.                                                                    |

Shopify's default customer accounts URL has the form `https://shopify.com/123456789/account`, where `123456789` is the shop ID. `pnpm dev`, `pnpm build`, and `pnpm start` fail if any of the three values is missing.

## 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. Shopify uses it to attribute storefront traffic and analytics to that storefront.  |
| `SHOPIFY_API_VERSION`               | Override the Storefront API version. Defaults to the version Hydrogen targets; 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. Without it, `site.url` defaults to `http://localhost:3000`. For another host or a custom origin, set `site.url` in [Shop Configuration](/docs/reference/shop-config).

Vercel supplies `VERCEL_ENV`, and `eve dev` sets `EVE_DEV`. You do not need to set either value.

`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://vercel.shop/docs/reference/routes"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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 and metaobject updates |

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

## Shopify-compatible paths

The storefront answers these standard Shopify paths before page routing, so links built for a Shopify store keep working. Pages added at these paths do not render.

| Route                                                                  | Description                                    |
| ---------------------------------------------------------------------- | ---------------------------------------------- |
| `GET /checkout`                                                        | Redirect to the current cart's checkout        |
| `GET /cart/[variant-id]:[quantity]`                                    | Shopify cart permalink, sent to your store     |
| `GET /buy/[item]:[quantity]`                                           | Shopify buy permalink, sent to your store      |
| `/api/mcp`                                                             | Shopify's Storefront MCP endpoint              |
| `/cart.js`, `/cart/add`, `/cart/update`, `/cart/change`, `/cart/clear` | Shopify Ajax Cart API, forwarded to your store |

## 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                                   |
| `/account/sign-in-failed` | Shown when sign-in does not finish; offers a retry |
| `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/static.xml`        | Home page and store policy URLs                                                                                                |
| `/sitemap/[type]-[page].xml` | Paged entries, where `[type]` is `products`, `collections`, `pages`, `blogs`, or `articles`, such as `/sitemap/products-1.xml` |
| `/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 query in Markdown, pointing agents to UCP                                                                               |
| `/.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` or with `.md` appended, such as `/products/[handle].md`. The home page's Markdown URL is `/index.md`. 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://vercel.shop/docs/reference/shop-config"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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, WebMCP, and Shopify redirects on missing pages 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`              | Enables Shopify analytics and its consent integration.                                                                         |
| `analytics.shopify.consent.isEnabled` | `false`              | Enables Shopify consent without enabling Shopify analytics.                                                                    |
| `analytics.shopify.consent.mode`      | `default-banner`     | Chooses how shoppers give 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.                                                                               |
| `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.                                                                             |
| `redirects.shopifyNotFound.isEnabled` | `false`              | Enables [Shopify redirect lookup](/docs/anatomy/proxy#shopify-redirects-on-missing-pages) when a storefront page is not found. |
| `search.isEnabled`                    | `true`               | Shows the header search button and predictive search dialog.                                                                   |
| `shopify.webmcp.isEnabled`            | `false`              | Registers Shopify catalog and cart tools with WebMCP in supported browsers.                                                    |
| `site.description`                    | Template tagline     | Summarizes the storefront for AI agents in `/llms.txt` and the home page's Markdown.                                           |
| `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 in addition to the shop ID required for every build. 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.

Set the required `NEXT_PUBLIC_SHOPIFY_SHOP_ID` to the numeric shop ID, not the Headless storefront ID.

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

Shopify consent loads when Shopify analytics, standalone consent, or WebMCP is enabled. If all three are off, the storefront still loads Shopify cart actions but skips the analytics and consent scripts. These modes apply whenever Shopify consent loads.

| 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://vercel.shop/docs/reference/storefront-api-permissions"
docs_index: /llms.txt
lastUpdated: 2026-10-14
type: reference
prerequisites:
  - /docs/getting-started
---

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

After [Getting Started](/docs/getting-started), grant these Storefront API permissions to your storefront's token. In Shopify admin, under **Sales channels**, click **Headless** and select your storefront. In the **Storefront API permissions** card, click **Edit**.

## Required

| Scope                                   | Enables                                                          |
| --------------------------------------- | ---------------------------------------------------------------- |
| `unauthenticated_read_product_listings` | Products, collections, search, and recommendations               |
| `unauthenticated_read_selling_plans`    | Subscription and selling-plan options on products and cart lines |
| `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_product_tags`      | The `tags` field on products; not required for tag filters                                              |
| `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://vercel.shop/docs/reference/storefront-api"
docs_index: /llms.txt
lastUpdated: 2026-10-18
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) 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) {
    product(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, and the shopper's cart updates in the browser after the assistant changes it. 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 or Shopify reports throttling or a server error, and logs a warning for other errors that 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://vercel.shop/docs/reference/troubleshooting"
docs_index: /llms.txt
lastUpdated: 2026-10-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", protocol: "https" }],
},
```

**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 it for your agent with [Shopify's install instructions](https://github.com/Shopify/shopify-ai-toolkit#install).

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`. The endpoint returns `404` when `SHOPIFY_WEBHOOK_SECRET` is not set and `401` when a signature does not match.

**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 the [`enable-shopify-markets` skill](/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 the project root has `AGENTS.md` and the Shop skills in `.agents/skills/`. Claude Code reads the skills through the `.claude/skills` link. Skill support depends on the agent.

**Fix:** From the project root, add the Shop skills:

```bash
pnpm dlx skills add vercel/shop --skill '*' --yes
```

This command adds skills; it does not restore a missing `AGENTS.md`. Restore that file from your project's version control if it was removed. To install the Vercel plugin and Shopify AI Toolkit, see [Extending with Agents](/docs/getting-started/extending-with-agents#set-up-the-agent).

**Expected result:** Your agent lists the Shop skills 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: Build with Vercel Shop
description: Build or adapt Shopify storefronts with source-backed Vercel Shop patterns.
url: "https://vercel.shop/docs/skills/build-shop"
docs_index: /llms.txt
lastUpdated: 2026-10-21
type: guide
---

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

## How to use

Ask your agent to use the `build-shop` skill, or run it as a slash command in Claude Code:

```text
/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: `AGENTS.md`
   - Route/layout shell: `app/layout.tsx`, `app/page.tsx`
   - Shopify boundary: `lib/shopify/storefront/server.ts`, `lib/shopify/operations/`, `lib/shopify/transforms/`, `lib/product/types.ts`, `lib/customer/types.ts`
   - Next.js cache boundary: `lib/<domain>/server.ts` cached `get*` wrappers around the uncached `fetch*` operations
   - Shared UI layout primitives: `components/ui/container.tsx`, `components/ui/page.tsx`, `components/ui/sections.tsx`
   - Public source fallback: [template source](https://github.com/vercel/shop)
3. Use the docs to orient before editing unfamiliar areas:
   - [Documentation index](https://vercel.shop/llms.txt)
   - [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.
- `request.ts` contains code that takes the `Request` or `Headers` explicitly, such as Hydrogen handlers and session cookies; the root `proxy.ts` and Route Handlers import it. It never imports `next/cache`, `next/headers`, `next/navigation`, or `react`, which load the App Router runtime into the proxy; `server.ts` may import it.
- `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 `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://vercel.shop/docs/skills/enable-analytics"
docs_index: /llms.txt
lastUpdated: 2026-10-14
type: guide
---

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

## How to use

Ask your agent to use the `enable-analytics` skill, or run it as a slash command in Claude Code:

```text
/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: {
    consent: { isEnabled: false, mode: "default-banner" },
    isEnabled: false,
  },
  speedInsights: { isEnabled: false },
  vercel: { isEnabled: false },
},
```

The `shopify` gate is separate: it controls Shopify storefront analytics and its consent integration. Standalone consent can be enabled separately through `shopify.consent.isEnabled`. Leave these settings alone unless the user asked about Shopify analytics or consent, 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 this row to the `# Optional` block of `.env.example`, keeping it alphabetical:

```bash
# NEXT_PUBLIC_GTM_ID="GTM-XXXXXX" # Set to load Google Tag Manager; leave unset to skip it.
```

Add `NEXT_PUBLIC_GTM_ID` to `turbo.json` `globalEnv`, keeping it alphabetical; Turborepo's strict mode hides undeclared variables from tasks and leaves them out of cache keys. 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 the Shopify scripts gate: Shopify analytics, standalone consent, and WebMCP use the required numeric `NEXT_PUBLIC_SHOPIFY_SHOP_ID`. Build the ShopifyScripts props from configuration rather than making a shop-data request:

```tsx
import type { ShopifyScriptsI18n, ShopifyScriptsShop } from "@shopify/hydrogen";
import { GoogleTagManager } from "@next/third-parties/google";
import { Analytics } from "@vercel/analytics/next";
import { SpeedInsights } from "@vercel/speed-insights/next";

import { isShopifyScriptsEnabled, shopConfig } from "@/lib/config";

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

export function AnalyticsComponents() {
  const gtmId = process.env.NEXT_PUBLIC_GTM_ID;
  const i18n: ShopifyScriptsI18n = {
    country: shopConfig.localization.country,
    language: shopConfig.localization.language,
  };
  const shop: ShopifyScriptsShop = {
    myshopifyDomain: process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN as string,
    shopId: process.env.NEXT_PUBLIC_SHOPIFY_SHOP_ID as string,
    storefrontId: process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ID ?? "",
  };

  return (
    <>
      {shopConfig.analytics.vercel.isEnabled ? <Analytics /> : null}
      {shopConfig.analytics.speedInsights.isEnabled ? <SpeedInsights /> : null}
      {gtmId ? <GoogleTagManager gtmId={gtmId} /> : null}
      {isShopifyScriptsEnabled && <ShopifyScriptsTracker i18n={i18n} shop={shop} />}
    </>
  );
}
```

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 or consent.

The storefront sends page, product, collection, search, cart-view, and confirmed cart-change events through Hydrogen's analytics bus when Shopify scripts are enabled. Shopify analytics is disabled by default. Set the required `NEXT_PUBLIC_SHOPIFY_SHOP_ID` before building the storefront. Use the numeric Shopify shop ID, not the Headless storefront ID. To turn Shopify's built-in analytics destination and consent integration on, set `analytics.shopify.isEnabled` to `true` in `lib/config/index.ts`. To load Shopify consent without its analytics destination, set `analytics.shopify.consent.isEnabled` to `true`. WebMCP also loads Shopify scripts and consent without enabling the analytics destination.

Consent mode is set by `analytics.shopify.consent.mode` 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`; mount Shopify scripts when Shopify analytics, consent, or WebMCP is enabled.
- 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://vercel.shop/docs/skills/enable-i18n"
docs_index: /llms.txt
lastUpdated: 2026-10-14
type: guide
---

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

## How to use

Ask your agent to use the `enable-i18n` skill, or run it as a slash command in Claude Code:

```text
/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. Locale-aware Markdown negotiation in `proxy.ts`
9. `generateStaticParams` and the carried `ensureStatic` on the locale root layout
10. Add or adapt a copy-language selector without introducing a currency selector

## Cache Components compatibility — read this first

The template runs with `cacheComponents: true`, Partial Prefetching, and `export const ensureStatic = "prefetch"` on the root layout, which requires the App Shell and per-link prefetches to be static. Skipping any of these produces 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. Carry `ensureStatic` and give `locale` static params

Move `export const ensureStatic = "prefetch"` with the root layout into `app/[locale]/layout.tsx`, and export `generateStaticParams` there for every enabled locale (Step 11). Cache Components requires at least one value for each root param; without one, the build fails:

```
Error: A required root parameter (locale) was not provided in generateStaticParams for /[locale]/cart, please provide at least one value.
```

Keep the nested `generateStaticParams` exports that return placeholder handles from `lib/static-params`; Next.js combines them with the layout's locales. A localized page or layout must not set a weaker `ensureStatic` than the locale layout:

```
A child segment cannot override a parent segment with a less-constrained `ensureStatic`.
```

### C. Keep request reads behind Suspense

Resolve locale from the root param, never from `headers()`, `cookies()`, or `searchParams` in the shell. A request read outside `<Suspense>` anywhere under the locale layout fails every localized route:

```
Error: Route "/[locale]/account/addresses": Next.js encountered uncached or runtime data during prerendering.
```

Move the read into the smallest Suspense boundary that needs it; `next build --debug-prerender` reports the source line. Do not set `export const instant = false` to silence the error: `AGENTS.md` forbids that opt-out, and it does not relax `ensureStatic`.

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

### E. Keep the template `Link`

Do not replace `Link` from `@/components/ui/link` with next-intl's `Link`. The template `Link` owns intent-based prefetching, and `AGENTS.md` requires it for every internal link. 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.

### 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 { usePathname, useRouter } = createNavigation(routing);
```

> Per "Cache Components compatibility E", use this navigation only in the locale switcher and other client-side programmatic routing; internal links keep the template `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/`, `llms.txt/`, `sitemap.xml/`, `sitemap/`, `robots.ts`, `global-error.tsx`, `globals.css`, `favicon.ico`, `icon.svg`, `apple-icon.png`, `manifest.ts`. 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"` and keep every export, including `export const ensureStatic = "prefetch"`, metadata, and viewport.

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"),
};

// Read the root param, not next-intl's request locale, which reads the
// `x-next-intl-locale` request header and fails the layout's static shell.
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 at the end of the existing proxy, where the template returns `NextResponse.next(...)`. Keep everything before that point unchanged: Shopify route dispatch (`handleShopifyRoutes()` returns `null` synchronously when Hydrogen does not own the pathname), and Markdown negotiation (Step 9). Pass the forwarded headers through `forwardCustomerRefreshAttempt` so the account refresh marker still reaches Server Components:

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

// Keep the existing imports and add NextRequest as a runtime import.
export async function proxy(request: NextRequest): Promise<Response> {
  // Keep the template's Shopify dispatch and Markdown negotiation here, unchanged.

  const i18nRequest = new NextRequest(request, {
    headers: forwardCustomerRefreshAttempt(
      request.nextUrl,
      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 });
}
```

Update the matcher. The template's single broad matcher is safe only while the proxy does nothing but Shopify dispatch; with locale negotiation, every request Hydrogen declines is localized, including app Route Handlers, webhooks, `robots.txt`, sitemaps, `llms.txt`, and static files. Replace it with Shopify's exact route families, locale-prefixed Shopify endpoints, and a catch-all that skips `/api`, Eve, Next.js and Vercel internals, and paths with a file extension:

```ts
export const config = {
  matcher: [
    "/api/cart",
    "/api/predictive-search",
    "/api/mcp",
    "/api/ucp/mcp",
    "/api/:apiVersion(unstable|2\\d{3}-\\d{2})/graphql.json",
    "/__shopify/:path*",
    "/.well-known/:path*",
    "/agent/:action(handoff|buyer-claims).:format",
    "/cart.:format(js|json)",
    "/cart/:operation(add|update|change|clear).:format(js|json)",
    "/:page(index|search).md",
    "/:resource(collections|products)/:handle.md",
    "/: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)",
    "/((?!api|eve(?:/|$)|_eve_internal(?:/|$)|_next/static|_next/image|_next/data|_vercel|favicon.ico|.*\\..*).*)",
  ],
};
```

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 the template `Link`

Per "Cache Components compatibility E", **leave existing `Link` imports from `@/components/ui/link` 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: Locale-aware Markdown negotiation

`proxy.ts` serves Markdown through `getMarkdownPath()` and `getMarkdownMirrorPath()` in `lib/markdown/representation/index.ts`, which map `/`, `/search`, `/collections/:handle`, `/products/:handle`, and their `.md` mirrors to the unlocalized `app/md/...` handlers. Teach both to accept and strip a validated locale prefix so `/:locale/products/:handle` negotiates like `/products/:handle`, and keep the localized proxy matcher covering the localized paths. Inspect the 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, and preserve `?variant=` and search parameters.

### Step 10: Bare `/` and unknown locales

With `localePrefix: "always"`, next-intl's middleware redirects `/` to the negotiated locale; without the proxy change, `/` returns 404. Do not add an `app/(unlocalized)/page.tsx` fallback: a page outside `app/[locale]/` has no root layout, and the build fails with `(unlocalized)/page.tsx doesn't have a root layout`.

Unknown locales and URLs outside every route render Next.js's built-in 404 page, because no root layout applies to them. A branded page needs `app/global-not-found.tsx`, which is experimental (`experimental.globalNotFound`) in this Next.js version; add it only when the user accepts that.

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

Keep `export const ensureStatic = "prefetch"` beside it (see "Cache Components compatibility B"):

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

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

### Step 12: 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
- [ ] The build still reports localized storefront routes as partially prerendered (◐)
- [ ] 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 `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://vercel.shop/docs/skills/enable-shopify-markets"
docs_index: /llms.txt
lastUpdated: 2026-10-21
type: guide
---

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

## How to use

Ask your agent to use the `enable-shopify-markets` skill, or run it as a slash command in Claude Code:

```text
/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);
```

Keep `Link` from `@/components/ui/link` for internal links; do not replace it with next-intl's `Link`. 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/`
- `llms.txt/`
- `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 keep every export of the moved layout, including `export const ensureStatic = "prefetch"`. Export `generateStaticParams` from the locale root layout using `enabledLocales.map((locale) => ({ locale }))`; Cache Components requires at least one root-param value. Keep request reads behind Suspense, and do not add pages outside `app/[locale]/`; `enable-i18n` "Cache Components compatibility" and Step 10 list the resulting build errors and bare-`/` behavior. 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

Hydrogen's handlers take their market from the request context's `i18n`: every cart, predictive search, and account operation they run uses `@inContext(country, language)` from it. The template pins that context to `shopConfig.localization` in `createCustomerRequestContext` (`lib/auth/request.ts`) and in the server cart seed (`lib/cart/server.ts`). Accept the validated commerce context in both instead. Resolve the locale in the proxy before Shopify dispatch (`resolveRequestLocale` below): from the validated locale prefix when the path has one, otherwise from the validated locale cookie (unprefixed handler paths such as `/api/cart` carry no locale segment), otherwise `defaultLocale`.

Compose next-intl after the existing Shopify route dispatch and Markdown negotiation, where the template returns `NextResponse.next(...)`. `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 locale = resolveRequestLocale(request);
  const requestContext = createCustomerRequestContext(request, getCommerceLocale(locale));
  const shopifyRoute = handleShopifyRoutes({
    // Preserve the template's handlers, session manager, and storefront client.
    request,
    requestContext,
  });
  if (shopifyRoute) return shopifyRoute;

  // Keep the template's Markdown negotiation here, unchanged.

  const i18nRequest = new NextRequest(request, {
    headers: forwardCustomerRefreshAttempt(
      request.nextUrl,
      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.

Update the matcher. The template's single broad matcher is safe only while the proxy does nothing but Shopify dispatch; with locale negotiation, every request Hydrogen declines is localized, including app Route Handlers, webhooks, `robots.txt`, sitemaps, `llms.txt`, and static files. Replace it with Shopify's exact route families, locale-prefixed Shopify endpoints, and a catch-all that skips `/api`, Eve, Next.js and Vercel internals, and paths with a file extension:

```ts
export const config = {
  matcher: [
    "/api/cart",
    "/api/predictive-search",
    "/api/mcp",
    "/api/ucp/mcp",
    "/api/:apiVersion(unstable|2\\d{3}-\\d{2})/graphql.json",
    "/__shopify/:path*",
    "/.well-known/:path*",
    "/agent/:action(handoff|buyer-claims).:format",
    "/cart.:format(js|json)",
    "/cart/:operation(add|update|change|clear).:format(js|json)",
    "/:page(index|search).md",
    "/:resource(collections|products)/:handle.md",
    "/: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)",
    "/((?!api|eve(?:/|$)|_eve_internal(?:/|$)|_next/static|_next/image|_next/data|_vercel|favicon.ico|.*\\..*).*)",
  ],
};
```

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

If `enable-shopify-menus` has added `getMenu`, pass the validated commerce context as `getMenu({ handle, locale })` from every caller; its query already carries `@inContext`. 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 `getMarkdownPath()` and `getMarkdownMirrorPath()` in `lib/markdown/representation/index.ts` and the proxy's negotiation so the locale reaches unlocalized `app/md/...` handlers as a validated query/header value. Preserve `?variant=` and search parameters.

## 9. Switch locale and carry the market to the cart

Cart writes stay in Hydrogen's `/api/cart` handler; never mutate the cart from a Server Action or a custom route. Because the proxy builds the handler's request context from the selected locale (section 7), the next cart read or write after a switch runs in the new market:

- `fr-CA` to `en-CA`: copy and Shopify language change; the country stays `CA`.
- `en-CA` to `en-US`: the cart's next Hydrogen operation runs with country `US`.

Use Shopify AI Toolkit to confirm how the store's cart buyer country and pricing follow `@inContext` on cart operations. If the store requires an explicit buyer-country update, the installed Hydrogen SDK exposes no cart intent for it: report that gap instead of adding a cart mutation outside Hydrogen's handlers.

The selector remains a leaf Client Component. A Server Action in `lib/i18n/action.ts` may validate the requested locale and set the locale cookie; it never reads or writes the cart. After a country change, call `refresh()` from `useCartActions()` so prices and checkout reflect Shopify's response for the new market; do not invalidate public cache tags.

- **Sub-path/per-domain:** use next-intl's client router to replace the current pathname with `{ locale: nextLocale }`, then refresh the cart.
- **Invisible cookie:** call the action, then call `router.refresh()` and refresh the cart. 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 the storefront root:

```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` keeps Shopify country `CA` for the cart.
- `en-CA` to `en-US` refreshes the cart with Shopify's `US` context, and every cart write still goes through `/api/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://vercel.shop/docs/skills/enable-shopify-menus"
docs_index: /llms.txt
lastUpdated: 2026-10-14
type: guide
---

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

## How to use

Ask your agent to use the `enable-shopify-menus` skill, or run it as a slash command in Claude Code:

```text
/enable-shopify-menus
```

<div className="pb-6" />

# Enable Shopify Menus

By default, the storefront's nav renders an inline `const items: MenuItem[] = [...]` in `Nav` (`components/nav/index.tsx`), and the footer renders an empty `const items: MenuItem[] = []` in `Footer` (`components/footer/index.tsx`). `QuickLinks`, `MobileMenu`, and `Footer`'s `FooterMenu` already render `MenuItem[]` from `lib/menu/types.ts` up to three levels deep.

This skill adds a cached Shopify menu read, points the selected components at it with the inline items as fallback, and adds a protected endpoint that refreshes menus after merchants edit them. Shopify sends no webhook when a menu changes, so menus stay cached until that endpoint is called.

## 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** — adds footer columns.
- **Both**

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

Ask for each selected menu. Defaults: `main-menu` for nav, `footer` for footer. Every Shopify store starts with both.

Wait for the user to answer before proceeding.

***

## 1. Add the menu operation

Create `lib/shopify/operations/menu/server.ts`. Shopify returns absolute item URLs on the store's myshopify or primary domain, sometimes with a Markets locale prefix; the operation turns those into storefront paths and leaves external links alone.

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

import { shopConfig } from "@/lib/config";
import type { CommerceLocale } from "@/lib/config/types";
import type { MenuItem } from "@/lib/menu/types";
import { assertStorefrontOk } from "@/lib/shopify/errors/server";
import { storefront } from "@/lib/shopify/storefront/server";

const GET_MENU_QUERY = gql(`#graphql
  query getMenu($handle: String!, $country: CountryCode, $language: LanguageCode) @inContext(country: $country, language: $language) {
    shop {
      primaryDomain {
        url
      }
    }
    menu(handle: $handle) {
      items {
        id
        title
        url
        items {
          id
          title
          url
          items {
            id
            title
            url
          }
        }
      }
    }
  }
`);

function toStorefrontUrl(url: string | null | undefined, internalHosts: string[]): string {
  if (!url) return "/";
  let parsed: URL;
  try {
    parsed = new URL(url);
  } catch {
    return url;
  }
  if (!internalHosts.includes(parsed.hostname)) return url;
  const path = parsed.pathname.replace(/^\/[a-z]{2}(?:-[a-z]{2,4})?(?=\/|$)/i, "") || "/";
  return `${path}${parsed.search}${parsed.hash}`;
}

export async function fetchMenu({
  handle,
  locale = shopConfig.localization,
}: {
  handle: string;
  locale?: CommerceLocale;
}): Promise<MenuItem[] | null> {
  const response = await storefront.request(GET_MENU_QUERY, { locale, variables: { handle } });
  assertStorefrontOk(response, "getMenu");

  const menu = response.data.menu;
  if (!menu || menu.items.length === 0) return null;

  const internalHosts = [
    process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN ?? "",
    new URL(response.data.shop.primaryDomain.url).hostname,
  ];
  const link = ({ id, title, url }: { id: string; title: string; url?: string | null }) => ({
    id,
    title,
    url: toStorefrontUrl(url, internalHosts),
  });

  return menu.items.map((item) => ({
    ...link(item),
    items: item.items.map((child) => ({
      ...link(child),
      items: child.items.map((leaf) => ({ ...link(leaf), items: [] })),
    })),
  }));
}
```

If you add fields, validate the document with the Shopify AI Toolkit first.

## 2. Cache the menu

Create `lib/menu/server.ts`:

```ts
import { cacheLife, cacheTag } from "next/cache";

import type { CommerceLocale } from "@/lib/config/types";
import type { MenuItem } from "@/lib/menu/types";
import { fetchMenu } from "@/lib/shopify/operations/menu/server";

export async function getMenu(params: {
  handle: string;
  locale?: CommerceLocale;
}): Promise<MenuItem[] | null> {
  "use cache: remote";
  cacheLife("max");
  cacheTag("menus");

  return fetchMenu(params);
}
```

## 3. Add the refresh endpoint

Create `app/api/revalidate/menus/route.ts`. It returns `404` until `MENUS_REVALIDATE_SECRET` is set:

```ts
import crypto from "node:crypto";

import { revalidateTag } from "next/cache";

const MENUS_REVALIDATE_SECRET = process.env.MENUS_REVALIDATE_SECRET;

export async function POST(request: Request) {
  if (!MENUS_REVALIDATE_SECRET) return new Response(null, { status: 404 });

  const expected = Buffer.from(`Bearer ${MENUS_REVALIDATE_SECRET}`);
  const received = Buffer.from(request.headers.get("authorization") ?? "");
  if (expected.length !== received.length || !crypto.timingSafeEqual(expected, received)) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  revalidateTag("menus", { expire: 0 });
  return Response.json({ revalidated: "menus" });
}
```

Add this row to the `# Optional` block of `.env.example`, keeping it alphabetical:

```bash
# MENUS_REVALIDATE_SECRET="your-menus-revalidate-secret-here" # Set when Shopify menus are enabled; send it as a Bearer token to POST /api/revalidate/menus after editing menus.
```

Add `MENUS_REVALIDATE_SECRET` to `turbo.json` `globalEnv`, keeping it alphabetical; Turborepo's strict mode hides undeclared variables from tasks.

Tell the user to set `MENUS_REVALIDATE_SECRET` in the deployment's environment variables, then refresh menus after editing them in Shopify admin:

```bash
curl -X POST https://your-store.example.com/api/revalidate/menus \
  -H "Authorization: Bearer $MENUS_REVALIDATE_SECRET"
```

Any scheduler or automation that can send that request can refresh menus on a schedule.

## 4. Wire the nav menu

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

Edit `components/nav/index.tsx`. Make `Nav` async (preserving any existing props in a customized storefront), import `getMenu`, and let the Shopify menu take precedence over the inline default:

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

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

Replace `"NAV_HANDLE"` with the handle the user provided. `QuickLinks` and `MobileMenu` need no changes.

## 5. Wire the footer menu

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

Edit `components/footer/index.tsx`. Import `getMenu` and replace `const items: MenuItem[] = [];` with:

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

Replace `"FOOTER_HANDLE"` with the handle the user provided. `FooterMenu` needs no changes; an empty or missing menu hides the columns.

## Verification

- The nav and footer render the Shopify menu items, with internal links as storefront paths such as `/collections/all`, and external links unchanged.
- A missing or empty menu falls back to the inline items.
- `POST /api/revalidate/menus` returns `404` without `MENUS_REVALIDATE_SECRET`, `401` with a wrong token, and `200` with the right one.
- Lint, typecheck, and build pass.

## Guardrails

- Keep the inline fallbacks so a missing menu or Storefront error never leaves a blank nav.
- Keep `cacheLife("max")` with the `menus` tag and refresh through the endpoint; do not shorten the cache life to pick up edits.
- In a localized or Markets-enabled store, pass the validated commerce locale to `getMenu` from each caller; do not read locale from request headers inside the cached read.
- New fallback labels belong beside the consuming component; Shopify menu translations and storefront fallback copy are separate.

---

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://vercel.shop/docs/skills"
docs_index: /llms.txt
lastUpdated: 2026-10-14
type: overview
prerequisites:
  - /docs/getting-started
---

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

Skills are agent-ready guides that change your storefront the same way every time. Every project includes the Shop skills in `.agents/skills/`. Any coding agent that supports project skills can run them, or you can follow a skill's steps yourself.

To add the Shop skills to an existing storefront, run this from its root:

```bash
pnpm dlx skills add vercel/shop --skill '*' --yes
```

#### [Initialize Vercel Shop](/docs/skills/init-vercel-shop)

Create a storefront from the template and connect a Shopify store.

#### [Build with Vercel Shop](/docs/skills/build-shop)

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

#### [Update Vercel Shop](/docs/skills/update-shop)

Compare a storefront with the current template and apply the updates you choose.

#### [Shopify GraphQL Integration](/docs/skills/shopify-graphql-reference)

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

#### [Analytics](/docs/skills/enable-analytics)

Add Vercel Analytics, Speed Insights, and Google Tag Manager.

#### [i18n](/docs/skills/enable-i18n)

Add locale-prefixed URLs and translated copy without Shopify Markets.

#### [Shopify Markets](/docs/skills/enable-shopify-markets)

Add regional locale routing and Shopify-localized commerce context.

#### [Shopify Menus](/docs/skills/enable-shopify-menus)

Replace hardcoded nav and footer links with Shopify-managed menus.

---

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: Initialize Vercel Shop
description: Create a Vercel Shop storefront from the template and optionally connect a Shopify store.
url: "https://vercel.shop/docs/skills/init-vercel-shop"
docs_index: /llms.txt
lastUpdated: 2026-10-14
type: guide
---

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

## How to use

The skill runs before a project exists, so install it for your user first:

```bash
pnpm dlx skills add vercel/shop --skill init-vercel-shop --global --yes
```

Then ask your agent to use the `init-vercel-shop` skill, or run it as a slash command in Claude Code:

```text
/init-vercel-shop
```

The skill creates the project with create-next-app and can connect an existing Shopify store with a public Storefront API token. The new project includes all Shop skills.

<div className="pb-6" />

# Initialize Vercel Shop

Ask for the target directory if the user did not provide one. Always pass it explicitly.

1. Inspect the target. If it already contains a Vercel Shop project, stop and use the relevant storefront skill instead. If it is a non-empty directory, ask for a different target rather than overwriting files.

2. Run:

   ```bash
   pnpm create next-app@latest <target-directory> --example https://github.com/vercel/shop
   ```

   The template pins pnpm 12 and Node.js 24.

3. Confirm the generated project contains `AGENTS.md`, `.agents/skills/`, `app/`, `components/`, `lib/shopify/`, and `package.json`.

4. Read the generated `AGENTS.md` before making further changes.

5. If dependency installation fails, keep the generated project and show the retry command to run from its root:

   ```bash
   pnpm install
   ```

6. Ask one optional follow-up: **Connect an existing Shopify store now?**

   If yes, ask only for the store's admin URL or `.myshopify.com` domain, then read [references/storefront-token.md](https://github.com/vercel/shop/blob/main/.agents/skills/init-vercel-shop/references/storefront-token.md) and follow its automated Shopify CLI flow. Try to reuse or create a public Storefront token before asking the user to visit the Headless channel.

   If no, finish with the normal environment setup as the next step.

Return the generated project path, whether a Shopify store was connected, and whether dependency installation needs a retry.

---

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://vercel.shop/docs/skills/shopify-graphql-reference"
docs_index: /llms.txt
lastUpdated: 2026-10-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. Ask your agent to use the `shopify-graphql-reference` skill, or run it as a slash command in Claude Code:

```text
/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 skill.
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://vercel.shop/docs/skills/update-shop"
docs_index: /llms.txt
lastUpdated: 2026-10-14
type: guide
---

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

## How to use

Ask your agent to use the `update-shop` skill, or run it as a slash command in Claude Code:

```text
/update-shop
```

The skill compares your storefront with the current `vercel/shop` repository. It can report the differences, plan an upgrade, or apply the changes you select. It preserves your customizations, validates each change, and does not add tracking files to your project.

<div className="pb-6" />

# Update Vercel Shop

Compare the working storefront with the current upstream `vercel/shop` repository. The template is a reference, not a replacement for downstream code. Base decisions on implemented behavior, not scaffold age or version metadata.

## Pick a mode

Infer the mode from the user's request:

- **Audit** — read-only comparison; report relevant differences and stop.
- **Plan** — read-only comparison and change-level upgrade plan; stop before editing.
- **Apply** — plan, confirm the selected changes, apply them, and validate. Use when the user asks to update or upgrade the shop.

## Inspect the project

Read applicable `AGENTS.md` instructions, project structure, dependency versions, scripts, configuration, and relevant source. Inspect git status and staged/unstaged diffs, including untracked files, before proposing edits. Identify the storefront root; the upstream repository root maps to that root, which is not necessarily the downstream repository root.

Preserve dirty state, merchant design, custom features, integrations, and intentional departures from the template. Never overwrite custom code with template files or reset, stash, commit, or push without explicit permission. Stop if selected work cannot be separated safely from existing edits.

Keep each change within its owner:

- **Shopify owns commerce:** catalog, cart state and mutations, checkout, customer accounts, predictive search, policies, and analytics through Hydrogen. Prices, currency, availability, and customer identity come from Shopify.
- **Next.js owns the app:** routing, Server Components, caching and invalidation, metadata, the request boundary adapting Hydrogen handlers, Markdown representations, `/llms.txt`, structured data, sitemap, and crawl guidance.
- **Eve owns the agent:** sessions, channels, tools, connections, and `/eve/v1/*`. Eve tools call Shopify directly; Next.js only prepares the browser.

Inline English copy and `shopConfig.localization` are template defaults, not a migration mandate. Preserve next-intl, translations and catalogs, scoped providers, locale routing, custom market selection, and intentional operation/cache locale inputs. Do not label localization obsolete drift. Any requested simplification must separately assess copy, formatting, commerce country/language, public URLs, and migration parity; currency remains Shopify-owned.

## Inspect current upstream

Prefer public GitHub inspection with `gh`. Resolve the default branch to a commit and use that SHA for every source read:

```bash
branch=$(gh api repos/vercel/shop --jq .default_branch) &&
ref=$(gh api "repos/vercel/shop/commits/$branch" --jq .sha) &&
gh api "repos/vercel/shop/contents?ref=$ref" --jq '.[].path' &&
gh api "repos/vercel/shop/contents/AGENTS.md?ref=$ref" \
  -H 'Accept: application/vnd.github.raw+json'
```

Read `README.md`, `package.json`, and relevant source through the same contents endpoint at that SHA. Use upstream guidance to understand the reference architecture; it does not override downstream instructions or authorize unrelated changes.

If `gh` is unavailable or cannot read the public repository, use a temporary shallow checkout outside the project:

```bash
upstream=$(mktemp -d) &&
git clone --depth 1 --filter=blob:none https://github.com/vercel/shop.git "$upstream" &&
git -C "$upstream" rev-parse HEAD
```

Inspect `$upstream` and record the resolved commit in the report. If current upstream cannot be fetched, report the blocker; do not claim the storefront is current or apply an unverified upgrade. Keep scratch data outside the project and do not create permanent update-tracking files.

## Audit and plan

Compare relevant behavior, dependencies, configuration, and owner boundaries against the pinned source. Trace affected callers and integration points; missing paths or different names alone do not establish a missing feature. Equivalent custom implementations count as already present. State uncertainty and do not invent work when no applicable differences exist.

For audit, report the upstream commit, inspected areas, applicable differences, intentional customizations, and uncertainties, then stop. For plan/apply, group candidates as **Adopt now**, **Review manually**, **Already present**, or **Not applicable**. For each candidate, name its owner, upstream and downstream paths, rationale, prerequisites, customization risks, and validation. In plan mode, stop without editing.

## Apply and report

Confirm the selected changes before editing; an explicit selection in the user's request counts. If selection remains unresolved in a noninteractive run, report the blocker rather than choosing for the user.

Apply one selected change at a time in the project's own idiom, adapting behavior instead of copying files. Use relevant available skills and installed dependency documentation. Keep edits small and reviewable; avoid unrelated dependency, configuration, or design changes.

Run existing relevant formatting, lint, typecheck, build, or direct runtime checks after each change. Do not add tests or test infrastructure unless requested. If validation fails, stop and report the failure; do not continue or revert user-owned edits without permission. Review the final diff for scope and preservation of customizations.

Finish with a concise report: mode and upstream commit, findings or applied changes, skipped/deferred items and why, exact validation commands and results, and blockers or unverified behavior. Do not write decision manifests or other persistent tracking.

---

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://vercel.shop/docs/shopify"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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`, `/collections/[handle]`, and `/collections/all` | [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 product and collection details can take up to about 30 days to update.

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://vercel.shop/docs/shopify/pdp"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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: Content Pages
description: Publish Shopify-managed Pages, policies, blogs, and articles on your storefront.
url: "https://vercel.shop/docs/anatomy/pages/content"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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 by default. You can opt into [Shopify redirects](/docs/anatomy/proxy#shopify-redirects-on-missing-pages) for missing pages.

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 their 50 most recent articles with dates, excerpts, and optional images; older articles are not listed. 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 take up to about 30 days to appear unless the cache is refreshed. If a change must appear sooner, 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.

## What’s next

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://vercel.shop/docs/anatomy/pages/home"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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://vercel.shop/docs/anatomy/pages"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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.

#### [Home](/docs/anatomy/pages/home)

Introduce the store and direct shoppers into the catalog.

#### [Product Detail Page (PDP)](/docs/anatomy/pages/pdp)

Help shoppers choose a variant and complete a purchase.

#### [Product Listing Page (PLP)](/docs/anatomy/pages/plp)

Help shoppers browse, filter, sort, and search the catalog.

#### [Content Pages](/docs/anatomy/pages/content)

Publish 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://vercel.shop/docs/anatomy/pages/pdp"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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.

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. Not available for subscription purchases.
- **Bundles** — shows included items and other bundles containing the product. Bundles that require shoppers to choose items cannot be added to the cart.
- **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://vercel.shop/docs/anatomy/pages/plp"
docs_index: /llms.txt
lastUpdated: 2026-10-14
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`. `/collections/all` lists every product. All of them provide the same product grid, filters, sorting, and continuous loading.

## Help shoppers refine results

Collection pages show the collection title and description. Search pages focus on the shopper's query. Filtered, sorted, and search results stay live; an unfiltered collection's first page is cached briefly, so Search & Discovery edits can take a few minutes to appear there.

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** — the filters you configure in Shopify Search & Discovery, plus price. Category filters are not shown.
- **Swatches** — color or image choices when Shopify provides swatch presentation data.
- **Sorting** — Shopify's supported collection sorts. Search and `/collections/all` sort only by relevance and price, the sorts Shopify search supports.
- **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 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)