What Does Remix v2 Mean for Shopify Hydrogen Developers?

Daniel Bergholz

11 Oct 2023 Headless, Shopify, Hydrogen

Daniel Bergholz

10 mins

If you have been working with Shopify’s Hydrogen framework for some time, you might still have some scars after the migration from Hydrogen v1 to Hydrogen v2.

One of the major changes in Hydrogen v2 was the transition from a custom-built React framework to Remix, a relatively new framework that is seeing a lot of traction thanks to its streamlined developer experience, its reliance on Server-Side Rendering and its deep focus on web standards.

Remix was a significant improvement over Shopify’s in-house React framework, and allowed Hydrogen stores to benefit from a much larger ecosystem of Remix docs, plugins and system integrators. We’ve discussed the benefits at length in our blog post covering the migration.

Unfortunately, the migration process from Hydrogen v1 to Hydrogen v2 was also incredibly complex. So much so, that many large brands were better off re-building their storefront from scratch with Hydrogen v2 rather than attempting to port their existing codebase. This caused a lot of discontent and frustration with Shopify’s decision, which didn’t seem to fully appreciate the complexity of maintaining a complex headless architecture.

Now, it feels like history might be repeating itself: Remix just announced a new major release. Remix v2 comes with several new features and improvements to the developer experience that Hydrogen developers will also benefit from. However, Hydrogen teams are also worried that the transition to Remix v2 might be their latest time-suck and source of frustration.

But is that really the case, or are things better this time around?

In the past few weeks, we’ve been following the Remix community and trying to unpack the implications of this release for Hydrogen. Let’s see what this release actually means for you, and how you can make the best of it!

What’s New in Remix v2 for Hydrogen Devs?

Let’s start by taking a look at the changes and new features in Remix v2 that are most relevant for Hydrogen developers.

Keep in mind that you can always read the release announcement if you want to see the complete list of changes!

New Dev Server with HMR and HDR

If you’ve used a more established React framework such as Next.js, you’re probably familiar with HMR even though you might not know the acronym. HMR, which stands for Hot Module Replacement, is the feature that allows the page to update instantaneously and reflect any changes you make to your code. It’s a handy functionality when you’re actively developing a new page, or making small visual tweaks that you want to preview in real time.

Historically, Remix did not offer support for HMR: that meant you had to manually refresh the page whenever you made a change to your client-side or server-side code, which was old-school and time-consuming.

Luckily, that’s not the case anymore! Remix v2 ships with a new dev server that supports HMR and Hot Data Reloading (HDR), the equivalent of HMR for server-side code. Whenever you make a change to your Hydrogen app, it will be propagated immediately to your browser. Client-side state is also maintained, so that you can continue interacting with the app without restarting any user flows from scratch (e.g., if you’re in the middle of checkout).

It has support for hot module replacement (HMR) and hot data reloading (HDR). The announcement is available on YouTube.

On earlier versions of Remix, whenever there was a code change, you would have to manually refresh the page to see the new changes (pretty old-school tech). Now, that is unnecessary and HMR will not reset client-side state between updates. And HDR can be described as HMR for data loaders. With HDR you can make changes to your server code and see those updates reflected in your UI immediately without resetting client-side state, just like HMR.

Native Support for PostCSS and Tailwind and PostCSS

As you probably know, Hydrogen uses Tailwind as the default CSS framework.

Unlike more traditional CSS frameworks (such as Bootstrap), Tailwind relies heavily on CSS post-processing to generate your final CSS build: it scans all your files, understands which Tailwind classes need to be generated, and then generates just the classes you need. This allows you to minimize the amount of CSS you’re shipping to the client while also allowing you to do incredible things, like attaching arbitrary CSS properties.

Before Remix v2, you would have to run any CSS transformations in a separate process, e.g. with Tailwind’s CLI interface, then import the final CSS file into your Remix app. This worked, but wasn’t really a smooth development experience.

Remix v2 adds out-of-the-box support for Tailwind directives and PostCSS, a popular CSS processor that Tailwind integrates natively with. If you’re using Hydrogen, all the relevant configuration is generated for you: simply bootstrap your app, and your Tailwind styles will just start compiling automagically!

Check out Remix’s documentation about PostCSS and Tailwind for more details.

Server-Side Streaming with defer

This one is a game-changer. Remix v2 aims to provide first-class support for React 18's SSR streaming capabilities through the defer helper.

When you return a defer function call in a route loader, you are initiating a streamed response. This is very useful when serving lower-priority data that may take longer to load. After all, you don’t want your users to wait on slow data if more important content is ready to display on the page!

Here’s an example of what streaming looks like on the server:

import { defer } from "@remix-run/node";

export async function loader ({ request }) {
  // Product info query is small, cached aggressively, ad
  // high priority for the user. Let’s go ahead and let it
  // resolve since it’s fast!
  let productInfo = await getProductInfo (rewquest);

  // Product reviews query is large and cache is more lax.
  // Let’s initiate the query but not wait for it to resolve.
  let productReviewsPromise = getProductReviews (request);

  // With defer, we initiate a streaming response. This allows
  // the user to access the resolved data (`productInfo`) as
  // soon as it’s available, while the unresolved product
  // reviews are loaded in the background.
  return defer ({
    productInfo,
    productReviewsPromise,
  });
}

The corresponding client-side code would then leverage the Await component, which is built on top of React’s <Suspense /> API:

import { Await } from "@remix-run/react";

