Migrate to Hydrogen 3 (storefront-kit working version, Remix → React Router 7) - #154
Draft
lucyxiang wants to merge 6 commits into
Draft
Migrate to Hydrogen 3 (storefront-kit working version, Remix → React Router 7)#154lucyxiang wants to merge 6 commits into
lucyxiang wants to merge 6 commits into
Conversation
Migrate the demo store from Remix to React Router 7.16 on the Hydrogen 3
preview (0.0.0-preview-0c3bff8-20260618001533): fs-routes, middleware-based
storefront/customer-account context, and Oxygen via @shopify/mini-oxygen.
Dev-enablement fixes:
- vite.config.ts: inject .env into the Oxygen worker via
oxygen({env: loadEnv(...)}) and pre-bundle react/react-dom for SSR
(the preview build ships no hydrogen() Vite plugin / no /vite export)
- .nvmrc: v18 → v22.18.0 (React Router 7 requires Node >20)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Moves off the npm preview build (0.0.0-preview) to the storefront-kit working version of Hydrogen 3, vendored as @shopify/hydrogen@0.0.1 (vendor/shopify-hydrogen-0.0.1.tgz, file: dependency). - package.json: shopify hydrogen dev|build restored; @shopify/hydrogen-classic (npm:@shopify/hydrogen@2026.4.2) supplies the hydrogen() vite plugin; react-router/@react-router/* 7.15.1, @shopify/cli 3.94.3, mini-oxygen 4.1.0. - vite.config.ts: plugins [hydrogen(), oxygen(), reactRouter()] with hydrogen from @shopify/hydrogen-classic/vite; CLI injects env + worker entry. - storefront.middleware.ts: server-handler wiring — createShopifyRequestContext, createStorefrontClient (private), handleShopifyRoutes with cartHandlers, handleShopifyRedirects, customer-account client + session commit. - customer-account.server.ts: compat wrapper over @shopify/hydrogen/customer-account (prepareLoginUrl/handleOAuthCallback/logout) replacing the hand-rolled OAuth URL that produced the /account/login 400. - customer-session.ts: vendored EncryptedCookieCustomerSession. - .npmrc: legacy-peer-deps=true (hydrogen-classic peers exact @react-router/dev); install with npm, not pnpm (pnpm symlinks break CLI project detection). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.env is listed in .gitignore but was force-added upstream long ago, so it stayed tracked. Untrack it on this branch so real tokens can't be committed by accident. Local .env stays on disk for dev. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The library useCartForm submits through its own cart store (store.handleFormSubmit + preventDefault), so React Router's useFetchers never sees the request and the old useCartFetchers drawer trigger never fired. Rewrite AddToCartButton to use useCartForm and drive the drawer from the cart store. Open the drawer on a mutation signal (useCart((s) => s.pending.lines.size > 0)) rather than absolute totalQuantity, which jumps 0 -> N on hydration and auto-opened the drawer on every page load. Match the quantity control to production: strip the interactive input's native field chrome (appearance-none, border-0, no focus ring) and tighten width so the -/+ layout matches the H2 store. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Fix gql.tada schema config: pointed at a nonexistent src/graphql/... path so typed GraphQL was silently disabled. Use the packaged dist/*.schema.json under named storefront + customer-account schemas, and run gql.tada check in typecheck. - Make the skip-to-content link visible on keyboard focus (was permanently sr-only) and focus #mainContent on route change. - Give the cart trigger an accessible name + aria-haspopup. - Add dev:customer script (shopify hydrogen dev --customer-account-push) so Customer Account OAuth works locally over the CLI tunnel. - Remove orphaned useCartFetchers hook (dead after the cart-drawer fix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lucyxiang
force-pushed
the
migrate-hydrogen-3-preview
branch
from
July 1, 2026 04:36
9593663 to
8c50e67
Compare
…d of link navigation
Same-product option values now render as buttons wired to the library's
useProductForm register('optionValue') activation, updating the selected
variant immediately. Only cross-product values (different handle) keep
<Link> navigation. Addresses the laggy variant-change feedback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Migrates the demo store from Remix to React Router 7 on Hydrogen 3, now targeting the storefront-kit working version Hydrogen 3 is consumed as a server-handler library you compose, not a framework that owns the request.
@shopify/hydrogen@0.0.1is unpublished (storefront-kit workspace pkg), so it is packed tovendor/shopify-hydrogen-0.0.1.tgzand installed via afile:dependency.shopify hydrogen dev|buildwork again. Thehydrogen()Vite plugin now ships in@shopify/hydrogen-classic/vite(@shopify/hydrogen-classic=npm:@shopify/hydrogen@2026.4.2); the CLI injects.envinto MiniOxygen and serves/graphiql+/subrequest-profiler.app/storefront.middleware.ts):createShopifyRequestContext→createStorefrontClient(private) →handleShopifyRouteswithcartHandlers→handleShopifyRedirectson 404, plus the customer-account client and session commit.app/lib/customer-account.server.ts): a compat wrapper over@shopify/hydrogen/customer-account(createCustomerSession→prepareLoginUrl/handleOAuthCallback/logout), replacing the hand-rolled OAuth URL.react-router/@react-router/*7.15.1,@shopify/cli3.94.3,@shopify/mini-oxygen4.1.0,vite^8, React 18.3.1.What the initial (preview) migration did NOT get right vs production
The first pass (npm
0.0.0-previewbuild) shipped several regressions against prod (hydrogen.shop). Status after this re-migration:/account/loginreturned 400…/auth/oauth/authorizeinstead of…/oauth/authorize. (Theclient_idwas correct all along — the rawPUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_IDUUID, not ashp_value; that earlier theory was wrong.)@shopify/hydrogen/customer-account. Verified the constructed URL (path,redirect_uri,scope, PKCE). Local login requires an HTTPS tunnel (library refuses http origins by design); works on prod https.useCartForm(), which submits through its own cart store (store.handleFormSubmit+preventDefault), so React Router'suseFetchers()never sees the request — the porteduseCartFetchersdrawer trigger could never fire. The quantity field also rendered the interactive<input>'s native chrome.AddToCartButtonrewritten onuseCartForm(); the drawer now opens off the cart store viauseCart((s) => s.pending.lines.size > 0)(a mutation signal, not absolutetotalQuantity, which would auto-open on every page load). Quantity control restyled to match prod./api/cartverified:POST intent=add→ 303 +CartCreate+cartcookie;GETreturnstotalQuantity:1./collections/$handlepagination: "Next" button vs prod's "Load more" infinite-append<Pagination>+getPaginationVariables(confirmed still absent in 0.0.1); needs hand-rolled accumulate +IntersectionObserver/collections/freestyledev renders 12 products + "Next" (height 1926px) vs prod's full grid + "Load more products" (height 3684px)./products(=/collections/all) matches. This is the one remaining browse regression.defer/<Await>) featured-data loader appeared to hang after the RR7 migration/notfoundnow renders the Featured Products swimlane pixel-identical to prod (heights match, 1451px). The deferred<Await>path works on this build.Install notes
@shopify/hydrogeninto.pnpm/, which defeats the Shopify CLI's realpath-based Hydrogen-project detection ("…outside of a Hydrogen project"). npm's flat install keeps it a real dir under the project root.legacy-peer-deps=truein.npmrc—@shopify/hydrogen-classicpeers an exact@react-router/devthat differs from the app's pin.Functional parity vs the previous store
This is a like-for-like port, not a feature addition — there is no net-new user-facing functionality:
Image,Money,MediaFile,Pagination,flatten-connection,csp/nonce,analytics,customer-account.server, …) are local reimplementations of things@shopify/hydrogenused to provide, because Hydrogen 3 is a slim server-handler library rather than a batteries-included framework. Pagination is currently a regression (see item 3 above), not a new feature.Post-migration improvements (this PR)
Small, functionality-preserving polish surfaced by comparing against the storefront-kit
examples/react-routertemplate:tsconfigpointed thegql.tadaplugin at a nonexistentsrc/graphql/…schema path. Repointed to the packageddist/*.schema.jsonunder namedstorefront+customer-accountschemas, and addedgql.tada checktotypecheckso query/field type errors fail the build.sr-only(unusable by keyboard) — now reveals on focus;#mainContentis focused on route change; the cart trigger got an accessible name +aria-haspopup.dev:customer(shopify hydrogen dev --customer-account-push) so the Customer Account OAuth flow works locally over the CLI tunnel (satisfies the HTTPS-origin requirement).useCartFetchershook (unused after the cart-drawer fix).Notes
.envis not committed with private tokens (shpat_/PRIVATE_STOREFRONT_API_TOKENstay local; onlyPUBLIC_*+SESSION_SECRET+SHOP_IDare tracked, pre-existing)./collections/$handleload-more pagination (item 3); draft until that reaches parity.🤖 Generated with Claude Code