When working on Core Web Vitals, it’s easy to overthink performance. In reality, many slow pages suffer from the same fundamental issues. This case study documents how I improved a slow Largest Contentful Paint by addressing render-blocking resources, without changing the design or user experience.
The Initial Situation
Lighthouse flagged a large opportunity under Eliminate render-blocking resources, with an estimated saving of more than six seconds. The page felt slow despite having a reasonable server response time.
Looking at the HTML head made the issue obvious:
<link rel="stylesheet" href="/css/styles.css">
<script src="/js/script.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
All of these resources were loaded synchronously. Until they finished downloading and parsing, the browser could not render anything.
Why This Hurt LCP
The LCP element lived inside the hero section at the top of the page. Because the CSS and JavaScript were blocking rendering, the hero could not be painted early, even though the HTML arrived quickly.
This created a poor user experience: a blank screen followed by a sudden layout appearance.
Step 1: Extracting and Inlining Critical CSS
The biggest improvement came from isolating the styles needed for the initial viewport. These included the header layout, hero container, typography, and spacing.
Instead of loading everything at once, I inlined only what was required to render the first screen:
<head>
<style>
header {
display: flex;
align-items: center;
height: 64px;
}
.hero {
min-height: 70vh;
display: flex;
align-items: center;
}
.hero h1 {
font-size: 2.5rem;
line-height: 1.2;
}
</style>
The rest of the stylesheet was then loaded asynchronously:
<link rel="preload" href="/css/styles.css" as="style" onload="this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="/css/styles.css">
</noscript>
This allowed the browser to render meaningful content almost immediately.
Step 2: Deferring JavaScript Safely
The JavaScript file handled navigation interactions and analytics. None of it was required for the initial paint.
Originally:
<script src="/js/script.js"></script>
After the change:
<script src="/js/script.js" defer></script>
With defer, the browser could continue parsing HTML and rendering the page while the script loaded in parallel. Execution happened only after the DOM was ready, which avoided layout blocking.
Step 3: Optimizing Google Fonts
Fonts were contributing a noticeable delay. The page relied on Google Fonts with multiple weights.
First, I added connection hints:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
Then, I moved to self-hosting the font and reduced the number of weights:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-semibold.woff2') format('woff2');
font-display: swap;
}
This removed external dependencies and ensured text was visible immediately.
Step 4: Preloading the LCP Image
The LCP element was a large hero image. Without hints, the browser discovered it too late.
To fix this, I explicitly preloaded the image:
<link rel="preload" as="image" href="/images/hero.webp" fetchpriority="high">
And ensured the image itself was not lazy-loaded:
<img src="/images/hero.webp" width="1200" height="800" decoding="async">
This helped the browser prioritize the most important visual asset on the page.
Results After Optimization
After applying these changes:
-
The render-blocking resources warning disappeared
-
LCP improved significantly
-
First Contentful Paint became visibly faster
-
The page felt responsive from the first moment
Most importantly, the layout and behavior of the site remained exactly the same.
What This Case Study Reinforced
This project reaffirmed that the most effective Core Web Vitals improvements usually come from fundamentals. Critical CSS, deferred JavaScript, font optimization, and proper LCP handling consistently deliver real-world gains.
Before introducing complex tooling or framework-level changes, it’s worth ensuring these basics are handled correctly. In many cases, they are all you need.