<Back to Updates

Winter '23 Editions: Remixing Hydrogen

February 9, 2023

At Shopify, we are passionate about helping you build the best commerce experiences on the web. Which is why we are super excited to partner with the Remix team in rewriting Hydrogen from the ground up. It took a couple of iterations and some critical feedback to land on the right abstraction, and we are ready to share how we got here!

The new Hydrogen is a set of tools, utilities, and best-in-class examples for building a commerce application with Remix. But fundamentally, Hydrogen is simply Remix. If you already know how to build a great experience in Remix, Hydrogen is only going to make that easier.

How did we get here?

Hydrogen 1.0 launched in June. Only a few months later in August, Shopify acquired Remix. After brainstorming together on all the ways we could join forces and push the web forward, we quickly realized that the future of Hydrogen would be Remix. At the start there were a lot of unanswered questions. But rather than attempting to answer all of them at once, we just started building. Together with the Remix team, we rewrote (pair coding for the win) the Hydrogen demo store in Remix. Our initial implementation followed standard React patterns of development, but Remix fundamentally encourages a different paradigm. We came to a few realizations from iterating on our implementation:

Realization #1: Server state > Client state

Most React applications (Hydrogen 1 included) rely heavily on component state and context to provide data throughout the app tree. For example, on a product detail page there might be a product options selector. A simple implementation is to use component state to track what product options are selected:

/components/ProductOptions.jsx


