DEV Community

Kholipha Ahmmad Al-Amin
Kholipha Ahmmad Al-Amin

Posted on

Zero-CLS Client-Side Faceting: Optimizing High-Cardinality Filters in Next.js 14

Zero-CLS Client-Side Faceting: Optimizing High-Cardinality Filters in Next.js 14

Faceted search interfaces in e-commerce applications are notorious for causing Cumulative Layout Shift (CLS) and input delays. When filtering across multiple high-cardinality taxonomy dimensions (skin type, skin concern, brand, and price bracket), naive implementations either trigger expensive server roundtrips or cause erratic layout reflows as elements unmount and resize.

On mobile connections, layout shifts exceeding 0.1 CLS trigger Core Web Vitals penalties in search rankings and frustrate purchasing intent.

To eliminate this on the Faceted Skincare Catalog, the frontend was engineered using an in-memory client-side indexing model with deterministic CSS grid aspect ratios and URL state synchronization.

The Performance Architecture

Rather than triggering full network re-fetches for every facet toggle, the client hydrates a lightweight product catalog once and performs instant filter passes:

[ Initial Static Page Hydration ]
       |
       +---> Fetch /api/catalog (gzipped ~28KB, cached in browser memory)
       |
[ User Toggles Filter (e.g. Oily Skin + Centella Asiatica) ]
       |
       +---> In-Memory Array Filter Pass (< 3ms)
       +---> Next.js URL SearchParams Sync (shallow router update, 0ms reload)
       +---> CSS Grid Layout Containment (CLS = 0.000)
Enter fullscreen mode Exit fullscreen mode

1. Zero-Reflow CSS Grid Containment

The root cause of CLS in dynamic catalog grids is unpredictable card height adjustments during image loading or tag wrapping. To guarantee stable dimensions, product card containers declare strict aspect ratios and layout containment:

<!-- ProductCard layout skeleton -->
<div class="group flex flex-col rounded-card bg-white border border-brand/10 p-4 shadow-card contain-content">
  <div class="relative aspect-square w-full overflow-hidden rounded-input bg-rose/5">
    <img 
      src={product.image} 
      alt={product.title} 
      loading="lazy" 
      class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" 
    />
  </div>
  <div class="mt-3 flex flex-1 flex-col justify-between">
    <h3 class="line-clamp-2 min-h-[2.5rem] font-serif text-sm font-semibold text-brand">
      {product.title}
    </h3>
    <div class="mt-2 flex items-center justify-between">
      <span class="font-bold text-rose-dark">৳{product.price}</span>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

By enforcing aspect-square on images and min-h-[2.5rem] on headings with contain-content, the browser engine pre-allocates exact coordinate boxes before assets load.

2. In-Memory Multi-Facet Evaluation

Filter predicates evaluate synchronously in memory, delivering sub-5ms UI updates:

// Client-side filtering logic
const filteredProducts = useMemo(() => {
  return products.filter((p) => {
    // Category match
    if (activeCategory && p.category_slug !== activeCategory) return false;

    // Skin type match (e.g. acne, oily, sensitive)
    if (activeSkinType && !p.skin_types?.includes(activeSkinType)) return false;

    // Skin concern match (e.g. pores, redness)
    if (activeConcern && !p.skin_concerns?.includes(activeConcern)) return false;

    // Price range bounds
    if (p.price < minPrice || p.price > maxPrice) return false;

    return true;
  });
}, [products, activeCategory, activeSkinType, activeConcern, minPrice, maxPrice]);
Enter fullscreen mode Exit fullscreen mode

When users explore specialized niches such as the Low-pH Cleanser Category, the grid re-renders immediately without waiting on edge roundtrips.

3. Shallow URL State Synchronization

To support deep-linking and browser back/forward history without triggering route unmounts, filter state pushes to URL search parameters using Next.js shallow navigation:

function updateFilter(key: string, value: string) {
  const params = new URLSearchParams(window.location.search);
  if (value) {
    params.set(key, value);
  } else {
    params.delete(key);
  }
  router.replace(`${pathname}?${params.toString()}`, { scroll: false });
}
Enter fullscreen mode Exit fullscreen mode

Benchmarking Metrics

Testing the faceted search interface on simulated 3G mobile devices confirms:

  • Cumulative Layout Shift (CLS): 0.000 across 50 consecutive filter operations.
  • Input Delay: Under 12ms per selection.
  • Memory Consumption: Under 18MB heap overhead for 500 catalog items.

Faceted interfaces can be both feature-rich and exceptionally fast when client memory is utilized intentionally. Experience the zero-CLS catalog in production at Iseul Glow.

Top comments (0)