Skip to main content

Weaverse v6 & Hydrogen 2026.10

@weaverse/hydrogen 6 runs on Hydrogen 2026.10, the release where Shopify turns Hydrogen from a framework into a library of server handlers and client stores. This guide moves a Weaverse Hydrogen storefront from classic Hydrogen (2026.4 and earlier, @weaverse/hydrogen 5.x) to Hydrogen 2026.10 and @weaverse/hydrogen 6, the same way the v5 guide covered the move to React Router v7. Every sample on this page comes from Pilot, which made this move in Weaverse/pilot#209 and shipped it as v2026.11.7. Samples are trimmed; // ... marks omitted code.

What Changed and Why

Classic Hydrogen owned the request: createHydrogenContext() built the storefront client, cart, session and customer account, createRequestHandler from @shopify/hydrogen/oxygen ran React Router, the hydrogen() Vite plugin and shopify hydrogen dev drove development, and components such as CartForm, Analytics.Provider, Image and Money came from the package. Hydrogen 2026.10 is a server-handler library. Your app composes the request lifecycle itself and calls into Hydrogen where it needs Shopify: @weaverse/hydrogen 6 follows: WeaverseClient takes the 2026.10 Storefront client instead of a classic Hydrogen context, and the SDK ships one entry point per framework.

Supported Versions

Themes on Hydrogen 2026.4 or earlier stay on @weaverse/hydrogen 5.x. React Router 8 is not supported yet.
@weaverse/hydrogen 6.0.0 depends on exactly @weaverse/react 6.0.0 and @weaverse/next 1.1.0, which are released together as one version group. If your package.json also lists @weaverse/react or @weaverse/next, align them to those versions. Mismatched versions install two copies of @weaverse/react with separate React contexts, and Weaverse components stop finding their data.

Before You Begin

This is a major upgrade with breaking changes across the whole theme. Before you start:
  1. Commit everything and work on a branch.
  2. Read Shopify’s migration skill. @shopify/hydrogen 2026.10 ships one at node_modules/@shopify/hydrogen/skills/migrate-to-hydrogen-2026-10/, with one reference file per area (cart, analytics, customer account, …). Install @shopify/hydrogen@2026.10 first, or read it in a fresh Pilot checkout.
  3. Keep Pilot’s cut-over open as a reference: the PR diff and the spec at .weaverse/specs/2026-10-29-hydrogen-2026-10-cut-over.md in the Pilot repo, which lists every decision and its reason.
  4. Plan for a full regression pass. Cart, product forms, analytics and customer accounts are rewritten, not renamed.
Classic projects should pin @shopify/hydrogen to ~2026.4.x until they migrate. A ^2026.4.0 range resolves to 2026.10, whose hydrogen() Vite plugin only throws an error telling you to pin.

Migration Options

If your theme is close to Pilot, create a new project from the current Pilot and move your customizations over:
Then copy your sections and components into app/sections/ and app/components/, run the codemod from Step 2 on them, and port each Storefront API query to gql() (Step 6). Your Weaverse project, pages and theme settings are not affected: point the project’s preview URL at the new storefront.

Option 2: Upgrade in Place

Follow the steps below. They follow the order Pilot used, so the project builds again as early as possible.

Step 1: Update Dependencies, Scripts and Config

Dependencies

@shopify/cli stays a dev dependency for shopify hydrogen deploy, link and env pull. It must be 4.4.0 or later: older versions, including every 3.x release, don’t support the --assets-dir and --worker-dir flags the new deploy script uses. Pilot uses ^4.9.4.

Scripts

Before (classic):
After (Hydrogen 2026.10):
The hydrogen binary ships with @shopify/hydrogen. dev:https runs the same command; vite.config.ts reads npm_lifecycle_event to turn on local HTTPS, which Customer Account login needs.

vite.config.ts

react-router.config.ts

Hydrogen 2026.10 has no React Router preset. Replace hydrogenPreset() with the values it applied:

tsconfig.json and .gitignore

Drop the generated-types paths and @shopify/hydrogen/react-router-types, and add the GraphQL TypeScript plugin:

Step 2: Run the Weaverse Codemod

@weaverse/cli 5.9.0 ships a codemod for @weaverse/hydrogen 6. Preview first:
--dry-run lists the files it would change without writing them. Then apply it and review the diff:
Use --dir to run it on another project folder. The codemod only touches files that import @weaverse/hydrogen or @weaverse/next, and does two things:
  • Moves imports to the new entry points: @weaverse/hydrogen → @weaverse/hydrogen/react-router, @weaverse/next → @weaverse/hydrogen/next, @weaverse/next/server → @weaverse/hydrogen/next/server.
  • Renames types listed in the table below.
