When you first set off in building a website or application, your index.html file is as performant as you will ever get.
Yes, you can set up caching, HTTP/2, and other server-side optimizations, but ultimately, you’ll be serving an index.html file to your users that starts out empty and gradually builds up over time as you ship features, fix bugs, add third-party scripts, and adjust a host of other interesting things that come over the lifetime of a project.
All this change can result in a “death by a thousand cuts” for the performance of your site. The pages start to load slower, it feels laggy, and your user engagement drops. You need to find some ways to get back to the snappy performance you once had, but how?
One of the ways you can improve the performance of your site is by inlining your CSS. This article explains the “what” and the “how” of inlining CSS by asking and answering a series of questions.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Before we get into what inlining CSS means and how to do it, it’s important to first understand how CSS can affect performance. While JavaScript and images generally play a much larger role in negatively impacting performance metrics, CSS can also play a significant role.
When a browser visits a URL, the first thing it gets back from the server is the HTML file. It then begins parsing this file, stopping to request any linked dependencies that it finds along the way. It checks the document’s <head> first, looking for anything necessary before beginning into the <body> to render the page.
CSS is treated as a render-blocking resource — that is, if you include any form of <link href="style.css" rel="stylesheet"> in the document’s <head>, the browser will trigger an additional request to the server to retrieve that stylesheet before even beginning to render your page for the user.
While our internet providers, infrastructure, and servers are all getting faster every year, there is no such thing as a free lunch. Every network request to the server has a time cost, and the requests required to complete before the browser begins rendering the page are the most expensive.
In its simplest form, inlining CSS is an approach to avoiding or deferring this second (or more) network request from being required before the user can begin to visually see anything on the page.
Before trying to improve anything in regards to performance on your site, it’s important to measure the current performance first. Pick some metrics to measure (there are some good ideas of what here) and get a baseline of how your site is performing.
While all the content in this article and others is theoretically true, things always work differently in practice. So measure first, try some things, measure again, and if it’s not giving you the results you expected, then revert. If you measure positive results after implementing some changes, then you have a nice success story to share with your team or manager.
Inlining is the practice of integrating a portion of code directly into the place where it will be used, eliminating the need for the computer to do a function call or some other type of lookup. This makes the code faster.
While the inlining terminology originally comes from the C programming language, the concept and the name have found their ways to web development. In the case of CSS, the performance gain comes from inlining styles directly into the HTML document, eliminating the need for the browser to do a network request before getting the render-blocking styles.
If you conduct any research on inlining CSS, you might find some suggestions to inline styles in this way:
<p style="font-size: 20px; font-weight: bold;">Some text</p>
This is indeed true inlining, as there is no need for a network request to fetch an external style sheet or even the need for the browser to apply a CSS class name to the element. However, you should almost never inline CSS in this way as it ironically introduces certain performance losses when runtime responsiveness is the unit of measure.
Here are some reasons that you should almost always avoid inlining CSS in this way:
This goes back to the concept of CSS being a render-blocking resource. While this is commonly considered in the context of network requests, it can also be the amount of time the browser takes to parse through and understand all the CSS.
Say you’d like to avoid loading an external CSS stylesheet and therefore inline each element in the DOM individually. You’ll likely end up with many duplicated styles that could either be atomic CSS class names or regular class names for a certain type of element. As one developer’s benchmark indicates, inline-styling every HTML element significantly affects the time to first contentful paint.
Banging the drum of “don’t repeat yourself” is neither necessary nor helpful in every case, but for inlining CSS, you will save yourself a lot of headaches if every element isn’t styled individually. Yes, there are ways you could define styles as a reusable object and spread those styles into each element, but at some point, having each element individually styled is going make a future refactor more difficult than it needs to be.
If duplication of styles wasn’t enough warning to “turn back now,” then this is a good time to remember that inline styles have high specificity and therefore can only be overwritten by use of the !important declaration.
For the sake of painting a complete picture, inlining CSS in this way is not always harmful to performance or maintainability.
Imagine some JavaScript is adding or removing elements dynamically from the DOM as the user interacts with the site. In this case, the browser has already finished its downloading, parsing, and rendering tasks, and performance is (mostly) no longer an issue. But in the case for inlining CSS that will be present in the DOM when the browser loads the page, inlining in this way should be avoided.
While the previous section discouraged a form of inlining CSS, there is another method of inlining that is much more performant in terms of runtime responsiveness. Instead of linking to a external CSS file like this:
<head> ... <link href="styles.css" rel="stylesheet"> </head>
You can directly include the styles (or a portion of them) in the document head like this:
<head>
...
<style type="text/css">
body{background:#fff;color:#000;margin:0}.link{color:#1a0dab}.ts{border-collapse:collapse}.ts td{padding:0}.g{line-height:1.2;text-align:left;width:600px}.ti{display:inline;display:inline-table}
</style>
</head>
This solves the problem of requiring the browser to send additional network requests before being able to render the page and also solves the problem of trying to inline-style each individual element. We can now style all the links in the page by just giving the element a link class <a href="some-link" class="link">Some link</a> as you might normally do.
Success! We’re now able to reduce the number of network requests while giving the DOM the styles it needs to render as quickly as possible. All good, right?
Almost.
Software development is all about tradeoffs. In the pursuit of better performance, we need to determine what tradeoff we’re willing to make. We eliminated a network request (good) but we just transferred those kilobytes to the HTML file and made it a bigger download (bad).
There’s no such thing as a free lunch! There is some balance to be made in accepting the overhead of having a larger HTML file in order to eliminate a synchronous network request, but at some point, if you put too many styles into the head, your performance metrics will actually suffer.
The observation to make here is that inlining CSS makes things more performant for metrics such as first contentful paint when we only inline critical CSS in the document head (with the key word being critical). Your site’s stylesheet might contain many, many more styles that the user will see when they first load your page.
For example, the styles for your footer will not be necessary when the browser first paints the site. Maybe only the header, hero, and some content styles are needed for that first paint. What is displayed in the browser’s viewport on the initial load is what can be considered as “critical”.
Going back to the discussion of tradeoffs, eliminating network requests, and making sure the HTML file doesn’t get too big, it’s worth mentioning that there is some advice given around the idea of having a size limit of 14KB for all the critical resources for your page. The following three links are helpful in case you’d like to dig deeper into this idea:
Ultimately, it’s important in terms of inlining CSS and performance that you don’t just dump all the CSS for your site into the head. If you inline too much, the performance implications will be worse than what you started with.
So we’ve now seen that inlining critical CSS can have a positive performance impact for our site. We’ve removed the link to the external stylesheet from the document head, figured out what our “critical” CSS is, and included it as an inline style in the head, but the rest of the page still needs styles. How can we style the rest of the page?
We can save the rest of the (non-critical) styles for the page in an external stylesheet and defer its download until the browser finishes with the render-blocking work, like this:
<head>
...
<style type="text/css">
/* inlined styles */
</style>
<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
</head>
Note that it is also possible to request the external stylesheet with JavaScript and add it to the DOM. However, by using a traditional link, the browser is able to store the response in its memory cache, which is something more difficult for a JavaScript solution to achieve. If the media type print in the link reads strangely to you, I’d encourage you to read this article, which explains how this works and how this approach is even more preferable than using a link prefetch.
With that, we now have all the styles necessary for the whole page but with the critical styles inlined and the non-critical loaded asynchronously shortly afterwards. The only difference your users will be able to notice is that the page seems to load quicker!
Let’s be honest: the idea of inlining critical CSS and preloading the rest seems fairly straightforward if your project consists of a page or two. Just do a bit of investigation to see what classes are first visible on different device aspect ratios, inline those, and preload the rest.
But add hundreds of different pages and a whole team of developers all working on different parts and you can quickly see how keeping track of critical and non-critical styles can be cumbersome, if not impossible. We would be greatly helped by some tooling here.
Did you know that Chrome has a tool to find unused JavaScript and CSS code? This coverage feature is quite manual but can help you discover what styles on your page are unused when you first load the page and then scroll or click around.
There is a project called Penthouse that generates critical CSS for your webpages. The author of this tool also runs a paid service on top of Penthouse with automation and other features.
Addy Osmani has a tool called Critical that extracts and inlines critical-path CSS from HTML. He also maintains a list of other tools for critical CSS, which is well worth diving into.
Ultimately, tooling for such a task really depends on the framework, build process, and other libraries you are using to develop your site with. I’d encourage you to peruse Addy’s list of tools and see if any of them could work in your stack.
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.
LogRocket lets you replay user sessions, eliminating guesswork around why bugs happen by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks.
LogRocket's Galileo AI watches sessions for you, instantly identifying and explaining user struggles with automated monitoring of your entire product experience.
Modernize how you debug web and mobile apps — start monitoring for free.

Memory leaks in React don’t crash your app instantly, they quietly slow it down. Learn how to spot them, what causes them, and how to fix them before they impact performance.

Build a CRUD REST API with Node.js, Express, and PostgreSQL, then modernize it with ES modules, async/await, built-in Express middleware, and safer config handling.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the March 25th issue.

Discover a practical framework for redesigning your senior developer hiring process to screen for real diagnostic skill.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now