Web Performance Calendar

The speed geek's favorite time of year
2024 Edition
ABOUT THE AUTHOR
Erwin Hofman

Erwin Hofman (LinkedIn) moved from building his own fast CMS to helping e-commerce agencies and merchants with web performance optimization as a Google Developer Expert. He is also the co-founder of RUMvision, providing merchants with real-time, detailed Core Web Vitals insights.

Nearly one in five websites is secretly sabotaging its own performance through a single CSS feature that’s been known to be problematic for over a decade. Based on our analysis of over 16 million mobile websites, there’s a good chance you might recognize this in your own codebase πŸ˜‰

The culprit? CSS @import. While its performance impact has been documented for years, our research shows it’s still widely used, particularly through third-party services and font providers. This article will explore what the numbers are, why it matters and what you can do about it.

How it all started

So here’s how this investigation began. Paul Calvano asked this question to extend his Performance.now() talk:

Can anyone think of other web performance mistakes that you suspect might be occurring on a lot of sites?

Paul Calvano on LinkedIn

That got me thinking! I reached out to Paul to propose a collaboration. Paul was kind enough to use his time to query the mobile November 2024 dataset of HTTP Archive to provide me with detailed @import data of millions of websites.

We’ll examine where @import is used the most and how this well-known but persistent performance issue continues to impact sites, particularly through third-party services like font providers. Whether you’re validating your existing knowledge, learning to spot new optimization opportunities, or questioning performance assumptions, we’ll explore real-world usage patterns and their impact on several metrics. First Contentful Paint (FCP) is the first metric to be impacted and noticed by your real visitors, so that’s what we’ll be focusing on.

From the past to the present

I chose to write about @import because even today I continue to encounter it in third-party resources during pagespeed audits. While I’m not the first to write about this issue, I hope this article will raise awareness about its performance impact.

The past

The performance impact of CSS @import was first documented by Steve Souders in 2009. In 2018, Harry Roberts reinforced this warning, stating:

@import, by virtue of how it works, is slow. It’s really, really bad for Start Render performance. This is because we’re actively creating more roundtrips on the Critical Path

Present

Nearly one in five websites (18.86%) still use CSS @import directives in their stylesheets. The breakdown is striking:

  • 15.16% (2.47 million sites) use it for third-party resources
  • 5.50% (894,000 sites) use it for first-party stylesheets
  • Some sites use both, hence the total of 18.86%

Understanding @import’s performance impact

When a CSS @import rule appears in your stylesheet, it triggers a cascade of events that impact your page’s performance. Let’s understand exactly what happens in the browser when it encounters an @import rule, why it causes delays, and what real-world data tells us about its impact.

Browser Support and implementation

While @import is supported across all modern browsers, its implementation comes with strict rules. The most critical requirement: an `@import` rule must be defined at the top of the stylesheet, before any other CSS rules.

How not to implement

Here’s a real-world example of what certainly not to do, found on the Brazilian government website:

.dsgov {
    @import "~flatpickr/dist/flatpickr.min.css";
    @import "~intro.js/minified/introjs.min.css";
} 

This implementation fails for two reasons:

  1. First, the @import rules aren’t at the top of the stylesheet.
  2. More importantly, they’re nested inside a CSS selector block.

Either the developer believed these vendor files would only apply to .dsgov elements (they won’t), or something went wrong in the build process. Whatever the cause, these imports won’t work at all.

How to implement (but please don’t)

Let’s say our first render-blocking stylesheet is called importing.css which then imports a stylesheet called imported.css.
When using the code example provided by MDN, we would then get the following:

@import url("imported.css");
* {
  margin: 0;
  padding: 0;
}
/* more styles */

Just to be sure: DO NOT USE THIS in your stylesheets β€” which is the whole point of this article πŸ˜‰

The waterfall effect

When a browser encounters an @import rule, it must:

  1. Discover importing.css in your HTML and then download and parse it
  2. Discover the @import rule in importing.css
  3. Start downloading the imported CSS file imported.css
  4. Parse the imported CSS file
  5. Continue with the rendering process

A question could be: why would this cause pagespeed issues while using two or even more stylesheets via <link rel="stylesheet" …> typically isn’t a big deal? And the answer is simple:

  • When putting a stylesheet in your initial stylesheet, that initial stylesheet needs to be downloaded first to allow a browser to learn about that second stylesheet.
  • When using <link rel="stylesheet" …> your stylesheet ends up being parser and render-blocking as well, but it can (and often will) still be detected by the preload scanner so that when your first stylesheet is done downloading, all other stylesheets are likely downloaded by that time as well.

I’ve created a few demos to demonstrate this, where each stylesheet contains a maximum of 5 CSS properties across two different selectors (<body> and <a>) and is smaller than 0.2 kB.

As I think WebPageTest is both accessible (easy to use by others) and convenient in spotting first issues, I used WebPageTest for my demos. So let’s start with a WPT Waterfall legend to see what different lines and (especially render-blocking) symbols mean:

default.html: This default setup has one render-blocking stylesheet called default.css. Start Render happens at 1.2 seconds.