The renames are plain find-and-replace on whole words across each file that imports Weaverse. A local type with the same name, such as your own ThemeSettingsResponse, is renamed too. Review the diff before you commit.

@weaverse/hydrogen 6 API Changes

Entry points. Hydrogen and React Router themes import everything from @weaverse/hydrogen/react-router, which re-exports the other public entries: Renamed types (applied by the codemod): Removed from the public entries (fix these by hand):
  • useThemeText, ThemeTextProvider and ThemeTextStore, the deprecated aliases: use useTranslation, TranslationProvider and TranslationStore.
  • interpolate, createWeaverseDataContext, useWeaverseDataContext and WeaverseArticle (WeaverseArticle had the same resource-picker shape as WeaverseBlog).
  • CachingStrategy: import it from @shopify/hydrogen.
Behavior changes:
  • WeaverseClient takes the request’s Storefront client plus env, cache and waitUntil instead of a spread classic Hydrogen context (Step 5).
  • Section loaders query Shopify with weaverse.storefront.graphql(); there is no query() shim.
  • Env extends the SDK’s WeaverseEnv instead of Hydrogen’s removed HydrogenEnv.
  • getSelectedProductOptions(request) keeps its signature and still drops Weaverse’s Studio query params.
  • @shopify/hydrogen >=2026.10.0 is now a peer dependency.
  • Weaverse content keeps its caching: 60 seconds fresh, then up to a day stale while it revalidates or when the API fails.

Step 3: Own the Request Lifecycle in the Server Entry

Classic server.ts built a Hydrogen context and handed the request to Hydrogen’s handler. In 2026.10 it composes the lifecycle itself. Before (classic):
After (Hydrogen 2026.10):
Notes:
  • handleShopifyRoutes decides synchronously: it returns a Promise<Response> for a path a handler owns, otherwise null. Check it without await.
  • Handlers are matched in order, so app handlers that wrap a library handler (Pilot’s POST /api/cart syncs the cart’s market, then calls cartHandlers.post) go first.
  • routeTemplates comes from createShopifyRouteTemplates() and maps Shopify resources to your routes, for ?variant= links, URL redirects and ShopifyScripts. Omitting it, or applyResponseHeaders, fails silently.
  • Pilot’s real server.ts also skips the redirect lookup for unsupported markets, makes the lookup market-aware (Markets), and leaves cart permalinks without a payment param to its own /cart/:lines route.

Step 4: Build the App Context Yourself

createHydrogenContext() is gone. Pilot replaces createHydrogenRouterContext() with createAppContext() in app/.server/context.ts, which builds the same values and returns a plain object that server.ts copies onto React Router’s context:

The Storefront Client

createRequestStorefront() (Pilot’s app/.server/storefront.ts) builds a request-scoped client. It uses the private token when Oxygen injects one, and the public token otherwise:
getRequestI18n and getBuyerIp are app code. Pilot resolves the market from the URL prefix, or from the Referer for unprefixed handler paths (Markets).

The Session

The session is now any object that implements Hydrogen’s WritableCustomerSessionManager (getSessionOrigin, getSessionItem, setSessionItem, removeSessionItem, plus an optional commit() returning cookie headers). Pilot’s AppSession in app/.server/session.ts wraps React Router’s createCookieSessionStorage and only commits when something changed, so anonymous pages never get a Set-Cookie that would make them uncacheable. An in-memory session works for carts but cannot complete Customer Account login, which spans two requests.

Types: env.d.ts

Before (classic):
After (Hydrogen 2026.10):
HydrogenEnv no longer exists, so list every variable your theme reads.

Step 5: Create the WeaverseClient From the Storefront Client

Before (v5):
After (v6):
projectId, fetchTimeoutMs and throwPageLoadFailures work as before. Weaverse reads storefront.i18n (language, country, pathPrefix) for the content locale and passes pathPrefix to Studio. Root withWeaverse(Layout), WeaverseHydrogenRoot, useThemeSettings and loadPage() / loadThemeSettings() don’t change beyond the import path.

Step 6: Move Queries to the New GraphQL Client

GraphQL codegen is replaced by gql(): TypeScript infers result and variable types from Hydrogen’s bundled Storefront API schema, and hydrogen gql check validates every document. Before (classic):
After (Hydrogen 2026.10):
What changes at every call site:
  • $country and $language are injected from the request context. Drop them from variables.
  • graphql() returns { data, errors, headers } and does not throw on GraphQL errors. Pilot keeps the throwing behavior with a small helper:
  • There is no default cache. Pass cache on every query, or it hits the API on every request. Pilot maps the classic strategies in app/utils/cache.ts:
    generateCacheControlHeader is gone too; Pilot builds Cache-Control values from these strategies with a local cacheControlHeader().

