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
@weaverse/hydrogen 5.x. React Router 8 is not supported yet.
Before You Begin
This is a major upgrade with breaking changes across the whole theme. Before you start:- Commit everything and work on a branch.
- Read Shopify’s migration skill.
@shopify/hydrogen2026.10 ships one atnode_modules/@shopify/hydrogen/skills/migrate-to-hydrogen-2026-10/, with one reference file per area (cart, analytics, customer account, …). Install@shopify/hydrogen@2026.10first, or read it in a fresh Pilot checkout. - 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.mdin the Pilot repo, which lists every decision and its reason. - Plan for a full regression pass. Cart, product forms, analytics and customer accounts are rewritten, not renamed.
Migration Options
Option 1: Start From the Latest Pilot (Recommended)
If your theme is close to Pilot, create a new project from the current Pilot and move your customizations over: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):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:
--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.
@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:
useThemeText,ThemeTextProviderandThemeTextStore, the deprecated aliases: useuseTranslation,TranslationProviderandTranslationStore.interpolate,createWeaverseDataContext,useWeaverseDataContextandWeaverseArticle(WeaverseArticlehad the same resource-picker shape asWeaverseBlog).CachingStrategy: import it from@shopify/hydrogen.
WeaverseClienttakes the request’s Storefront client plusenv,cacheandwaitUntilinstead of a spread classic Hydrogen context (Step 5).- Section loaders query Shopify with
weaverse.storefront.graphql(); there is noquery()shim. Envextends the SDK’sWeaverseEnvinstead of Hydrogen’s removedHydrogenEnv.getSelectedProductOptions(request)keeps its signature and still drops Weaverse’s Studio query params.@shopify/hydrogen>=2026.10.0is 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
Classicserver.ts built a Hydrogen context and handed the request to Hydrogen’s handler. In 2026.10 it composes the lifecycle itself.
Before (classic):
handleShopifyRoutesdecides synchronously: it returns aPromise<Response>for a path a handler owns, otherwisenull. Check it withoutawait.- Handlers are matched in order, so app handlers that wrap a library handler (Pilot’s
POST /api/cartsyncs the cart’s market, then callscartHandlers.post) go first. routeTemplatescomes fromcreateShopifyRouteTemplates()and maps Shopify resources to your routes, for?variant=links, URL redirects andShopifyScripts. Omitting it, orapplyResponseHeaders, fails silently.- Pilot’s real
server.tsalso skips the redirect lookup for unsupported markets, makes the lookup market-aware (Markets), and leaves cart permalinks without apaymentparam to its own/cart/:linesroute.
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’sWritableCustomerSessionManager (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):
HydrogenEnv no longer exists, so list every variable your theme reads.
Step 5: Create the WeaverseClient From the Storefront Client
Before (v5):
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 bygql(): TypeScript infers result and variable types from Hydrogen’s bundled Storefront API schema, and hydrogen gql check validates every document.
Before (classic):
-
$countryand$languageare injected from the request context. Drop them fromvariables. -
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
cacheon every query, or it hits the API on every request. Pilot maps the classic strategies inapp/utils/cache.ts:generateCacheControlHeaderis gone too; Pilot buildsCache-Controlvalues from these strategies with a localcacheControlHeader().
Section Loaders
Section loaders keep{ data, weaverse }; weaverse.storefront is the 2026.10 client.
Before (v5):
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.tsandlogout.ts(the Customer Account handlers),routes/api/predictive-search.tsandroutes/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):
app/root.tsx:
/api/cart, so they work without JavaScript; once hydrated, the store applies the change optimistically.
Before (classic):
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 beforecartHandlers: syncs the cart’s country and the signed-in customer’s access token, drops acartcookie whose cart no longer exists, then delegates tocartHandlers.post.POST /api/cart/buyer-identityfor the country selector andPOST /api/cart/gift-cardsfor gift cards, built withcreateShopifyRouteHandler./cart/:linespermalinks,/discount/:codeand/cart/checkoutwith attribution, as routes.
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.
encodedVariantExistence, encodedVariantAvailability, options, selectedOrFirstAvailableVariant and adjacentVariants, as Pilot’s PRODUCT_QUERY does.
Before (classic):
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):
-
Build
shopandi18nin the root loader fromenvandstorefront.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.tsxhas small trackers (PageView,ProductView,CollectionView,SearchView,CartView) that callwindow.Shopify.analytics.publish(AnalyticsEvent.PRODUCT_VIEWED, { products })once per URL. -
useCartAnalytics()from@shopify/hydrogen/reactpublishescart_updated,product_added_to_cartandproduct_removed_from_cartfrom the cart store. Render it insideCartProvider, afterShopifyScripts. -
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. -
ShopifyScriptsreads the market once. After a client-side market switch, Pilot’ssyncShopifyMarket()updateswindow.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):
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 whenSHOP_IDor 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:flattenConnection is still exported by @shopify/hydrogen.
Step 13: Verify
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 underprefix(":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.
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
ErrorBoundaryrenders the regular 404 page inside the header and footer, for documents and.datanavigations alike. A 404 fromserver.tswould 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 needsfuture.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/carthandler callscartBuyerIdentityUpdatewith 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-identitywhile it navigates, then refreshes the store andwindow.Shopify:Without JavaScript, the same form posts to/api/cart/buyer-identity, which updates the cart and redirects.
Common Pitfalls
The codemod renamed one of my own types
The codemod renamed one of my own types
ThemeSettingsResponse becomes WeaverseThemeSettingsResponse too. Run with --dry-run first and review git diff after applying.Weaverse components lose their data after the upgrade
Weaverse components lose their data after the upgrade
@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.Pages hit the Storefront API on every request
Pages hit the Storefront API on every request
cache to every storefront.graphql() call.A failed query renders a half-empty page
A failed query renders a half-empty page
graphql() returns errors instead of throwing. Check errors or unwrap the result as in Step 6.Shopify headers or redirect targets are missing
Shopify headers or redirect targets are missing
requestContext.applyResponseHeaders() must run on the normal response path, and routeTemplates must reach both handleShopifyRoutes and handleShopifyRedirects. Both fail silently.Error: requestContext.buyerIp is required for private Storefront API clients
Error: requestContext.buyerIp is required for private Storefront API clients
oxygen-buyer-ip (or your proxy’s header), or fall back to the public client as Pilot does.Login links land on the account catch-all route
Login links land on the account catch-all route
/account/login and the other handler paths must be reached with a full document request. Use <a>/<form>, and redirectDocument when redirecting from a loader.The cart shows old totals after a /discount/:code link
The cart shows old totals after a /discount/:code link
/discount/:code loader must end with redirectDocument (not redirect), so a client-side link to it reloads the page and the store.Products are priced in the store's primary currency in one market
Products are priced in the store's primary currency in one market
@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.Customer access tokens on carts break vite dev
Customer access tokens on carts break vite dev
@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.Loading components through Vite ssrLoadModule fails on extensionless imports
Loading components through Vite ssrLoadModule fails on extensionless imports
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"].Hydration is silently dead (cart, variant picker, analytics)
Hydration is silently dead (cart, variant picker, analytics)
ShopifyScripts, Scripts and ScrollRestoration, and check the console for CSP violations. See Content Security Policy.Anonymous pages are no longer cached
Anonymous pages are no longer cached
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.tswithouthydrogen();react-router.config.tswithbuildDirectory: "dist"andv8_middleware- GraphQL codegen removed;
@shopify/hydrogen/ts-pluginandhydrogen gql checkin place npx @weaverse/cli@latest migrate v6applied and its diff reviewedserver.tscomposes the request lifecycle:handleShopifyRoutes, session commit,applyResponseHeaders,handleShopifyRedirectsWeaverseClientbuilt from the request’s Storefront client- Every query is a
gql()document run withstorefront.graphql(), checkserrors, and passescache hydrogenRoutes()removed, classic cart and auth routes deleted, market guard layouts added- Cart on
createCartServerHandlersandcreateCartComponents; noCartForm - Product pages on
ProductProvideranduseProductForm; nouseOptimisticVariant ShopifyScripts, route trackers anduseCartAnalyticsin place ofAnalytics.*- Customer Account handlers registered; login over
dev:httpsworks Image,Money,getSeoMeta, sitemaps,Paginationand CSP portednpm run typecheckandnpm run buildpass; Studio preview, design mode and section loaders work
Reference Implementation
- Weaverse/pilot#209: the full cut-over, one area per commit
- Pilot v2026.11.7: the first release on Hydrogen 2026.10 and
@weaverse/hydrogen6.0 .weaverse/specs/2026-10-29-hydrogen-2026-10-cut-over.mdin the Pilot repo: scope, decisions and acceptance checklist
Getting Help
- Compare with Pilot: most questions are answered by the matching file in Pilot
main. - Read Shopify’s packaged skills in
node_modules/@shopify/hydrogen/skills/. - Community Support: join our Slack Community.
- Official Support: contact support@weaverse.io.