Going Headless: The Three Months We Wished We'd Planned Better
A build-in-public Hydrogen case study. What we underestimated month by month, what we got right, and the four-point pre-engagement checklist we now insist on before any headless Shopify SOW.
The phone call came on day nineteen of a three-month Hydrogen rebuild. The client's head of ecommerce was direct. The merchandising team had been editing product copy in Shopify admin all week, assuming the changes would land on the new site at launch, and they had just realized that our migration snapshot was taken on day one and everything since had drifted. That conversation was the moment we understood how badly we had scoped the data layer.
This is the full story of that build. It covers what we underestimated, what we got right, and the four pre-engagement checks we now require before we sign a statement of work for any headless project. If you are weighing up Hydrogen or a custom front-end against staying on the monolith, this is the honest version of a timeline that the vendor marketing pages do not show you.
The client and the brief
The client was a mid-sized DTC apparel brand on Shopify Plus, roughly eight million in annual GMV across the United Kingdom and the United States. They had editorial guides living awkwardly inside a Liquid theme and a product catalogue with deep metafield usage for fit notes, material details, care instructions, and supplier provenance. Core Web Vitals on mobile were not passing. The marketing team wanted to ship landing pages that interleaved product grids with editorial content without filing a developer ticket for every change.
The brief was specific. Move to Hydrogen on Oxygen. Keep Shopify checkout. Move content into Sanity. Preserve SEO rankings. Launch in twelve weeks. We knew going in that the quote was optimistic. We did not know how optimistic until we started writing the content importer.
Month one, where metafields ate our plan
The data layer is where every headless Shopify project either builds its foundation or loses a month. On this one we lost a month. The plan said five working days for metafield to Sanity schema mapping. It took fourteen calendar days, and that lesson is the most valuable thing we took out of the project.
The surface problem is that Shopify metafields are flat typed scalars. Sanity schemas are nested object graphs with references, arrays, portable text, and computed derived fields. A naive importer reads a metafield, decides a target type, and writes a document. A working importer handles the edge cases that show up the moment you try to migrate a real catalogue.
The first edge case was metaobjects with optional fields. The merchant had a fit note metaobject with four required and two optional fields. Several hundred variants had empty optional fields, and Sanity rejected those documents on validation. We ended up with conditional validation rules that accepted legacy empty fields while requiring them on any document created after the cutover.
The second edge case was rich text. Shopify's rich text metafield is a JSON blob that looks superficially like a Prosemirror document. Sanity's portable text is a different block content schema with its own mark definitions. No off-the-shelf converter handles inline product references, the difference between a Shopify link mark and a Sanity link annotation, or nested lists that carry marks on specific spans. We wrote the converter ourselves. It took four days and broke twice in QA.
The third edge case was file references. Shopify file_reference metafields return CDN URLs. Moving those assets into Sanity meant reuploading, which breaks any existing embed still reading the Shopify URL, or proxying, which kills Sanity's image pipeline transforms. We split it. Hero and editorial assets moved into Sanity. Product lifestyle shots stayed on Shopify CDN because they were owned by the merchandising workflow.
The fourth edge case, and the costliest, was reference cycles inside metaobjects. A fit guide referenced a size chart that referenced the fit guide. Single-pass import failed because the second document did not exist when the first tried to write its reference. We rewrote the importer as a two-pass job that created stubs first and patched references second. Two-pass importers double your debugging surface.
The underestimate we could not have quoted around was drift. Merchandising kept editing Shopify admin during the rebuild because the business kept running. A content freeze was not realistic for a live catalogue. We built a delta sync that ran nightly, pulled metafield changes since the last run, and applied them to Sanity while skipping documents that a Sanity editor had touched more recently. That is a small distributed systems problem hiding inside a content migration, and it was not in the plan.
By the end of month one the data layer worked. We were ten days behind schedule and had eaten most of the buffer.
Month two, the redirect map and the canonical drift
Month two was SEO preservation, scoped at seven days. It took twenty-two. The problem is not that SEO preservation is hard in theory. The problem is the scale of the redirect map and the silent canonical traps that a default Hydrogen setup ships with.
The original site had 4,200 indexed URLs. The rebuild flattened collection hierarchy, moved editorial content from /blogs/story to /guides, and consolidated near-duplicate product pages. That produced a redirect requirement of roughly 1,800 rows. Anyone who has managed Shopify admin redirects knows the bulk import works but the interface is not built for maintaining a map of that size. You cannot query by pattern. You cannot diff against a sitemap.
We pulled redirects out of admin and onto the edge, handled in the Oxygen worker before the Hydrogen route matcher ran. That was not optional. Hydrogen's router matches routes first, and if a new route exists at the same path as an old URL that should redirect elsewhere, the new route wins and returns a 200 before the redirect fires. Moving redirects to the edge gave us pattern matching, version control, and a single source of truth.
The trailing slash and case mismatch issue caught us on launch day. Googlebot had indexed some URLs with uppercase segments from old campaign links. Hydrogen's default matcher is case sensitive, so /Products/red-jacket returned a soft 404. Search Console flagged it within twenty-four hours. We added a normalization step in the worker that lowercased all paths and enforced a canonical trailing slash, and the soft 404s cleared inside a week.
Canonical drift was more insidious. Hydrogen's default collection layout rendered filtered URLs with query parameters, and the default head helper emitted a self-referential canonical. So /collections/jackets?color=red canonicalized to itself, and Google happily indexed three variants all competing for the same jackets keyword. The fix was an explicit canonical pointing filtered variants back to the un-filtered collection, with a small allowlist for combinations that genuinely deserved their own SEO landing page.
Hreflang was another gap the i18n helper did not fill. The GB and US storefronts shared a catalogue priced in local currency, but the default locale setup did not emit hreflang alternates linking each market's URL. We wrote a custom head helper that read the locale routing table and emitted the pair on every localized route.
Structured data needed a full rewrite because we were no longer rendering Product JSON-LD from Liquid. The Hydrogen route pulled a merged payload of Storefront API and Sanity, and our first pass read priceCurrency from the wrong field. That triggered a rich result warning the morning after launch. The merge layer between Shopify and the CMS is a new place where structured data can silently break, and it deserves its own QA step.
By the end of month two we had clean redirects, correct canonicals, valid structured data, and hreflang Search Console liked. Rankings held within a two-position band on the tracked keyword set for the first month post-launch, which for a migration of that size is the outcome you want.
Month three, checkout parity and analytics re-setup
Month three was supposed to be the calm month. Shopify still handles checkout on a Hydrogen storefront, and the team assumption was that we would deploy, tune performance, and hand off. The reality is that the join between a Hydrogen storefront and Shopify's hosted checkout has more integration points than the plan recognized.
The first surprise was that UI extensions built on Checkout Extensibility do not render identically across a Liquid theme and a Hydrogen storefront. The extensions run in the same sandbox, but the surrounding chrome and the cart handoff behave differently. A post-purchase upsell app that worked perfectly on staging Liquid fired late in the Hydrogen flow because cart attributes we were passing through had not been written when the checkout redirect fired. The fix was an explicit cartAttributesUpdate mutation before the redirect, awaited, then the customer is sent to checkout. That detail is not well documented and cost us two days.
Customer Account UI extensions were the second surprise. The merchant assumed that a headless storefront meant a headless account page. It does not. Customer Account UI extensions render in Shopify's hosted account portal with Shopify's account chrome. You can theme it, but you cannot render it inside your Hydrogen routes. The marketing team had planned editorial content on the account dashboard and had to redesign around the portal's constraints.
The third issue was Shop Pay behavior in the cart drawer. In Hydrogen, the buttons needed Shop Pay SDK version two, initialized differently, and the prefill fired a few hundred milliseconds later on mobile. That delay was enough to drop mobile conversion by three percent for a week until we pre-warmed the Shop Pay iframe on cart open.
Analytics re-setup was the last piece. The theme's client-side GA4 events were gone with the theme. We rebuilt event firing in Hydrogen route components and wired checkout and thank-you events through the Customer Events API, subscribing from a custom Web Pixel that forwards to a server-side GTM endpoint on a first-party subdomain. Meta CAPI needed recertification in Business Manager because the event source URL had changed. Google Ads Enhanced Conversions needed a new gtag snippet in the Hydrogen head because the old autotrack no longer fired on client-side route transitions.
None of that was individually hard. All of it together was a week of concentrated work that the plan had treated as an afternoon of configuration.
What we got right
The project shipped on time and the post-launch results were good, so a few things deserve credit.
The Oxygen deploy pipeline was excellent from day one. Every pull request produced a preview URL rendering the current branch against live Sanity draft content. Merchandising reviewed product pages in isolation before merge, and marketing previewed editorial layouts without touching engineering.
The Sanity presentation tool wired to a live Hydrogen preview pane turned out to be the feature the marketing team cared about most. Editors wrote portable text on one side and saw the rendered page on the other with all design system components applied. Landing page velocity after launch went up roughly forty percent measured in pages shipped per sprint, because marketing stopped filing developer tickets for section variants.
Core Web Vitals moved decisively. Median mobile LCP went from 3.1 seconds on the monolith to 1.3 seconds on Hydrogen. INP sat at 120 milliseconds. CLS dropped to 0.02. Those numbers held steady over the three months post-launch as content volume grew.
The four pre-engagement checks we now require
Every headless Shopify engagement we quote now starts with a paid two-week pre-flight audit. The audit produces four deliverables, and nothing in the subsequent SOW is estimated until these are in hand.
The first check is a content model audit. We export every metafield definition, every metaobject definition, every theme section schema, and every third-party app that writes to the product or collection surface. We map each one to a target schema in the headless CMS and flag every edge case that needs custom migration logic. The output is a mapping sheet with an effort estimate per row, signed off by the client before the build SOW is written.
The second check is a URL and redirect inventory. We pull the full URL list from Search Console, the XML sitemap, and a Screaming Frog crawl. We classify every URL into keep, merge, kill, or transform, and produce the redirect map in spreadsheet form before a single line of front-end code is written. If the map is over a thousand rows, we plan for edge-level redirect handling from sprint one.
The third check is a checkout dependency map. Every app that touches cart, checkout, customer account, or post-purchase gets listed. For each we confirm Checkout Extensibility compatibility, verify how the UI extension behaves in the headless surface, and document any custom cart attributes that need to be passed through. Apps that are not Checkout Extensibility compatible are replaced before the build starts, not during.
The fourth check is an analytics re-platform plan. We inventory every tag, pixel, and server-side integration. We decide up front what moves to a server-side GTM endpoint, what stays client side, and what runs through the Customer Events API. The plan is signed off by the client's performance marketing lead before sprint one.
Those four checks take two weeks, cost a fraction of the build budget, and keep projects landing on time.
If you are considering headless Shopify
Headless on Shopify is a good move for the right merchant. It pays off when you have a content team that needs more than Liquid gives them, a performance ceiling you cannot break on a theme, and a roadmap that includes editorial commerce or multi-market complexity. It does not pay off if you are chasing a speed number you could hit with a theme refactor, or if your real problem is an overloaded app stack that will still be overloaded on Hydrogen.
The difference between a clean headless project and a painful one is almost never the code. It is the diligence done before the code starts. If you are about to sign a statement of work, ask your agency for their version of the four checks above. If they do not have a pre-flight audit, they will do the learning on your budget. We did, once. We do not plan to again.
WitsCode runs a two-week headless pre-flight audit that produces the content model, URL, checkout, and analytics deliverables described above. It gives you a realistic timeline before you commit, and it gives any agency you hire the foundation to quote the real project rather than the optimistic one. Get in touch and we will walk through your current stack.
Get weekly field notes.
Practical writing on shipping products, straight to your inbox. No spam.
Need help with this?
Shopify Development
We design and build web apps, MVPs, and SaaS products. Talk to us about what you are working on.
Talk to usWant to discuss ecom for your business?
Start a project and we'll talk through where you are, what's working, and the highest-leverage moves for the next 90 days.