Section Loaders

Section loaders keep { data, weaverse }; weaverse.storefront is the 2026.10 client. Before (v5):
After (v6):
gql() results are typed precisely, including nullable and union fields such as nodes(ids:). Narrow them where you read them instead of casting to any. Customer Account documents use gql from @shopify/hydrogen/customer-account, with InferResult<SourceOf<typeof DOC>> for their result types.

Step 7: Update app/routes.ts

  • Remove hydrogenRoutes(). It no longer exists; export the route array directly.
  • Delete routes Hydrogen now owns. In Pilot these were routes/api/cart.ts (the library serves /api/cart), routes/account/auth/login.ts, authorize.ts and logout.ts (the Customer Account handlers), routes/api/predictive-search.ts and routes/api/graphql.json.ts.
  • Wrap the :locale? routes in the market guard layouts described in Markets.

Step 8: Move the Cart to Hydrogen’s Cart Store

context.cart, CartForm and the /cart action are replaced by server handlers at /api/cart and a client store. Server. Create the handlers once, extending the default cart fields with your fragment, and register them in handleShopifyRoutes (Step 3):
Client. Bind the store to your handlers’ type and render the provider once in app/root.tsx:
Pilot renders CartProvider without server initialData. The cart loads from /api/cart after hydration, so the HTML stays anonymous and Oxygen can full-page cache it.
Forms. Cart forms post to /api/cart, so they work without JavaScript; once hydrated, the store applies the change optimistically. Before (classic):
After (Hydrogen 2026.10):
Read state with selectors, for example useCart((state) => state.data), useCart((state) => state.pending.lines.has(line.id)) and useCart((state) => state.errors). After an app-owned change (a gift card, a market switch), call refresh() from useCartActions(). What Pilot keeps as app code, because the library doesn’t cover it yet:
  • POST /api/cart, registered before cartHandlers: syncs the cart’s country and the signed-in customer’s access token, drops a cart cookie whose cart no longer exists, then delegates to cartHandlers.post.
  • POST /api/cart/buyer-identity for the country selector and POST /api/cart/gift-cards for gift cards, built with createShopifyRouteHandler.
  • /cart/:lines permalinks, /discount/:code and /cart/checkout with attribution, as routes.
See app/.server/cart.ts in Pilot for these handlers. Note that CartCost.totalDutyAmount and totalTaxAmount are no longer populated, and the order discount total comes from lines.discountAllocations(lineLevelOnly: false) rather than the deprecated Cart.discountAllocations.

Step 9: Rebuild Product Forms and Variant Selection

useOptimisticVariant, getAdjacentAndFirstAvailableVariants and getProductOptions are replaced by Hydrogen’s product form store. Keep getSelectedProductOptions(request) from @weaverse/hydrogen/react-router in the loader: it now wraps Hydrogen 2026.10’s version and still filters out Studio params.
The product query must select encodedVariantExistence, encodedVariantAvailability, options, selectedOrFirstAvailableVariant and adjacentVariants, as Pilot’s PRODUCT_QUERY does. Before (classic):
After (Hydrogen 2026.10):
productOptionPath is Pilot’s wrapper around Hydrogen’s buildProductSelectionSearchParams. Pilot’s product route also remounts the provider when a link returns to the variant the page loaded; see app/routes/products/product.tsx. Every section under the provider reads the same selection. Option pickers and the add-to-cart button use useProduct() and useProductForm():
canAddToCart comes from @shopify/hydrogen; openCartDrawer is your own UI. Quick shop and single-product sections render their own ProductProvider, inside the root CartProvider.

Step 10: Replace Analytics.Provider With ShopifyScripts

