TL;DR
Performance on the frontend is crucial for a positive user experience. Bad performance can lead to users leaving the application before they have a chance to see your app in action. Core web vitals measure the performance of a website. I recently updated my Texavor blog page to improve performance for both web and mobile.
Before: PageSpeed Insight
After: PageSpeed Insight
In this article, we will discuss the techniques we used to improve performance.
Understanding Core Web Vitals and Their Importance for SaaS
Core Web Vitals (CWV) are a set of web performance metrics. Google uses these metrics to determine which web pages are optimized to rank higher. Core Web Vitals have become one of the ranking factors for web pages. With the rise of AI agents, these pages are now read by bots in real time to fetch data. If your page takes a significant amount of time to load, it may be skipped by AI agents. This can reduce not only rankings but also citations. Along with that, good performance of a website encourages trust among the users and a better user experience. We are going to focus on the performance metrics of Core Web Vitals.
Key Metrics of the performance core vitals:
- First Contentful Paint (FCP): It measures the time taken for the first piece of content to appear on the screen.
- Largest Contentful Paint (LCP): It measures the time taken to load the largest contentful paint, which could be elements such as a card, image, text block, or code block, etc.
- Total Blocking Time (TBT): It measures the total time the browser’s main thread is blocked by long tasks between First Contentful Paint and Time to Interactive. The browser's main thread handles running JavaScript, rendering HTML and CSS, handling the DOM, and handling user input.
- Cumulative Layout Shift (CLS): It measures how much of the visible content moves unexpectedly while the page is loading.
- Speed Index: It measures how quickly the visible part of the page is displayed to the user during page load. FCP deals with the first element, and Speed Index deals with the visible area of the screen.
Now that we know what each metric measures, we can use this information to identify what might be causing different types of performance issues. Now, let's see what are the steps I took to improve the performance.
Using the latest version of Next.js
We were using Next.js 15 for the website. It was fast, but always using the latest stable version will give more advantage. We used the Next.js migration guide to move to Next.js 16. You can use the code below to automatically upgrade to the latest version:
npx @next/codemod@canary upgrade latest
The codemod does the following:
- Updating next.config.js to the new turbopak
- Migrate next lint to ESLint CLI
- Moved middleware to proxy
- Remove
unstable_prefix andexperimental_pprRoute segment config.
By updating the next.js version, we were able to use the React Compiler. It does automatic performance optimization without needing manual memoization. Previously, we were using React.memo, useMemo, and useCallback for memorization. With the React compiler, React automatically detects when a component should not re-render.
From CSR to SSG/ISR
We reduced the client-side rendering code by moving most of the code to the server. Previously, we were using the top-level use client for tracking scroll for the top scroll depth of the article. We defined the 'use client' at the top of the component that does other things too. This makes the whole component client-side. We pick all the components that were using the 'use client', identified which specific feature requires client rendering, and move that part of the code to a separate component to use 'use client' for that only specific feature.
We moved the heavy parsing job of converting markdown to html in the server. This has significantly improved the speed index of the website. Previously, it was 2.9 seconds, and now it is 1.2 seconds.
We use the built-in bundle analyzer to identify which bundle is taking larger sizes. You can use the command below to analyze:
npx next experimental-analyze
It does the following:
- Per Page Analysis: You can analyze each page individually for the bundle size and understand which page is heavier.
- Client-side and Server-side View: You can see the client-side and server-side analysis, too, for each bundle.
- Dependency Breakdown: It shows which libraries and modules take the most of the bundle size.
- Import Chain Insight: You can inspect the import chain to find insights on why a dependency was included.
Image Optimization
Largest Contentful Paint and Cumulative Layout Shift were the major bottlenecks of the performance previously. We identified that it was caused by the images. Here is the breakdown of both:
- High LCP: High LCP was due to the thumbnail image of the blog in mobile that was the largest element.
- High CLS: This was also causing due to the element shift of the author image while the image was loading.
We implemented more robust image optimization at both places. To reduce high LCP, we reduced the image quality. It was a thumbnail image whose quality can be reduced to improve loading speed. We reduced the image quality to 50. You can use the quality attribute with Next Image.
<Image
src={articleData?.image}
alt={`Cover image for ${articleData?.title}`}
fill
className="object-cover z-10"
priority
quality={50}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 75vw, 900px"
/>
For the higher CLS caused by the author's image. We added a placeholder prop to the Image. We used the placeholder="blur" . In this way, a placeholder image is always there.
<Image
src={articleData?.easywrite_author?.profile_pic}
alt=""
width={40}
height={40}
className="rounded-full object-cover"
priority
quality={50}
placeholder="blur"
blurDataURL={articleData?.easywrite_author?.profile_pic_smaller}
/>
Note: placeholder="blur" requires a blurDataURL. This should be different from the original src URL. It should be very small base64 blurred version.
Efficient CSS Architecture
After analyzing the bundle, we came to know that global.css was holding all the reusable CSS apart from tailwindcss. We were using certain text formatting and styles that were common to docs and blogs. We put both the CSS in the global.css. This significantly increases the size of the global.css file. It was getting imported into pages that were not necessary. We extracted all the article-specific CSS (Prose, Tables, Syntax Highlighting) from global.css to a dedicated file blog.css .
The Optimization: By only loading blog.css on the article pages, we reduced the CSS bundle size for the HomePage and other landing pages, improving First Contentful Paint (FCP) across the entire site. The bundle size also saw a 40% reduction for global.css.
Heavy Code Before:
// layout.tsx
import "./globals.css"; // Contained 500+ lines of Blog & Docs CSS
Optimized Code After:
// app/blog/[slug]/page.tsx
import "./blog.css"; // Only loaded for articles
export default async function ArticlePage() {
// ... content
}
Core Web Vitals & Performance Comparison
Below is the comparison of the metrics before and after optimization. The comparison is for the mobile device.
| Metric | Before Optimization (1:42 PM) | After Optimization (1:57 PM) | Improvement |
|---|---|---|---|
| First Contentful Paint | 1.2 s | 1.2 s | No Change |
| Largest Contentful Paint | 3.8 s (Orange) | 3.2 s (Orange) | 15.8% Faster |
| Total Blocking Time | 0 ms | 10 ms | +10 ms (Negligible) |
| Cumulative Layout Shift | 0.289 (Red) | 0 (Green) | 100% Stable |
| Speed Index | 2.9 s | 1.2 s | 58.6% Faster |
Frequently Asked Questions
What are Core Web Vitals, and why are they important for SaaS platforms?
Core web vitals measure the performance of a webpage, which includes loading speed, interactivity, and visual stability. For SaaS platforms, a better score can improve user experience, SEO ranking, and overall application reliability.
How does Next.js improve Core Web Vitals compared to other frameworks?
Next.js offers performance enhancement features such as server-side rendering, static side generation, image optimization, and efficient ode splitting.
What are the best practices for optimizing images in Next.js?
Use Next.js Image Component with proper sizes, optimized quality, and modern image formats like WebP. You can use placeholder="blur" to reduce Cumulative Layout Shift.
How does upgrading to Next.js 16 improve performance?
Upgrading to Next.js 16 allows the use of the React Compiler and newer optimizations, which reduce unnecessary re-renders and improve overall application performance.
Why is reducing client-side rendering important for performance?
Moving logic from client components to server components reduces the amount of JavaScript sent to the browser, improving metrics like Speed Index and load times.
Conclusion
Improving frontend performance often comes from a series of small but meaningful optimizations rather than a single large change. By upgrading to the latest Next.js, reducing unnecessary client-side rendering, optimizing images, and restructuring CSS loading, we were able to significantly improve key metrics like Largest Contentful Paint, Cumulative Layout Shift, and Speed Index.
Performance optimization should be treated as an ongoing process. Regularly analyzing your site using tools like PageSpeed Insights and monitoring Core Web Vitals helps identify new bottlenecks as your application evolves, ensuring a fast and reliable experience for users.
Top comments (0)