export function ProductOptions({ availableOptions }) {
const [option, setOption] = useState(getDefaultOption);

return (
  <ListBox
    availableOptions={availableOptions}
    selectedOption={option}
  />
);

The problem with this code is that it quickly gets complicated. The selected product option and associated product variant should be tied to the URL. This is important so that the user can select a specific variant and then bookmark or share the URL without losing the selected state. So when a selected option is changed, the URL needs to be updated and getDefaultOption needs to be aware of the current URL state. Additionally, a context provider is often added so that any component in the tree can get access to product options.

However, Remix makes synchronizing URL state with app state extremely easy. Consider this simplified Remix code:

/routes/products/$handle.jsx


export async function loader({ request }) {
  const product = await queryProductVariant(request.url);
  return json({ product });
}

export default function ProductDetailPage() {
const { product } = useLoaderData();

return (
  <>
    <ProductDetails product={product} />
      {product.variants.map((variant) => (
        <Link> to={\`/products/\${variant.id}\`}>
          {variant.name}
        </Link>
      ))}
  </>
);

The loader function is executed before the product detail page renders. Any data returned from the loader is available anywhere within the app tree through{' '} useLoaderData(). Notice also that there is no local component state! The user selecting an option and changing product variants is simply a navigation event to a new page. Remix will re-execute the loader on the new URL and re-render the page.

The great thing about loaders is that they only execute on the server. Only the data they return is ever sent to the client. This means that a lot less JavaScript is needed in the client. A bonus benefit of this approach is that the page is fully functional before JavaScript loads, or even without JavaScript entirely. This is a significant advantage when visitors have a slow internet connection where it might take longer than expected for JavaScript to download and execute. With the Remix implementation the app is immediately interactable!

Realization #2: Wow, I can delete a lot of code!

We completely rewrote the Cart to utilize Remix{' '} loaders and actions. Now all the cart logic is on the server, never needs to ship to the client, and we can delete all of this JavaScript! Remix provides robust primitives for mutating and fetching data from the server. Utilizing these primitives means that we can use their state to power our app’s UI. This is useful for optimistic UI, which is the pattern of presenting the outcome of an interaction immediately, without needing to wait on the network to resolve. For example, a cart sidebar can pop open when something is added to the cart and show the updated cart right away, instead of waiting for the API response to show feedback to the user. The improvements are most obvious in that the new demo store has almost no useState or useEffect — and zero providers!

Realization #3: Remix’s new defer functionality is the real deal and a game changer!

One of Hydrogen v1’s design flaws was that we leaned too heavily into streaming. In particular, we found many developers confused about how to defer certain parts of the UI while prioritizing others. When does the response start streaming? What if I decide to redirect or 404 after streaming has already begun? What about meta and SEO tags that need to be included in the head? How do I make sure those are included before streaming begins? Hydrogen v1 also lacked a nested routing strategy, exacerbating all of these streaming issues.

Remix introduces the ability to stream content via the defer API. If we update our earlier loader example to stream a piece of secondary content:

/routes/products/$handle.jsx


export async function loader({ request }) {
  const product = await queryProductVariant(request.url);
  // If the request is not awaited, it is deferred
  const recommendedProducts = queryRecommendedProducts();

  return defer({ product, recommendedProducts });
}

export default function ProductDetailPage() {
  const { product, recommendedProducts } = useLoaderData();

  return (
    <>
      <ProductDetails product={product} />
      <Suspense
        fallback={<p>Loading recommended products...</p>}
      >
        <Await
          resolve={recommendedProducts}
          errorElement={
            <p>Error loading recommended products!</p>
          }
        >
          {(products) => (
            <RecommendedProducts products={products} />
          )}
        </Await>
      </Suspense>
    </>
  );
}

We added another request to fetch recommended products. This content will be streamed because the loader now returns `defer` instead of `json`. You can easily pick and choose which content is deferred by what is and isn’t `await`ed within the loader. Within the UI, deferred data is simply a promise. Resolve the deferred data with a{' '} {` boundary and a Remix{' '} {` component. Notice that you can define loading and error UI states.

Remix won’t start the response to the browser until after the loader executes. If you need to prioritize data or determine if a 404 or redirect is necessary, just await within the loader. All other data that is streamed isn’t awaited. And that data should not make a difference on page status codes, headers, or other page meta data.

On the way to the new Hydrogen

After building out a new Demo store on Remix, we identified areas of abstraction to make your life easier. And we abstracted… a lot. Too much. In fact, our abstractions ended up making Hydrogen not feel like Remix anymore. The abstractions made it confusing how everything fit together. So we pulled out a lot of our abstraction prototypes and have put them back on our shelf, potentially to be reborn in another shape someday. We’d rather Hydrogen be simple and make it easy to add to any Remix application. Our aim is to have Hydrogen feel so close to Remix in design that you might not even notice it’s there—but you will notice when it’s not. While we’ll continue to add Hydrogen abstractions over time, we’re leaning into Remix’s simplicity, and will bias towards only introducing new ones as they become obvious.

Another change is in how we’re now approaching versioning. Because Hydrogen is inherently coupled to the Storefront API, we’ve decided that Hydrogen will now follow calendar versioning. This should make it clear which API versions our components are designed to support, and consolidate any potential breaking changes to a predictable timeline (at most, once per quarter). This also means that, technically, there is no Hydrogen 2. Instead the next version of Hydrogen will be 2023.1.0 and moving forward new versions of Hydrogen will be released at the same time as new versions of the Storefront API (we’ll continue to add features frequently, however, as minor bumps; for example, 2023.1.1 will be our following release).

What about Oxygen?

Preparing Oxygen for the new Hydrogen meant building an Oxygen adapter for Remix. The Cloudflare adapters were a source of inspiration. Though you could swap it for the Node adapter, our demo store is opinionated for Oxygen. This is a huge development win; the development runtime now matches the production runtime! No more surprises when third-party packages aren’t compatible in a production worker runtime.

We are also happy to share that Oxygen is now available to more Shopify plans. Previously only available to Plus, now if you are on a Basic, Shopify, Advanced, or Plus plan, you can now deploy for free on Oxygen.

What’s next?

The Remix team is shipping like crazy , and that’s the real power you now get with Hydrogen. While the Remix team continuously works to improve best in class web apps, the Hydrogen team is laser focused on improving headless commerce at Shopify. We will continuously improve the commerce abstractions on top of Remix, emphasizing synergy with Commerce Components and the rest of the Shopify ecosystem. And if you aren’t using Hydrogen or Remix, we haven’t forgotten you! We are excited to also announce Hydrogen React, a more generic package of React components that includes a client for querying the Storefront API, components for a client-side cart, and Analytics and SEO tools.

We are excited for you to try out Hydrogen with Remix. Head over to our docs to get started, or chat with us on Discord to get started!

Get building

Spin up a new Hydrogen app in minutes.

See documentation