Analytics.* and getShopAnalytics are gone. ShopifyScripts from @shopify/hydrogen/react loads Shopify’s runtime: window.Shopify, the analytics bus at window.Shopify.analytics, the consent banner, Shopify analytics and Standard Actions. Before (classic):
After (Hydrogen 2026.10):
  • Build shop and i18n in the root loader from env and storefront.i18n. Keep them per market, never per shopper, so the document stays cacheable.
  • Route events are published to the bus. Pilot’s app/components/root/analytics-views.tsx has small trackers (PageView, ProductView, CollectionView, SearchView, CartView) that call window.Shopify.analytics.publish(AnalyticsEvent.PRODUCT_VIEWED, { products }) once per URL.
  • useCartAnalytics() from @shopify/hydrogen/react publishes cart_updated, product_added_to_cart and product_removed_from_cart from the cart store. Render it inside CartProvider, after ShopifyScripts.
  • Third-party tags subscribe through a destination instead of useAnalytics().subscribe, so they follow Customer Privacy consent:
    Add a destination once per page: adding it again replays the bus’s retained events.
  • ShopifyScripts reads the market once. After a client-side market switch, Pilot’s syncShopifyMarket() updates window.Shopify (country, currency, locale, routes root).

Step 11: Move Customer Accounts to Hydrogen’s Handlers

context.customerAccount.login(), authorize() and logout() in your routes are replaced by handlers that own the fixed, unprefixed paths /account/login, /account/authorize, /account/refresh and /account/logout. Before (classic):
After (Hydrogen 2026.10):
Account loaders then call the Customer Account API with the session’s token:
Pilot wraps this in context.customerAccount (graphql, isLoggedIn, getAccessToken, handleAuthStatus, logout), so its account routes keep reading context.customerAccount. Without a token, the wrapper throws a redirectDocument to /account/login?return_to=<localized path>&locale=<language>, so the shopper returns to their market after signing in.
  • Link to the handler paths with a plain <a> or <form>, never React Router’s <Link> or <Form>: the handlers answer with a raw redirect that client-side routing cannot follow.
  • Customer Account OAuth needs HTTPS locally: run npm run dev:https.
  • createCustomerSession() throws when SHOP_ID or the client ID is missing or invalid. Pilot only creates it when both are set, so a store without accounts keeps serving pages.
  • Register every market’s home URL as an allowed logout URI in the Customer Account API settings.
  • Addresses use countryCode.

Step 12: Port Removed Classic Helpers

These classic exports are gone. Pilot ports them as local files with unchanged output, so you can copy them: Most ports are a find-and-replace of the import path:
flattenConnection is still exported by @shopify/hydrogen.

Step 13: Verify

Then test in a browser with the console open. Check for CSP violations, cart add/remove/quantity, variant selection without a full navigation, analytics events, and Customer Account login over npm run dev:https. Finally, open the storefront in Weaverse Studio and check design mode, section loaders and theme settings.

Markets

Pilot keeps one list of markets, SUPPORTED_LOCALES in app/utils/locale.ts, and serves them as URL prefixes (/fr-fr/products/...). Three parts change on 2026.10.

Locale-Prefixed Routes

Routes still sit under prefix(":locale?", [...]). The request’s market becomes the i18n of createShopifyRequestContext(), and storefront.graphql() fills $country and $language from it. Hydrogen’s handler paths (/api/cart, /api/predictive-search, /account/login, …) are exact and never carry a prefix. Pilot resolves their market from the same-origin Referer instead (and from return_to for /account/login), falling back to the default market. Shopify’s URL redirects are stored on market-neutral paths, and handleShopifyRedirects only matches document paths. Pilot’s marketAwareRedirect() (app/.server/market-redirect.ts) looks redirects up on the neutral path, re-prefixes the target, and answers single-fetch .data requests with 204 and X-Remix-Redirect, as classic storefrontRedirect did.
Chinese markets need two language codes: Shopify wants its LanguageCode (ZH_TW), while Weaverse keys translations by the public locale (zh-tw). Pilot gives the Storefront client the Shopify code and hands WeaverseClient a copy of the client whose i18n carries the public one (weaverseStorefront() in app/.server/context.ts). graphql() reads $language from the request context, so Shopify still receives ZH_TW.

Unsupported Markets Answer 404

A market-shaped prefix the store doesn’t sell in (/en-xx/products/hoodie) must not render the default market’s catalogue at an unlimited number of URLs. Pilot uses two layout routes, because page routes and resource routes run differently:
  • Pages throw from a layout loader below the root route. The root loader’s data survives, so the root ErrorBoundary renders the regular 404 page inside the header and footer, for documents and .data navigations alike. A 404 from server.ts would have no header or footer.
  • Resource routes (APIs, sitemaps, /discount/:code, /cart/checkout) need the second layout. A resource-route request runs only that route’s loader, never the page guard’s, so the check runs as middleware, which React Router calls for every matched route. It needs future.v8_middleware: true.
server.ts also skips the redirect lookup for unsupported market paths, which would otherwise fall back to the default market.

Switching the Cart’s Market

