Performant data loading with Hydrogen
Performance starts with how you load data onto a page. Hydrogen gives you the ability to fetch data server-side, but if it’s not done efficiently, then your store’s Time To First Byte (TTFB) can suffer. TTFB is the time it takes for your website to start loading from when it's requested, until it first starts to show on your screen.
To better understand these requests that fetch data, Hydrogen ships with a tool called the Subrequest Profiler. This should be your first stop in diagnosing performance issues.
This document offers a set of best practices for our Hydrogen merchants to support efficient data loading, improving the speed of storefronts and customer experience.
Fetch data in parallel
Anchor link to section titled "Fetch data in parallel"It's common for pages to fetch data from more than one source. For example, your product page might fetch some product information from Shopify and additional content from a third party service such as a CMS.
When awaiting this data it’s easy to end up with a "request waterfall", where each request is made one after another, causing an often avoidable delay. Where possible, it's faster to execute these requests simultaneously.
This can be achieved inside your data loaders using Promise.all
:
As a general rule, only critical data should be prioritized and awaited. For example, product information.
Prioritize critical data
Anchor link to section titled "Prioritize critical data"The more data you need to start rendering a page, the longer your TTFB will be. A longer TTFB delays the page load, which negatively affects the user experience and potentially increases bounce rates.
Fortunately, with Hydrogen you can prioritize critical data and stream in any non-critical data later, which enables you to minimize your TTFB. Critical data is anything that you need before rendering the page, such as product information. Non-critical data is anything that can load after the page renders, such as reviews, or anything below the fold.
Defer non-critical data and stream the response so that the page can begin to render as soon as critical data is retrieved.
In order to render streamed data, you need to use <Suspense>
from React and <Await>
from Remix.
Optimize the loading sequence
Anchor link to section titled "Optimize the loading sequence"The order of your requests in the loader matters. By initiating deferred (streamed) requests first, you can prevent them being blocked by critical (await
ed) data. It might seem counterintuitive to start loading non-critical data first, but this ensures that all data loads in parallel.
Eliminate data dependencies
Anchor link to section titled "Eliminate data dependencies"It’s important to keep in mind that the way third party data is modeled can have an impact on performance. Try to organize it in a way that allows you to perform multiple queries at the same time instead of one after the other. Take the following example where it’s common to store and lookup data by product ID.
By using the product ID as the key, your product page first needs to look up the product ID from Shopify before it can fetch any information. This creates a request waterfall.
In contrast, by storing the data by the product handle, you can fetch all data in parallel:
Separate critical and non-critical queries
Anchor link to section titled "Separate critical and non-critical queries"Larger queries are slower to execute. If a single query contains both critical and non-critical data, consider splitting it into multiple queries so you can defer the non-critical content.
A good example is found on the product page. A product query often fetches all of the product’s variants up front, which can be a lot of data. By splitting it into two queries and deferring all of the possible variants, the product page can render more quickly, with the variant data streaming in after the initial page load. See Hydrogen’s Skeleton template for reference.
Query splitting also provides more fine-grained control over your caching. Each query can use the best caching strategy for its data type.
Do not over-fetch
Anchor link to section titled "Do not over-fetch"Fetching data that isn’t used (aka over-fetching) increases response times and causes unnecessary processing.
The great part about GraphQL is you have the ability to request only the data you need for your page, so make sure that every field in your query is being used on your page — and if it’s not, get rid of it!
Review your loaders to ensure that you’re only requesting resources that are actually required and your GraphQL queries only contain fields that are used.
Leverage caching
Anchor link to section titled "Leverage caching"Fetching data over the network takes time. You can eliminate this delay for future visitors by caching the response of each subrequest.
Think of caching as a last line of defense. After you have an efficient data loading strategy in place, supercharge the storefront by caching your subrequests. For data that changes regularly, adopt short-term caching. For stable data, implement longer-term caching strategies.
The Storefront API client built into Hydrogen includes the following caching strategies:
For third party data, use Hydrogen’s built-in withCache
utility.