DEV Community

Cover image for How I Fixed Flash of Unstyled Content (FOUC) in Shopify Collection Filters (Step-by-Step Debugging)
Mindmagic
Mindmagic

Posted on

How I Fixed Flash of Unstyled Content (FOUC) in Shopify Collection Filters (Step-by-Step Debugging)

Flash of Unstyled Content (FOUC) is one of those frontend issues that looks small but can completely break the user experience. Recently I had to fix this problem in a Shopify store where the collection filter panel briefly appeared as raw HTML before the page finished loading.

The store was running a paid Shopify theme with several custom edits and third-party apps, so the source of the problem was not obvious.

In this post I’ll show how I tracked down the cause and fixed it without breaking the theme.

The problem

Every time a user opened a collection page, the filter sidebar appeared for a split second without styling. After that, the CSS loaded and everything looked normal.

This happened on:

  • Desktop
  • Mobile
  • Slow connections
  • First page load

This is a classic FOUC (Flash of Unstyled Content).

Possible causes

In Shopify themes, FOUC usually happens because of:

  • CSS loaded too late
  • JavaScript modifying DOM before styles load
  • App scripts injected in wrong order
  • Deferred or async assets
  • Duplicate CSS files

Because this store had custom work from another developer, I suspected an asset loading conflict.

Step 1 — Check theme.liquid

First I opened theme.liquid and checked the order of assets.

I noticed that some CSS files were loaded after scripts, and one app was injecting styles dynamically.

Bad example:

<script src="{{ 'filters.js' | asset_url }}"></script>
<link rel="stylesheet" href="{{ 'theme.css' | asset_url }}">
Enter fullscreen mode Exit fullscreen mode

CSS should load before scripts.

Step 2 — Inspect DevTools

Using Chrome DevTools I watched the Network and Elements tabs while reloading the page.

I saw:

  • Filter HTML rendered immediately
  • CSS applied later
  • App script re-rendered filter

That confirmed the FOUC was caused by render timing.

Step 3 — Temporary hide until ready

Instead of letting the filter render unstyled, I hid it until the page finished loading.

Added class to filter container:

<div class="filter-panel no-fouc">
Enter fullscreen mode Exit fullscreen mode

CSS:

.no-fouc {
  visibility: hidden;
}
.js-ready .no-fouc {
  visibility: visible;
}
Enter fullscreen mode Exit fullscreen mode

Step 4 — Enable when DOM is ready

Then I added a small script:

document.addEventListener("DOMContentLoaded", function() {
  document.documentElement.classList.add("js-ready");
});
Enter fullscreen mode Exit fullscreen mode

This ensures styles are applied before showing UI.

Step 5 — Remove duplicate app assets

I also found one app loading CSS twice.

Removing the duplicate include reduced the flash.

Step 6 — Test in duplicate theme

Important rule when fixing Shopify issues:
Never edit live theme first.

I duplicated the theme, tested there, then published after confirming the fix.

Result

  • No more flash
  • Filters load clean
  • Works on all devices
  • No layout shift

Lessons learned

FOUC in Shopify usually comes from asset order, not CSS itself.

Always check:

  • theme.liquid
  • app includes
  • async / defer scripts
  • duplicate assets
  • DOM render timing

Conclusion

Fixing FOUC is not about hiding problems, it’s about controlling when the UI becomes visible.

Small changes in load order can make a big difference in perceived performance.

Top comments (1)

Collapse
 
stackedboost profile image
Peter Hallander

Good debugging approach, and the visibility: hidden + js-ready class pattern is the right fix for DOM-timing FOUC.

Worth adding one more layer. The reason this still flashes on slow connections is that all the work — parsing, CSS application, JS execution, js-ready toggling — happens at click-time. The user waits for DOMContentLoaded to fire before the filter panel appears. On a cold 3G connection that can still be a 1-3 second gap.

The complementary approach: move that work before the click. Speculation Rules API (Chrome 108+):

<script type="speculationrules">
{ "prerender": [{ "source": "document", "eagerness": "moderate" }] }
</script>
Enter fullscreen mode Exit fullscreen mode

With moderate eagerness, Chrome prerenders collection pages when a user hovers over or focuses a link. By the time they click, the page is already fully rendered in a background browsing context — CSS applied, JS executed, js-ready class already added. FOUC doesn't happen because the browser activates an already-complete render rather than initializing one.

Your no-fouc fix is still worth keeping (handles direct navigations, bookmarks, hard refreshes), but on any link-click navigation through a Shopify store it becomes a no-op — the page is already done. You can verify with performance.getEntriesByType('navigation')[0].activationStart > 0 on a prerendered load.

(I'm the developer of Prefetch — apps.shopify.com/prefetch — which handles the Speculation Rules setup for Shopify stores automatically.)