Navigating Bugs in Shopify's Hydrogen Runtime: Overcoming the Lack of a Node Environment

July 13, 2024
ShopifyRemixHydrogenOxygen

Developers diving into Shopify's Hydrogen framework often appreciate its innovative approach to building custom storefronts using React. However, an aspect that can catch many off guard is that the Shopify's Oxygen runtime environment does not include a traditional Node.js environment. Oxygen use a worker-based Javascript runtime that is built on Cloudflare's workerd library. While Shopify has implemented some standard and critical functionality like the Fetch API, the fetch method, and the Web Crypto API, some third party dependencies may throw errors about missing core packages. The documentation has a full list of implemented featuresThis limitation can introduce various bugs and compatibility issues, especially for those accustomed to Node's extensive ecosystem.  

One common issue developers face is the absence of core Node modules like path and fs. Many third-party libraries rely on these modules, and their absence in the Hydrogen runtime can lead to unexpected errors and broken functionality. For instance, a library designed to handle file operations might throw an error because it cannot access the fs module.

To tackle these issues, one effective solution is to use polyfills. Polyfills are code that provide the functionality of missing features, enabling developers to bridge the gap between environments. For instance, the path module, which is used for handling and transforming file paths, can be polyfilled using packages like path-browserify. The latest versions of Remix allow both server side and browser side Node module polyfills to be configured simply by name in the remix.config.js file in the project root using the serverNodeBuiltinsPolyfill option.


    
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
  serverNodeBuiltinsPolyfill: {
    modules: {
      buffer: true, // Provide a JSPM polyfill
      fs: "empty", // Provide an empty polyfill
    },
    globals: {
      Buffer: true,
    },
  },
};
    

Beyond polyfilling, another strategy would be to abstract the incompatible functionality to serverless functions that can support the missing packages. AWS Lambda for example.