Performance best practices for Hydrogen
Hydrogen custom storefronts should be built with performance in mind, so that merchants and their customers can benefit from the fastest, most reliable online shopping experiences.
This guide describes best practices for making your Hydrogen custom storefront performant.
React Server Components
Anchor link to section titled "React Server Components"Hydrogen is modelled after React Server Components, an approach that offers an opinionated data-fetching and rendering workflow for React apps.
As you develop your Hydrogen custom storefront, you'll need to determine what to render on the server, what to the render on the client, and what to render on both the server and client. Making the right choices will result in performance benefits.
Build shared components by default
Anchor link to section titled "Build shared components by default"When you need to build a component from scratch, start with a shared component. The functionality of shared components can execute in both server and client contexts.
Starting in the middle helps you ask important questions:
Can this code run only in the server or client?
Should this code run only in the server or client?
Build server components as often as possible
Anchor link to section titled "Build server components as often as possible"The majority of the components in your app should be server components. Consider building a server component if any of the following use cases apply:
The component includes code that shouldn’t be exposed on the client, like proprietary business logic and secrets.
The component won’t be used by a client component.
The code never executes on the client.
The code needs to access the filesystem or databases, which aren’t available on the client.
The code fetches data from the Storefront API.
The code renders static or infrequently updated content, such as an About page.
Build client components in rare cases
Anchor link to section titled "Build client components in rare cases"Generally, you don't need to convert the entire component into a client component - only the logic necessary for the client needs to be extracted out into a client component. Consider building a client component if any of the following uses cases apply:
You require client-side interactivity.
You're using the
useState
oruseReducer
React hooks.You're using lifecycle rendering logic (for example, implementing the React
useEffect
hook).You're making use of a third-party library that doesn’t support React Server Components.
You're using browser APIs that aren’t supported on the servers.
Data fetching
Anchor link to section titled "Data fetching"Delivering fast server-side responses requires fast and efficient first-party (Shopify) and third-party data access.
First-party (Shopify) data source
Anchor link to section titled "First-party (Shopify) data source"Consider deploying your Hydrogen custom storefront on Oxygen, Shopify's recommended deployment platform for Hydrogen storefronts. Oxygen provides caching out of the box for routes and sub-requests.
Third-party data source
Anchor link to section titled "Third-party data source"If you're fetching from a third-party data source, then the runtime exposes the standard Fetch API enhanced with smart cache defaults and configurable caching strategies.
The following example shows how to fetch from a third-party data source and make sure that customers get the quickest response possible while also displaying the latest data:
Caching and stale-while-revalidate
Anchor link to section titled "Caching and stale-while-revalidate"Caching is a fundamental building block of a good shopping experience. By configuring maxAge
and staleWhileRevalidate
, you have full control over data freshness and the revalidation strategy.
For example, if a response is considered stale due to being older than maxAge
, but it's still within the additional time window provided by staleWhileRevalidate
, then the response is used. The data is also asynchronously revalidated in preparation for the next request. This keeps your content fresh and your store performant.
If a stale response falls outside the staleWhileRevalidate
window, then the response is revalidated before being delivered.
Hydrogen's caching strategies include maxAge
and staleWhileRevalidate
values by default:
If you don't want to use the caching strategies provided by Hydrogen, then you can create your own using a CustomCache
strategy:
Avoid overfetching
Anchor link to section titled "Avoid overfetching"Requesting too much data from the Storefront API or from other resources can slow down your Hydrogen storefront. You should make sure that your Hydrogen app is only requesting that data it needs to render a route.
To help you request only the data that you need, Hydrogen includes a log
utility that identifies unused data returned from useShopQuery
. The log
utility prints unused query properties in the server console to highlight potential data over-fetching.
To enable logging for unused query properties, set the logger.showUnusedQueryProperties
option to true
in your Hydrogen configuration file.
Then, visit your terminal that's running the development server to see any notices printed by the utility:
Pages and subrequests
Anchor link to section titled "Pages and subrequests"Hydrogen doesn't require that all requests are server-rendered. Routes and subrequests with static or infrequently updated content can be served from the edge.
For example, a marketing page that’s typically static can be cached, served directly from the CDN edge, and asynchronously revalidated with the help of the CacheLong()
caching strategy:
Suspense boundaries
Anchor link to section titled "Suspense boundaries"Data fetching in Hydrogen is powered by React Suspense. When you define a Suspense boundary, you provide a fallback component to render until the contents of the Suspense boundary is resolved.
It's important to wrap your server components that fetch data in Suspense boundaries. This allows Hydrogen to stream the fallback components to your users immediately rather than waiting for all of the data to be resolved.
Placement of Suspense boundaries
Anchor link to section titled "Placement of Suspense boundaries"Wrap a Suspense boundary around the content that suspends, not inside of it:
Prioritizing components
Anchor link to section titled "Prioritizing components"It's important to prioritize some content over other content. For example, you might want some product details like title, image, and description to load before other product details, like reviews or related products.
You can prioritize some components and defer other components by wrapping Suspense boundaries around the deferred components in the same app tree. This allows Hydrogen to stream the prioritized component's data first, and fetch the data for the deferred components asynchronously:
Split queries
Anchor link to section titled "Split queries"Some data sources might load more quickly than others. If your Hydrogen storefront is responding slowly, then you might want to evaluate how you're writing your queries and consider splitting them up.
For example, requesting a shop's name and information from the Storefront API is very quick, while loading many collections with nested product details will be less quick. Because both pieces of data are requested in the same query, the response will only be as quick as the slowest resource:
Instead, you can split the query for basic storefront data from the query for collection information to make the storefront data load quicker:
Combine and re-use queries
Anchor link to section titled "Combine and re-use queries"Sometimes it makes sense to split queries, and other times it makes more sense to combine and re-use queries. You can experiment with combining or splitting your queries to see what approach works better for your use case.
Hydrogen de-duplicates identical requests made to fetchSync
, useShopQuery
and useQuery
. This means that if you fetch a data resource in one component, then fetching an identical data resource in another component won't result in an additional API request.
You can use this behavior to your advantage. For example, the following components request very similar data, but they're not identical:
If you combine the above two queries, then Hydrogen only makes a single call to the Storefront API, and your components can read from the same response:
Use a preload cache
Anchor link to section titled "Use a preload cache"Hydrogen offers a preload cache that you should enable for non-personalized data resources. This allows Hydrogen to start loading all of the required resources for a given page immediately, rather than after the entire app tree has been resolved and rendered.
Server bundle size
Anchor link to section titled "Server bundle size"When you deploy your Hydrogen storefront on a Workers runtime like Oxygen or Cloudflare Workers, it's important to maintain a small server bundle size. This is because each serverless invocation becomes slower as the size of the code grows larger.
Some client-only dependencies like threejs
might be larger than 500KB when bundled on the server. You can reduce the server bundle size by preventing these dependencies from being included in the bundle.
Hydrogen provides a import.meta.env.SSR
object to allow you to tree-shake these dependencies from your server bundle: