GAZAR

Principal Engineer | Mentor

Optimizing Remix.js Performance: A Cache-Driven Approach

Optimizing Remix.js Performance: A Cache-Driven Approach

Addressing the performance concerns of my Remix.js website became a priority following an initial Web Vitals analysis, revealing a somewhat sluggish Largest Contentful Paint (LCP) at 5.68 seconds. The culprit was identified as a series of requests sent to my backend powered by Strapi.js. To swiftly tackle this issue, I delved into caching strategies, primarily focusing on HTTP caching.

remixjs-performance.png

Implementing HTTP caching involved adding headers to requests, such as:

"Cache-Control": "public, s-maxage=3600"

I configured my server to respond with caching directives like:

responseHeaders.set("Cache-Control", "public, max-age=3600");

Leveraging popular CDNs like Cloudflare or Cloudfront, these headers facilitated efficient page caching for up to a day. This resulted in faster page retrieval, akin to serving content from a proximate server, thereby significantly reducing load times for HTML and JS.

Extending this caching strategy to client-side fetch requests enhanced browser-level caching. For instance:

const headers = {
    "Content-Type": "application/json",
    Authorization: `Bearer ${token}`,
    "Cache-Control": "public, max-age=3600"
};

This client-side cache control empowered the browser to intelligently manage caching, contributing to an expedited user experience.

However, recognizing that true performance optimization required more than just efficient caching, I explored memory caching. Employing the lru-cache library, I encapsulated my fetch requests within a caching mechanism. This memory cache, acting as a global variable in my Node.js app, stored responses for quick retrieval.

import { LRUCache } from "lru-cache";
const lruCacheOptions = {
  max: 500,
  ttl: 1000 * 60 * 60 * 24, // 1 day
};
const cache = new LRUCache(lruCacheOptions);
const fetchHandler = async (
  url: string,
  options?: any,
  cacheEnabled = true,
  clearCache = false
) => {
  // ... (implementation details)
};

This integration resulted in a remarkable improvement in LCP, slashing it to a mere 0.66 seconds in Remix.js. With the cache-enabled flag set to true, subsequent requests were seamlessly served from the cache, ensuring a consistently swift user experience.

remixjs-after-web-vitals.png

In conclusion, these strategic optimizations, encompassing advanced caching techniques and memory caching, not only expedited initial page loads but also fostered a sustained improvement in subsequent interactions. The combination of server-side and client-side caching, along with memory caching, lays a robust foundation for a high-performing Remix.js website.