importing.html: This setup has one render-blocking stylesheet importing.css that imports yet another stylesheet (imported.css). See how Start Render is being pushed back as the result of the nature of @import: it’s now at the 1.5-second mark.

incorrect-importing.html: This setup has one render-blocking stylesheet incorrect-importing.css that imports yet another stylesheet (imported.css), but is used in a wrong way as the @import rule was placed at the end of the stylesheet. We can see that our imported stylesheet isn’t even downloaded.

Let’s dive slightly deeper in the first demo, where we deliberately introduced a correctly implemented @import to cause two roundtrips. This confirms that the browser is only discovering our imported.css once the initial stylesheet is fully parsed:



this timeline shows that imported.css is only requested once the initial CSS was parsed

RUM gains from removing @import

In both of the discussed cases, the commercial version of RUMvision was used; a real-time pagespeed monitoring solution built on top of the official web-vitals library and allowing site owners to segment data into different buckets. The possibility to segment was convenient in our research.

Case 1: Vipio

Vipio.com is a site that used both Google Fonts and Typekit, and both got @import‘ed from within their self-hosted render-blocking stylesheet. As a matter of fact, they were importing Typekit that was importing yet another Typekit CSS file, resulting in 3 roundtrips. More details about this implementation can be found further down this article.

In 2022, I advised them to prevent these third parties from being render blocking. As a result, mobile P80 FCP dropped from 2782 to 1872 ms for cold (uncached) pageloads by real visitors. In other words, a 32.7% improvement.

However, it might be more interesting to look at the firstByteToFCP, which is the delta between TTFB and the first contentful paint (FCP). This way, we remove any TTFB fluctuations from our data and get to see the pure win from removing @import. Below are the results, again for cold (uncached) pageloads.

Metric Before (ms) After (ms) Change (%)
FCP delta 1995 1177 -41%
FCP 2782 1872 -32.7%

Aside from cold pageloads, all other pageviews ended up benefitting from this change as well. The firstByteToFCP saw a 30% improvement while FCP saw a 20%. See all raw numbers on github.

Case 2: WooCommerce

In a more recent case, a WooCommerce used only one @import And only for a search widget that wasn’t even visible initially. As TTFB was still a challenge within this site, I only looked at the firstByteToFCP to see progress from my recommendation. We saw a 37.1% improvement after removing a single @import that embedded a Google Font.

gains from removing @import on mobile P75, excluding Safari browsers

Running the HTTP Archive numbers

Now that we know why we shouldn’t be using @import in our stylesheets (and how to fix it), let’s find out how many sites are still using it:

Paul’s analysis of over 16 million websites reveals a significant presence of CSS @import usage across the web. Nearly 19% of all sites employ @import directives in their stylesheets, with a clear distinction between third-party and first-party usage patterns.

CSS Imports Sites Percent of Sites
Total sites in dataset 16,257,083 100%
Third Party 2,465,041 15.16%
First Party 893,989 5.50%
All Imports 3,065,307 [*] 18.86%

[*] Some sites might be importing both third- and first-party stylesheets at the same time, resulting in 15.16 + 5.50 not being equal to 18.86

Third-party CSS imports dominate the landscape, with 15.16% of sites (approximately 2.47 million) pulling in external stylesheets through @import directives. This suggests a heavy reliance on third-party resources, often including font services, CSS frameworks, or widget styles.

In contrast, first-party CSS imports appear in 5.5% of sites (around 894,000), indicating that while some developers use @import for organizing their own stylesheets, it’s far less common than third-party imports.

I think that the difference between the two numbers is caused by code snippets that can be found online and are easy to copy and paste, as well as some frameworks (Laravel) and libraries (Typekit) using @import by default for third parties.

Most common usage: web fonts

The analysis of @import usage across the web reveals a clear dominance of font-related services. Just the top 3 are clearly related to fonts that are used by 2 million sites alone:

  1. fonts.googleapis.com for Google Fonts, used by 1,688,694 sites
  2. p.typekit.net for Adobe fonts, used by 294,396 sites
  3. use.fontawesome.com for icon fonts, used by 65,481 sites
Top 15 most imported third-party stylesheets ordered by the amount of sites (blue) and requests (green).

See all 5000 results or get the query that was used.

Several interesting patterns emerge when looking at the top 15 most frequently imported hostnames:

Google Fonts usage patterns

Looking at the amount of requests for fonts.googleapis.com (3,053,944) and dividing it by the amount of unique sites using it (1,688,694), each site using fonts.googleapis.com is embedding 1.8 Google Font stylesheets on average.

Google Fonts has achieved remarkable market penetration, with 52.69% of all analyzed websites using the third-party service. The implementation methods break down as follows:

  • 77.8% use the recommended <link>
    element approach
  • 22.2% (approximately 1.9 million sites) use the suboptimal @import method

See query and raw numbers on github. Google Fonts imports via first-party a.k.a. self-hosted CSS files were not part of this research.

In case you really don’t want to self-host Google Fonts but do want to mitigate the impact of loading a third-party stylesheet, be sure to read Harry’s fastest Google Fonts article.

Adobe Typekit usage patterns

