Optimizing Infrastructure Cost for Next.js Server Side Rendered Pages (SSR)

With the changes announced by Google last year regarding how they are going to measure Google Core Web Vitals (LCP, FID, CLS) for web apps, SSR (Server Side Rendered Pages) became the survivor to avoid any downgrading on SEO front from Google. Most of the business websites running on Client-Side Rendered (CSR) React application moved to SSR on Next.js framework. This gave a huge push to Adoption of SSR and hence the Next.js framework.

We at HealthKart, followed the same path and migrated our two digital properties, HealthKart.com and Muscleblaze.com from CSR to SSR on Next.js framework.

By transitioning to SSR with Next.js, we have achieved significant performance improvements. SSR enables us to generate the initial HTML on the server and send it to the client, resulting in faster page rendering and improved perceived performance. Additionally, SSR facilitates better SEO as search engine crawlers can easily access the fully rendered content.

All Good till now, but wait !! below is the catch ..

Next.js (SSR) Increased our Infrastructure Spend. Seems Obvious !!… Well that’s not the case.

During the migration process, we realized that the shift from Client-Side Rendering (CSR) to Server-Side Rendering (SSR) requires a more robust infrastructure to handle the increased server-side processing. SSR relies on server resources to generate and deliver the initial HTML content to clients, resulting in higher server load and increased infrastructure costs.

As our website experienced a surge in traffic and user engagement, the demand on our servers also increased significantly. This led to higher resource utilization and, subsequently, increased infrastructure expenses. The additional computational power required to handle the server-side rendering process, coupled with the need for more scalable server configurations, resulted in elevated costs compared to our previous CSR setup.

Lets Dig this further, APM might have some clue about the issue

Having spend almost a decade and working on JSP/Groovy kind of pages in our earlier days, we were somehow not convinced about the spike on infrastructure spend and somehow we were sure that something is wrong somewhere. Carefully looking at APM tool, we figured out that there were lots of File IOPS were happening while processing the request and this was somehow attributing to higher CPU utilization and hence the bigger servers, at the same time there were slight increase in latency after migration on server side too. (Perceived performance on client side was much better though and improved significantly though)

File IOPS ? something is fishy for sure…

After further digging it was clear that Next.js is reading critical CSS from disk every time when processing the request. Holy crap.. why are they doing it, why are not they using any caching at first place.

This brings us to the point that if we can provide some way to cache Critical CSS and our problem would be solved. To our surprise there is no way till current version of Next.js to provide the support of caching for critical css.

But wait, another twist here is that Next.js is dependent on underlying Critters framework which is actually reading the file from hard disk. So lets understand the Critical CSS, Critters framework and how we solved the problem from here.

Understanding Critical CSS and Underlying Challenge:

Before we delve into caching, let’s briefly understand what critical CSS is and how it impacts page rendering. Critical CSS refers to the subset of CSS required to render the above-the-fold content of a web page. In other words, it encompasses the styles necessary for the initial viewable area of a page. By delivering critical CSS as early as possible, we can eliminate render-blocking resources and provide a faster initial page load.

What is Critters framework?

Critters is a powerful library specifically designed for extracting and inlining critical CSS. As a part of the Next.js ecosystem, it seamlessly integrates into our development workflow. Critters dynamically analyze our components and extract the relevant CSS, ensuring only the necessary styles are loaded initially. By reducing network requests, we can achieve faster-perceived performance.

Caching Critical CSS:

Now, let’s explore how we can further optimize the process by caching the critical CSS generated by Critters. By default, Critters generates the critical CSS dynamically on every server-side rendering (SSR) request. While this approach works well for dynamic content, it can introduce unnecessary overhead by regenerating the CSS repeatedly.

To mitigate this, we can leverage server-side caching mechanisms. By storing the critical CSS in a cache layer, subsequent requests for the same page can be served directly from the cache, eliminating the need for regeneration. This caching strategy significantly reduces the time spent on generating the critical CSS, resulting in improved performance.

Implementation Steps:

  • Set up a caching layer: Implemented a caching mechanism such as node-cache to store the generated critical CSS. These caching solutions offer high-performance key-value stores suitable for our purpose.
  • Enable Critters and Caching in Next.js: Add a new key called cacheCriticalCSS in the experimental optimizeCss property, and set its value to true:
//next.config.js
module.exports = {
  experimental: { optimizeCss: { cacheCriticalCSS: true } },
};
  • Create a Cache Key: Generate a unique cache key for each web page based on its URL or any other relevant identifier. This key will be used to store and retrieve the critical CSS from the cache. Pass this key to a top-level parent component in the data attribute "data-pagetype={uniqueKey}".
render() {
    return (
      <div className="page_layout_nxt" data-pagetype={uniqueKey}>
        {
          this.props.children
        }
      </div>
    )
  }
  • Generate and cache critical CSS: Modified the existing Critters implementation to check the cache first on the basis of the key provided in the previous step before generating the critical CSS. If the CSS is found in the cache, serve it directly. Otherwise, generate the CSS using Critters and store it in the cache with the same unique key for future use.
  • Cache invalidation: To ensure consistency, implemented a mechanism to invalidate the cache whenever the CSS or component structure changes. This can be achieved by clearing the cache at the time of new production build deployment.

Benefits and Impact:

By caching critical CSS generated by Critters, Healthkart unlocked several benefits for our Next.js applications:

  • Improved performance: Caching eliminates the need for generating critical CSS on every SSR request, resulting in faster response times and enhanced user experience.
  • Reduced Latency: Latency refers to the time it takes for data to travel between the user’s browser and the server. Caching critical CSS helps reduce latency by minimizing the number of requests needed to fetch CSS files.
  • Improved LCP: A faster LCP means that the main content of your web page becomes visible to users more quickly. By caching critical CSS, we have reduced the rendering time, resulting in a faster LCP. This improvement enhances the user experience by providing a more responsive and visually appealing website.
  • Reduced server load: With cached CSS, server resources are freed up, allowing them to handle other requests efficiently.
  • Scalability: Caching critical CSS enables our applications to handle higher traffic loads without compromising performance, making them more scalable and resilient.

And Yes this brings us to a big Cost Saving !!!!

The below sheet summarize the whooping ~65% on servers running cost of node servers.

Conclusion:

If you have recently migrated to Next Js framework for implementing SSR pages for your website, please do consider looking into above specified things, this might save your few hundred bucks for sure.

The above content is based on our experience with working on above problem statement and your experience might vary. Please feel free comment out with your feedback.

Photo by Lautaro Andreani on Unsplash

Leave a comment