Repository navigation
Vercel Shop 2026.10.1
Vercel Shop 2026.10.1
A security and hardening release for every storefront. It moves to Next.js 16.4.3, which fixes two critical remote code execution vulnerabilities, sanitizes Shopify rich text, validates Server Action input, and keeps the page runtime out of the proxy. Single-price products become eligible for Google merchant listings, filters, option pickers, the cart, and the mobile menu work better with screen readers and short screens, and update-shop upgrades one release at a time.
This release changes update-shop, so refresh it before upgrading:
pnpm dlx skills add vercel/shop --skill update-shop --yesStack: Next.js 16.4.3, Hydrogen 2026.10.2, eve 0.83.0, Node.js 24.
next-16-4-3
Action: adopt. Applies to: all. Paths: package.json, pnpm-lock.yaml.
Next.js 16.4.3 and React 19.3.1. Next.js 16.4.1 fixes two critical remote code execution vulnerabilities (GHSA-673q-mjm6-r5xr, GHSA-2464-fhr4-w9xw) and a Server Actions denial of service (GHSA-p3m5-59mp-jv5r), and 16.4.3 keeps the page runtime out of the proxy, so it starts faster.
Apply when: the storefront runs Next.js 16.4.2 or earlier.
Skip when: the storefront runs Next.js 16.4.3 or later.
Validate: pnpm build passes, pnpm why next sharp reports next 16.4.3 and sharp 0.35.5 or later, and product, search, and cart pages work.
dependency-updates-2026-10-25
Action: adopt. Applies to: all. Paths: package.json, pnpm-lock.yaml.
Patch and minor updates: @shopify/hydrogen 2026.10.2, cn 0.5.0, lucide-react 1.61.0, oxfmt 0.74.0, oxlint 1.89.0, streamdown 2.7.1, turbo 2.11.10, and zod 4.6.7, and for Shop Agent, eve 0.83.0 and ai 7.0.150. Hydrogen 2026.10.2 fixes cart lines hidden by a change made during a cart load, multi-line updateCart calls, and cart hydration mismatches.
Apply when: the storefront runs older versions on Node.js 22 or later, which cn 0.5.0 requires. If its agent sets limits.maxInputTokensPerSession, maxOutputTokensPerSession, or maxTokenCostUsdPerSession, move them to limits.sessionUsage and match SESSION_USAGE_LIMIT_REACHED; replace eve/sandbox/microsandbox with DockerSandbox from eve/sandbox/docker.
Skip when: the storefront deliberately holds these packages back.
Validate: pnpm lint, pnpm typecheck, pnpm build, and pnpm build:agent pass, and cart changes keep every other line in the drawer.
proxy-safe-modules
Action: adopt. Applies to: all. Paths: .agents/skills/build-shop/SKILL.md, .agents/skills/build-shop/references/cart-provider.md, .agents/skills/enable-shopify-markets/SKILL.md, AGENTS.md, app/api/agent/session/route.ts, lib/auth/request.ts, lib/auth/server.ts, lib/cart/client.ts, lib/cart/request.ts, lib/cart/server.ts, lib/cart/types.ts, lib/search/request.ts, lib/search/server.ts, proxy.ts.
The proxy imports its auth, cart, and predictive search code from lib/{auth,cart,search}/request.ts, which never import next/cache, next/headers, next/navigation, or react, so it no longer loads the page runtime. lib/auth/request.ts holds the session cookie and manager, the customer request context, the REFRESH_* constants, and getHydrogenCustomerSession(), which now throws instead of calling notFound() when customer accounts are off.
Apply when: the storefront's proxy.ts imports a server.ts module or anything that imports those Next.js or React modules.
Skip when: the storefront has no proxy.ts, or its proxy imports none of them.
Validate: pnpm build passes, the proxy chunk's source map lists no next/headers, next/cache, navigation.react-server, or react sources, and cart, search, Markdown, and sign-in requests work.
proxy-optional-session-manager
Action: optional. Applies to: all. Paths: proxy.ts.
The proxy passes a session manager only with the customer-account handlers and drops NOOP_SESSION_MANAGER, which Hydrogen 2026.10.2 no longer needs.
Apply when: the storefront runs @shopify/hydrogen 2026.10.2 or later and its proxy.ts defines NOOP_SESSION_MANAGER.
Skip when: the storefront runs an earlier Hydrogen, or a custom handler reads the session while customer accounts are off.
Validate: /api/cart and predictive search work, and with customer accounts on, signing in and changing the cart while signed in work.
proxy-static-files
Action: adopt. Applies to: all. Paths: proxy.ts.
The proxy skips the storefront's own static and metadata files, matched by exact path, so icons, robots.txt, sitemaps, and /llms.txt never run it. Shopify routes such as /cart/add.js and the .md mirrors still do.
Apply when: the storefront keeps the template's single matcher. Add the exact paths of any public files the storefront adds.
Skip when: the storefront replaced the matcher, as enable-i18n and enable-shopify-markets do.
Validate: on next start, /robots.txt responds without Hydrogen's powered-by header, while /cart.js and a product's .md mirror carry it.
storefront-market-variables
Action: optional. Applies to: all. Paths: .agents/skills/shopify-graphql-reference/references/REFERENCE.md, AGENTS.md, lib/shopify/operations/products/server.ts, lib/shopify/storefront/server.ts, lib/shopify/storefront/types.ts.
storefront.request takes its options type from Hydrogen's GqlRestParam, so only $country: CountryCode and $language: LanguageCode come from the request's market, and same-named variables of other types are the caller's to pass. lib/shopify/storefront/types.ts is removed, and the wrapper forwards signal.
Apply when: the storefront runs @shopify/hydrogen 2026.10.2 or later and still has lib/shopify/storefront/types.ts.
Skip when: the storefront runs an earlier Hydrogen, which fills both variables by name.
Validate: pnpm typecheck and pnpm build pass.
sanitize-shopify-html
Action: review. Applies to: all. Paths: components/blog/article-page.tsx, components/content/rich-text-page.tsx, lib/shopify/html/server.ts, lib/shopify/operations/blogs/server.ts, lib/shopify/operations/pages/server.ts, lib/shopify/operations/policies/server.ts, lib/shopify/operations/products/server.ts, package.json, pnpm-lock.yaml.
Shopify rich text is sanitized on the server with sanitize-html 2.18.1, typed by @types/sanitize-html 2.16.2, before it renders. Pages, policies, articles, and product descriptions keep their formatting, links, images, tables, and YouTube and Vimeo embeds, and lose scripts, forms, other iframes, event handlers, classes, and styles other than color, alignment, and decoration.
Apply when: the storefront renders Shopify body, contentHtml, or descriptionHtml as HTML.
Skip when: the storefront already sanitizes this HTML.
Validate: content pages keep their formatting and video embeds. Content that needs scripts, forms, or other embeds needs storefront code or a wider allowlist in lib/shopify/html/server.ts.
no-danger-lint
Action: adopt. Applies to: all. Paths: .oxlintrc.json, components/product-detail/product-info.tsx.
pnpm lint fails on dangerouslySetInnerHTML without a react/no-danger disable directive, so raw HTML can't skip sanitizeShopifyHtml unnoticed.
Apply when: the storefront uses the template's lint config; give each of its own dangerouslySetInnerHTML a directive naming the sanitizer.
Skip when: the storefront has its own lint policy for raw HTML.
Validate: pnpm lint passes.
server-action-input-schemas
Action: adopt. Applies to: all. Paths: AGENTS.md, lib/collections/action.ts, lib/customer/action.ts, lib/product/action.ts.
Every Server Action parses its arguments with a zod schema first, so malformed input gets the action's usual failure result instead of reaching Shopify or throwing a 500. AGENTS.md states the rule for new actions.
Apply when: the storefront keeps the template's Server Actions or adds its own.
Skip when: the storefront replaced these actions.
Validate: pnpm typecheck passes, and load more, quick shop, profile edits, and address changes work.
session-cookie-tag-length
Action: adopt. Applies to: customer-accounts. Paths: lib/auth/request.ts.
The customer session cookie decrypts only with a full 16-byte AES-GCM tag, so a forged cookie can't use a shorter one. Existing sessions keep working.
Apply when: the storefront keeps the template's session cookie code, even with customer accounts off.
Skip when: the storefront stores customer sessions another way.
Validate: with customer accounts on, an existing session stays signed in, and signing in, refreshing, and signing out work.
markdown-mirror-input
Action: adopt. Applies to: all. Paths: app/md/collections/[handle]/route.ts, app/md/products/[handle]/route.ts, components/nav/search-modal.tsx, lib/markdown/index.ts, lib/markdown/not-found/index.ts, lib/markdown/search/index.ts, lib/search/index.ts.
Markdown mirrors don't echo crafted input: the search mirror reduces the query to one escaped line of at most 100 characters, and not-found mirrors omit the requested handle.
Apply when: the storefront serves the template's Markdown mirrors, or puts request input into its own Markdown.
Skip when: the storefront removed the mirrors.
Validate: /search.md?q=shoes%0A%0A[x](https://example.com) returns one escaped heading line, and /products/missing.md omits the handle.
eve-info-guard
Action: adopt. Applies to: agent. Paths: agent/channels/eve.ts, README.md.
Anonymous shoppers can no longer reach Shop Agent's info route, which exposes its instructions, tools, and model, through a trailing slash or a percent-encoded path. The README says to turn on bot protection before enabling public chat.
Apply when: Shop Agent is enabled, or the storefront keeps the template's Eve channel.
Skip when: the Eve channel no longer admits anonymous browsers.
Validate: /eve/v1/info, /eve/v1/info/, and /eve/v1/%69nfo return 401 without operator credentials, the trailing-slash form after a 308, and chat still works.
collection-id-invalidation
Action: adopt. Applies to: all. Paths: app/api/webhooks/shopify/route.ts, lib/collections/server.ts.
A deleted collection, or the old URL of a renamed one, shows the not-found page as soon as Shopify's webhook arrives. The webhook invalidates collections by ID, because delete payloads carry no handle.
Apply when: the storefront keeps the template's webhook handler at /api/webhooks/shopify, registered or not.
Skip when: the storefront does not use the template's webhook handler.
Validate: a signed collections/delete request with {"id":123,"admin_graphql_api_id":"gid://shopify/Collection/123"} returns collection-123 in tagsInvalidated.
root-canonical
Action: adopt. Applies to: all. Paths: app/layout.tsx.
Pages without their own canonical, such as the cart, account, and 404 pages, no longer declare the home page as canonical.
Apply when: the storefront keeps the template's root layout metadata.
Skip when: the storefront's root layout sets no alternates.
Validate: home, collection, and product pages each have their own canonical, and /cart has none.
uniform-price-offer
Action: adopt. Applies to: all. Paths: components/product-detail/schema.tsx.
Products whose variants share one price emit a single Schema.org Offer instead of an AggregateOffer, which makes multi-variant products eligible for Google merchant listings.
Apply when: the storefront renders the template's ProductSchema.
Skip when: the storefront replaced the product structured data.
Validate: a single-price product with several variants has an Offer with price and priceCurrency, and Google's Rich Results Test lists it as eligible for merchant listings.
sitemap-article-shards
Action: adopt. Applies to: all. Paths: app/sitemap/[shard]/route.ts, lib/seo/server.ts, lib/shopify/operations/sitemap/server.ts, lib/shopify/operations/sitemap/types.ts.
Each article sitemap shard costs one Storefront API request once earlier shards are cached, and shard numbers past the last page return 404.
Apply when: the storefront serves /sitemap/{type}-{n}.xml through the template's shard route.
Skip when: the storefront generates its sitemap another way.
Validate: /sitemap.xml lists the same shards as before, and /sitemap/products-9999.xml returns 404.
price-filter-bounds
Action: adopt. Applies to: all. Paths: lib/filters/types.ts, lib/shopify/transforms/filters/index.ts.
The price filter uses only the bounds Shopify returns; a missing bound means no limit rather than 0 or 1000.
Apply when: the storefront uses the template's collection filters.
Skip when: the storefront builds price filters from its own data.
Validate: pnpm typecheck passes with priceRange.min and priceRange.max treated as optional.
discount-badge-rounding
Action: adopt. Applies to: all. Paths: components/product-card/components.tsx, components/product-detail/product-price.tsx, lib/product/index.ts.
A markdown that rounds to 0% no longer shows a stray 0 beside the price; the discount badge starts at 1% off.
Apply when: the storefront renders the template's ProductCardPrice or ProductPrice.
Skip when: the storefront replaced both.
Validate: a 99.99 variant with a 100.00 compare-at shows no badge and no 0, and a 20% markdown shows 20% OFF.
sold-out-option-values
Action: adopt. Applies to: all. Paths: components/product-detail/color-picker.tsx, components/product-detail/option-picker.tsx.
Shoppers can select sold-out option values to see that variant's price and its Out of Stock state, on the product page, in quick shop, and in agent results.
Apply when: the storefront renders the template's OptionPicker or ColorPicker.
Skip when: the storefront replaced the option pickers.
Validate: a sold-out value is a link labeled "(sold out)" that shows Out of Stock when selected.
unavailable-option-values
Action: adopt. Applies to: all. Paths: components/product-detail/color-picker.tsx, components/product-detail/option-picker.tsx.
Option values for combinations the product doesn't have render as disabled buttons labeled "(unavailable)", so screen readers announce them. They look the same.
Apply when: the storefront renders the template's OptionPicker or ColorPicker.
Skip when: the storefront replaced the option pickers.
Validate: a missing combination is a disabled button named ": (unavailable)".
filter-and-cart-labels
Action: adopt. Applies to: all. Paths: components/cart/discount-form.tsx, components/cart/line-item.tsx, components/collections/filters-client.tsx.
Filters and the cart work with screen readers and the keyboard: filter options and swatches are toggle buttons that report pressed and respond to Space, badges read "Remove filter: …", the price inputs and each cart line's controls have names, and the pending discount button keeps its name.
Apply when: the storefront renders the template's filters, cart lines, or discount form.
Skip when: the storefront replaced them.
Validate: in the accessibility tree, a selected filter option reports pressed, and two cart lines of one product in different sizes have distinct control names.
mobile-menu-scroll
Action: adopt. Applies to: all. Paths: components/nav/mobile-menu.tsx.
The mobile menu scrolls inside its drawer, so nested menus stay reachable on short screens.
Apply when: the storefront keeps the template's MobileMenu drawer.
Skip when: the storefront replaced the menu's drawer.
Validate: on a phone-sized viewport, every group expands and the last link scrolls into view.
footer-without-copyright
Action: optional. Applies to: all. Paths: components/footer/index.tsx.
The footer drops its undated © Store name. All rights reserved. line.
Apply when: the merchant does not need a copyright notice.
Skip when: the merchant wants one; date it with the year of first publication.
Validate: the footer shows the policy links without the line.
button-render-prop
Action: optional. Applies to: all. Paths: components/ui/button.tsx, components/agent/shopping-results.tsx, components/error/error-boundary-content.tsx, app/account/(authenticated)/orders/[id]/page.tsx, app/account/(authenticated)/orders/page.tsx.
Button composes only through render, such as <Button render={<Link href="/" />}>Home</Button>; asChild is removed. Rendered markup is unchanged.
Apply when: the storefront keeps the template's Button; convert each <Button asChild> to render.
Skip when: the storefront prefers asChild. The previous Button accepts render too, so the rest of this release works without this change.
Validate: no <Button asChild remains, and pnpm typecheck passes.
agent-eve-0-83
Action: adopt. Applies to: agent. Paths: agent/agent.ts, agent/connections/shopify.ts, agent/instructions.ts, agent/lib/catalog-middleware.ts, app/api/agent/session/route.ts, components/agent/agent-panel.tsx, lib/botid/index.ts, package.json, pnpm-lock.yaml.
Shop Agent prepares the browser cart once per session, and cart preparation returns an empty 204 without a second BotID check. The Shopify connection exposes only catalog search, with each variant's availability and options, and experimental.workflow.retention: "default" keeps conversations restorable without recording their content in traces.
Apply when: the storefront keeps the template's Shop Agent code, enabled or not. Keep the retention setting unless the storefront records trace content or wants transcripts deleted after each run.
Skip when: the storefront removed Shop Agent.
Validate: pnpm build:agent passes, and with Shop Agent on, POST /api/agent/session returns 204 with the cart cookie, and a conversation survives a reload.
agent-catalog-guidance
Action: adopt. Applies to: agent. Paths: agent/instructions.ts, agent/tools/add-to-cart.ts.
Shop Agent follows Shop's catalog guidance: it searches with the shopper's words, sends a firm price cap as a catalog filter, shows requested sizes and colors on the product cards, and confirms an available variant with those options before adding to the cart.
Apply when: the storefront keeps the template's Shop Agent code, enabled or not. Carry the rules into customized instructions, including a commerce-context line the storefront builds itself.
Skip when: the storefront removed Shop Agent.
Validate: with Shop Agent on, asking for a product with a price cap, size, and color returns results under the cap with that size and color on the cards, and adding one to the cart picks a matching available variant or asks first.
agent-thinking-label-lookup
Action: adopt. Applies to: agent. Paths: components/agent/thinking.tsx.
The Shop Agent status line shows "Thinking…" for tools without a label instead of crashing on names such as __proto__.
Apply when: the storefront uses the template's AgentThinking.
Skip when: the status line no longer looks up labels by tool name.
Validate: pnpm typecheck passes, and tool labels still show while tools run.
agent-guidance-sanitize-schemas
Action: adopt. Applies to: all. Paths: .agents/skills/build-shop/SKILL.md, .agents/skills/enable-i18n/SKILL.md, .agents/skills/enable-shopify-markets/SKILL.md, AGENTS.md.
AGENTS.md and the skills tell coding agents to render Shopify HTML only through sanitizeShopifyHtml and to parse every Server Action's arguments with a zod schema.
Apply when: the storefront keeps the template's AGENTS.md and skills.
Skip when: the storefront maintains its own agent instructions; carry the two rules into them.
Validate: AGENTS.md and build-shop state both rules.
template-doc-corrections
Action: optional. Applies to: all. Paths: .agents/skills/build-shop/references/plp-search.md, .agents/skills/enable-shopify-markets/SKILL.md, .agents/skills/enable-shopify-menus/SKILL.md, .agents/skills/shopify-graphql-reference/references/REFERENCE.md, AGENTS.md, README.md.
AGENTS.md, the README, and the skills describe the current code, including the two handleShopifyRoutes calls in the enable-shopify-markets proxy example.
Apply when: the storefront keeps the template's versions of these files.
Skip when: the storefront maintains its own.
Validate: the enable-shopify-markets proxy example matches the storefront's proxy.ts.
update-shop-releases
Action: adopt. Applies to: all. Paths: .agents/skills/update-shop/SKILL.md.
update-shop upgrades from the release in .vercel-shop/template.json one release at a time, using each release's change items and the template source at its tag, and updates template.json as it goes.
Apply when: the storefront keeps the template's update-shop.
Skip when: the storefront upgrades another way.
Validate: in audit mode on a storefront at 2026.10.0, it reports the base and target releases and lists the target's change items.