How I Cut a Hydrogen Homepage From 5s to 2s
By Emre Mutlu, creator of the world's first English Shopify Hydrogen course on Udemy. April 21, 2026.
Published: April 21, 2026
Last updated: April 21, 2026
Reading time: 6 min
Article summary
I cut a Shopify Plus Hydrogen homepage from roughly 4 to 5 seconds to about 2 seconds by stopping the initial useEffect fetch, server-rendering only the first tab of each section, and lazy-loading the rest on interaction. The bigger win was not speed alone. It was getting product HTML back into the first response.
I cut a production Shopify Plus Hydrogen homepage from roughly 4 to 5 seconds to about 2 seconds by removing one bad assumption: that every tab on the page needed its product data on first load. It did not. Once I moved the first tab into the route loader and lazy-loaded the rest, both speed and SEO got cleaner.
Why the homepage was slow in the first place
The slowdown came from loading too much data too late. A single homepage section had 12 tabs with 8 products each, which meant 96 products per section before counting multiple sections. All of that was fetched client-side in useEffect, so the page hydrated first, then started the real work.
This was for a Shopify Plus jewelry brand I work with, a homepage with multiple horizontal product rails and filter tabs. From a React point of view, the code looked harmless. From a Hydrogen point of view, it was a footgun, especially if you have not internalized React's You Might Not Need an Effect guidance yet.
// Before, anti-pattern in Hydrogen
function HomepageSection() {
const [products, setProducts] = useState([]);
useEffect(() => {
fetchAllTabsProducts().then(setProducts);
}, []);
return <Tabs products={products} />;
}
The practical result was obvious. The homepage took roughly 4 to 5 seconds to feel ready. The SEO problem was quieter. The initial HTML had no product listings at all, because product data only arrived after hydration.
What I changed, and why it worked
I changed the fetch strategy to match real user behavior. Only the first tab of each section is server-rendered with its 8 products. The rest of the tabs lazy-load on click. That cuts initial data weight, gets product HTML into the response, and stops the homepage from paying for tabs most users never open.
I moved the first-tab fetch into the route loader and removed the initial useEffect path entirely. The component now receives the first tab as props from the server response, and only requests other tabs after an actual interaction. If you want the official Hydrogen side of this pattern, Shopify's caching docs are the right reference for the cache layer.
// After, Hydrogen-native
export async function loader({context}) {
const firstTabProducts = await context.storefront.query(FIRST_TAB_QUERY, {
cache: context.storefront.CacheCustom(
// Tuned to the product catalog's update frequency
),
});
return json({firstTabProducts});
}
function HomepageSection() {
const {firstTabProducts} = useLoaderData();
return <Tabs initialProducts={firstTabProducts} />;
}
That one change cut the homepage from roughly 4 to 5 seconds to about 2 seconds in observed behavior. I did not benchmark it with Lighthouse or WebPageTest, so I am not pretending this is a lab-grade performance study. It is a production engineering note. The page felt dramatically lighter because it stopped trying to fetch everything just in case.
The SSR bug that almost made this change look finished when it wasn’t
The first deploy still had an SSR hole. The in-house team tested the homepage with JavaScript disabled and found that the first-tab products were still missing from the initial HTML. That meant the SSR fix was not actually fully server-side yet, even though the page looked correct in a normal browser session.
The root cause was leftover client-only logic. The first tab had been conceptually moved to the loader, but one residual branch still routed its fetch through the old client path. So the UI worked, but the HTML response was still incomplete.
The fix was simple once the test exposed it. I made sure the first-tab data was fetched in the route loader and passed straight into the component as props, with no fallback path that depended on hydration. After that, the JS-disabled test showed product listings in the HTML exactly where they should be.
This is why I keep telling teams that it works in the browser is not a real SSR test. If the route matters for SEO, disable JavaScript once before you call it done. It is the fastest sanity check you can run.
The small UI cleanup that broke context after the performance fix
Performance changes often create pressure to compress the UI, and that can quietly remove meaning. In this case I stripped category prefixes from tab labels so long titles fit better inside horizontal rails. The result looked cleaner, but one section lost enough context that the shortened label became confusing.
A good example was the chain section. Titles like Diamond Chains and Gold Chains were shortened to make the tabs visually lighter. After deploy, the team noticed that under the Gold Chains section, the label just read Chains. It was shorter, but it also felt detached from the parent context.
The fix was to add a conditional guard rail. Prefix stripping stayed in place for titles where the parent section already carried the right context, but not in cases where shortening the label made the tab feel generic. That was a small change, but it mattered. Faster UI is not automatically clearer UI.
I mention this because performance work often gets discussed as if it is separate from interface language. It is not. Every time you compress data, text, or layout, you risk dropping context that the user still needed.
What I would tell another Hydrogen developer before they copy this pattern
If your first instinct is to preload every tab on a tabbed homepage, stop and check whether real users justify that cost. Most do not. They look at the first tab, some click the second, and very few make their way through all 12 tabs across multiple sections. Optimize for the common path, not the paranoid one.
If you are fetching primary route data in useEffect on a Hydrogen page, you are usually fighting the framework. Hydrogen gives you route loaders for a reason. Use the server for the part of the page that must exist on first load, then lazy-load the rest behind real user intent.
If you need more background before making that decision, my Should I Use It? page explains where Hydrogen is justified and where it is not. My cost breakdown is the practical version of the same conversation, and the case studies page shows the kind of stores where these tradeoffs actually appear.
The larger lesson is that this was not really a Hydrogen problem. It was a React habit problem. Teams coming from SPA-heavy or Next.js-heavy workflows reach for useEffect because it feels familiar. In Hydrogen, that habit gets expensive fast. You lose SSR, you lose HTML visibility, and you slow down the exact page that should be easiest to trust.
FAQ
Why was useEffect the wrong place for this homepage fetch?
Because it pushed primary page data to the client after hydration. That delayed the moment the homepage felt ready and removed product listings from the initial HTML, which is the wrong tradeoff for a category-heavy storefront.
What changed after moving the first tab to the route loader?
The first tab became part of the server-rendered response. That put real product HTML in the initial response, improved the ready state, and passed the simplest SSR check, loading the page with JavaScript disabled.
Should every tab be preloaded on a Hydrogen homepage?
Usually no. Most users interact with the first tab, some click the second, and very few open every tab in every section. Preloading everything is often an expensive guess that hurts the common path.
Where does intentional cache tuning fit in this pattern?
It matters when collection freshness and edge caching need to match catalog behavior. The bigger point is not one exact number. It is moving the first tab into the loader and caching the query deliberately instead of relying on a client-side effect.
FAQ
Why was useEffect the wrong place for this homepage fetch?
Because it pushed primary page data to the client after hydration. That delayed the moment the homepage felt ready and removed product listings from the initial HTML, which is the wrong tradeoff for a category-heavy storefront.
What changed after moving the first tab to the route loader?
The first tab became part of the server-rendered response. That put real product HTML in the initial response, improved the ready state, and passed the simplest SSR check, loading the page with JavaScript disabled.
Should every tab be preloaded on a Hydrogen homepage?
Usually no. Most users interact with the first tab, some click the second, and very few open every tab in every section. Preloading everything is often an expensive guess that hurts the common path.
Where does intentional cache tuning fit in this pattern?
It matters when collection freshness and edge caching need to match catalog behavior. The bigger point is not one exact number. It is moving the first tab into the loader and caching the query deliberately instead of relying on a client-side effect.
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Why was useEffect the wrong place for this homepage fetch?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Because it pushed primary page data to the client after hydration. That delayed the moment the homepage felt ready and removed product listings from the initial HTML, which is the wrong tradeoff for a category-heavy storefront."
}
},
{
"@type": "Question",
"name": "What changed after moving the first tab to the route loader?",
"acceptedAnswer": {
"@type": "Answer",
"text": "The first tab became part of the server-rendered response. That put real product HTML in the initial response, improved the ready state, and passed the simplest SSR check, loading the page with JavaScript disabled."
}
},
{
"@type": "Question",
"name": "Should every tab be preloaded on a Hydrogen homepage?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Usually no. Most users interact with the first tab, some click the second, and very few open every tab in every section. Preloading everything is often an expensive guess that hurts the common path."
}
},
{
"@type": "Question",
"name": "Where does intentional cache tuning fit in this pattern?",
"acceptedAnswer": {
"@type": "Answer",
"text": "It matters when collection freshness and edge caching need to match catalog behavior. The bigger point is not one exact number. It is moving the first tab into the loader and caching the query deliberately instead of relying on a client-side effect."
}
}
]
}
Review Notes
- Weakest part: The post still relies on observed production timing instead of a repeatable lab benchmark, so the performance claim is persuasive but not yet benchmark-grade.
- Missing raw material: A real DevTools waterfall screenshot, Lighthouse diff, or the exact first-tab query would make the next version much stronger.
- AI citation potential: 8/10
Your Shopify store works, but every new feature takes 3x longer than last year? That is when I come in. If your homepage, collection, or product pages are hitting the ceiling of what your current stack can deliver, I can help you see whether Hydrogen is the right move, and if it is, how to implement it without these traps.
Top comments (0)