Typekit is Adobe’s hosted web font service. When it comes to embedding Typekit via CSS, using @import as amongst their suggested embed strategies – without any warning about its performance impact:

@import url("https://use.typekit.net/xxxxxxx.css");

This implementation is particularly harmful because a stylesheet from use.typekit.net will trigger yet another CSS import from p.typekit.net. Jake Archibald documented this behavior in his analysis of F1 websites (see lines 2 and 12 in particular).

Even if you use the recommended <link rel="stylesheet" ...> for the initial typekit stylesheet, Typekit will still impact your FCP (and likely LCP), while p.typekit.net only serves primarily tracking purposes.

The good news? Only 3.73% of all 16 million analyzed domains (606,074 sites) embed resources from use.typekit.net. Of those sites, just 7.33% use @import for embedding. See query and raw numbers on githubbased on this query.

Typekit content type analysis

Is their JavaScript implementation more popular? We can answer this by analyzing Typekit.net resources by hostname and content-type via another HTTP Archive query.

Looking at the most common combinations, then yes, JavaScript is slightly more popular:

resource type use.typekit.net p.typekit.net
css 280,302 296,838
font 542,093
script 305,595

Here’s what this tells us: You can embed Typekit fonts via CSS or JavaScript. The JavaScript approach is essentially a wrapper that still downloads CSS from use.typekit.net. That stylesheet then downloads another stylesheet from p.typekit.net and embeds the actual fonts. Browsers only download fonts when they confirm usage in the DOM.

This explains why p.typekit.net has more CSS file downloads than use.typekit.net. And since multiple font variants can be used in a single CSS file, font downloads from use.typekit.net are significantly higher.

Earlier hostname data showed p.typekit.net is imported across 294,396 sites, meaning 0.8% do not import from p.typekit.net. This percentage might include developers aware of the FCP penalty from @import who developed custom solutions to mitigate the impact.

CSS versus JavaScript implementation

So, some third parties, like Typekit and FontAwesome, offer a JavaScript implementation. However:

  • TypeKit JS might not introduce an @import in a render blocking way, but it will introduce JavaScript and thus more main thread blocking.
  • The JS alternative for FontAwesome does come with a different implementation. And they have a dedicated page about performance related to each implementation.

Nevertheless, I would always choose a CSS implementation as it is easier to both add to and remove from a site. Moreover, technical debt will be less compared to JS equivalents: When forgetting to remove a Google Font stylesheet, a browser won’t download nor apply the font files when you don’t actively use the font anymore. In the case of JS, it will continue to block the main thread if you forgot to remove it.
But if you do end up using CSS, be sure not to use @import!

Detect and prevent

To summarize this chapter: The best tip is to just not use @import in your stylesheets. Instead, remove the following from your stylesheets:

@import url("imported.css");

And convert it into a link element:

<link rel="stylesheet" href="imported.css">

Detect

To detect @import usage, you can analyze waterfalls yourselves or use the search feature in DevTools to search for @import across all resources.

I sometimes see that the contents of an imported file are barely even used via the DevTools coverage panel. In those cases, it’s safe to remove the @import and stylesheet altogether. Do note, though, that the Coverage panel will flag your @font-face declarations as unused, while in reality it is being used.

In those cases, you could check the DevTools network panel and look for referenced font files. If you’re using the Google Fonts service, you could use a JavaScript snippet that I created to view Google Font usage. This could help decision-making: for example, using a different font-family instead of continuing to embed a stylesheet to style a single element or removing an entire stylesheet when it’s not actively used anymore.

The HTML fix

Sometimes, getting rid of an @import isn’t an option if you’re not in control of the stylesheets. Let’s look at those scenarios.

Preload strategy

If you really can’t remove @import, use this:

<link rel="preload" href="imported.css" as="style">

Optimal Typekit implementation

When dealing with Typekit, your hands are tied even more. The following will then be your best approach (from a pagespeed perspective) to continue using their licensed fonts:

<!-- Allow preload scanner to detect the main file in time, 
	but prevent it from being render-blocking -->
<link rel="preload" href="https://use.typekit.net/xxxxxxx.css" as="style">
<link href="https://use.typekit.net/xxxxxxx.css" rel="stylesheet">

<!-- Fonts will be downloaded from the use.typekit.net as well, 
	so preconnect (with crossorigin as we're dealing with font files) -->
<link rel="preconnect" href="https://use.typekit.net" crossorigin>
<!-- The CSS will download yet another CSS from t.typekit.net, 
	so preconnect (but without crossorigin) -->
<link rel="preconnect" href="https://p.typekit.net">

Conclusion

While CSS @import remains widely used, especially for third-party resources like web fonts, it introduces unnecessary performance penalties. The web performance community has known about @import‘s impact for over a decade. Now with real data showing its continued widespread use, it’s time to finally retire this pattern and embrace more performant alternatives.

And hey, since this article is published on Christmas Eve – consider this your early Christmas present: an 18.8% chance to give your users the gift of better performance! πŸŽ„ Maybe checking your codebase for @import can be one of your New Year’s resolutions? πŸ˜‰

Leave a Reply

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
And here's a tool to convert HTML entities