function ProductRoute () {
  let {
    // Product info has already resolved. Render immediately!
    productInfo,
    // Product reviews might not be ready yet 🤔
    productReviewsPromise,
  } = useLoaderData ();

  return (
    <div>
      <h1>{productInfo.name}</h1>
      <p>{productInfo.description}</p>
      <BuyNowButton productId={productInfo.id} />
      <hr />
      <h2>Reviews</h2>
      <React.Suspense fallback={<p>Loading reviews…</p>}>
        <Await resolve={productReviewsPromise} errorElement={<ReviewsError />}>
          {(productReviews) =>
            productReviews.map ((review) => (
              <div key={review.id}>
                <h3>{review.title}</h3>
                <p>{review.body}</p>
              </div>
            ))
          }
        </Await>
      </React.Suspense>
    </div>
  )
}

// Error fetching the data? Slow connection and timed out?
// Show an error message *only* for reviews. The rest
// of your product UI is still usable!
function ReviewsError () {
  let error = useAsyncError (); // Get the rejected value
  return <p>There was an error loading reviews: {error.message}</p>;
}

This was a framework-agnostic example, but Hydrogen is already leveraging defer. On the home page, for instance, recommended products are streamed rather than being loaded immediately:

export async function loader ({context}: LoaderArgs) {
  const {storefront} = context;
  const {collections} = await storefront.query (FEATURED_COLLECTION_QUERY);
  const featuredCollection = collection.nodes[0];
  const recommendedProducts = storefront.query (RECOMMENDED_PRODUCTS_QUERY);

  return defer ({featuredCollection, recommendedProducts});
}

For more examples of how Hydrogen leverages Remix features to optimize data handling, you can take a look at this article.

Flat Routing by Default

Older versions of Remix use subfolders to nest route segments. This leads to a lot of directory nesting, and also makes it harder to navigate the directory tree.

Remix v2 embraces flat routing instead. With flat routing, all your routes are in the same directory, which makes it easy to find them at a glance. For example, here’s the new folder structure in a Hydrogen application based on Remix v2:

There’s a bit more to it, so we encourage you to check out Remix’s documentation about the change to fully understand the new folder structure.

Since not everyone will love this change, you can also maintain the current approach by installing the @remix-run/v1-route-convention package and adding the following to your remix.config.js:

const {
  createRoutesFromFolders,
} = require ("@remix-run/v1-route-convention");

/** @type {import ('@remix-run/dev').AppConfig} */
module.exports = {
  future: {
    // makes the warning go away in v1.15+
    v2_routeConvention: true,
  },

  routes (defineRoutes) {
    // uses the v1 convention, works in v1.15+ and v2
    return createRoutesFromFolders (defineRoutes);
  },
};

How to Upgrade Your Hydrogen Application to Remix v2

Now, this is the part you were all waiting for! How difficult is it to upgrade an existing Hydrogen application to Remix v2? Luckily for you, not very difficult.

The typical approach for performing major upgrades to your application’s dependencies typically involves creating a separate, long-lived feature branch, working on the upgrade in isolation, and then releasing it all in one go.

This approach, which is still very widespread, has several drawbacks:

  • It is time-consuming, because it requires a lot of work and frequent rebases.
  • It is prone to error, because you’re releasing a lot of changes in one deployment.
  • It is all-or-nothing, because it prevents you from adopting new features before the upgrade.

Remix aims to fix this problem through Future Flags. Future flags allow developers to incrementally adopt new features and then seamlessly upgrade their application, eliminating the need for long feature branches and scary deployments.

Conceptually, the way they work is very simple: the Remix team releases Remix v2 features in Remix v1, but puts them behind a feature flag that you need to manually enable in your Remix configuration. Then, when Remix v2 is released, the Remix team simply removes all future flags and turns them into default behavior. This allows you to integrate one feature at a time as it’s released by Remix, rather than doing it all in one pass.

For instance, here are the future flags for Remix v2:

/** @type {import ('@remix-run/dev').AppConfig} */
module.exports = {
  //…
  future: {
    v2_dev: true,
    v2_meta: true,
    v2_headers: true,
    v2_errorBoundary: true,
    v2_routeConvention: true,
    v2_normalizeFormMethod: true,
  },
};

If you are on the latest version of Remix v1, your remix.config.js looks like the one above, and you’re not getting any errors, you should be able to safely upgrade to Remix v2 by following the official upgrade guide.

If you haven’t enabled those future flags yet, then we strongly recommend doing so in the next few weeks so that you can have a smooth upgrade path in front of you.

Note that the Hydrogen team still needs to officially release support for Remix v2, but thanks to future flags, most of Hydrogen’s code is already compatible. All you need to do is upgrade the dependency in package.json, remove the future flags in remix.config.js, and you should be good to go!

What’s Next for Remix and Hydrogen?

In our work so far, we found the developer experience offered by Hydrogen through Remix is miles ahead of what we had in Hydrogen v1. With a few lines of code, it’s possible to develop rich e-commerce experience while also abiding by web standards and following modern UX practices. Because of that improvement, we’re very excited for the future of this synergy!

The Remix team recently announced that they want to be more transparent about their plans for the framework, but the public roadmap doesn’t offer a lot of information yet. That means it’s still early to tell what other features might be coming up that Hydrogen developers can benefit from.

One initiative on our radar is the work done by the Remix team to integrate React Server Components (RSC). Unfortunately, this will take some more time, as RSC are still unstable and won’t be included in Remix just yet as a result.

If you were waiting for more features or stability in order to start using Hydrogen, today is the best time to give it a try! We’re sure you will be pleasantly surprised.

You may also like

Let’s redefine
eCommerce together.