A cart keeps the country it was created in. If the shopper switches market, the cart keeps the old market’s prices and taxes until its buyer identity changes. Pilot handles this in two places:
  • Its POST /api/cart handler calls cartBuyerIdentityUpdate with the request’s country (and the customer’s access token) before every cart change.
  • The country selector posts the new country to /api/cart/buyer-identity while it navigates, then refreshes the store and window.Shopify:
    Without JavaScript, the same form posts to /api/cart/buyer-identity, which updates the cart and redirects.

Common Pitfalls

The renames match whole words in every file that imports Weaverse. A local ThemeSettingsResponse becomes WeaverseThemeSettingsResponse too. Run with --dry-run first and review git diff after applying.
Two copies of @weaverse/react are installed. Keep @weaverse/hydrogen 6.0.0, @weaverse/react 6.0.0 and @weaverse/next 1.1.0 aligned, and check with npm ls @weaverse/react.
Hydrogen 2026.10 has no default cache. Pass cache to every storefront.graphql() call.
graphql() returns errors instead of throwing. Check errors or unwrap the result as in Step 6.
requestContext.applyResponseHeaders() must run on the normal response path, and routeTemplates must reach both handleShopifyRoutes and handleShopifyRedirects. Both fail silently.
The private client needs the buyer IP on the request context. Read it from oxygen-buyer-ip (or your proxy’s header), or fall back to the public client as Pilot does.
With @inContext(country:), Shopify silently falls back to the store’s primary country when the store doesn’t sell to that country, while a cart whose buyerIdentity.countryCode is that country prices in its currency. This is store configuration, not a code bug: add the country to a Market in Shopify admin.
In @shopify/hydrogen 2026.10.2, the ./customer-account export has no development condition, and passing customerSession to createCartServerHandlers breaks under vite dev. Pilot attaches customerAccessToken in its own POST /api/cart handler instead.
If a script loads your component registry or manifest through Vite’s ssrLoadModule, packages that publish ESM with extensionless relative imports (such as react-use/esm/*) fail under Node’s ESM resolver. Add them to ssr.noExternal in that Vite server config, as Pilot’s other/weaverse-manifest.mjs does with noExternal: [/^@weaverse\//, "react-use"].
The nonce was dropped while porting CSP. Pass the nonce to ShopifyScripts, Scripts and ScrollRestoration, and check the console for CSP violations. See Content Security Policy.
Something personalized reached the document: a cart passed as CartProvider initialData, a customer token in a loader, or a session committed on every response. Load the cart after hydration and commit the session only when it changed.

Migration Checklist

  • @shopify/hydrogen ^2026.10, @weaverse/hydrogen ^6, React Router 7.18+ on one version, Vite 8
  • Scripts: vite dev, dev:https, react-router build, vite preview, shopify hydrogen deploy
  • vite.config.ts without hydrogen(); react-router.config.ts with buildDirectory: "dist" and v8_middleware
  • GraphQL codegen removed; @shopify/hydrogen/ts-plugin and hydrogen gql check in place
  • npx @weaverse/cli@latest migrate v6 applied and its diff reviewed
  • server.ts composes the request lifecycle: handleShopifyRoutes, session commit, applyResponseHeaders, handleShopifyRedirects
  • WeaverseClient built from the request’s Storefront client
  • Every query is a gql() document run with storefront.graphql(), checks errors, and passes cache
  • hydrogenRoutes() removed, classic cart and auth routes deleted, market guard layouts added
  • Cart on createCartServerHandlers and createCartComponents; no CartForm
  • Product pages on ProductProvider and useProductForm; no useOptimisticVariant
  • ShopifyScripts, route trackers and useCartAnalytics in place of Analytics.*
  • Customer Account handlers registered; login over dev:https works
  • Image, Money, getSeoMeta, sitemaps, Pagination and CSP ported
  • npm run typecheck and npm run build pass; Studio preview, design mode and section loaders work

Reference Implementation

Weaverse/pilot
Loading repository data...
  • Weaverse/pilot#209: the full cut-over, one area per commit
  • Pilot v2026.11.7: the first release on Hydrogen 2026.10 and @weaverse/hydrogen 6.0
  • .weaverse/specs/2026-10-29-hydrogen-2026-10-cut-over.md in the Pilot repo: scope, decisions and acceptance checklist

Getting Help

  1. Compare with Pilot: most questions are answered by the matching file in Pilot main.
  2. Read Shopify’s packaged skills in node_modules/@shopify/hydrogen/skills/.
  3. Community Support: join our Slack Community.
  4. Official Support: contact support@weaverse.io.

Additional Resources

Last modified on November 9, 2026