Each internet efficiency story is analogous, isn’t it? It all the time begins with the long-awaited web site overhaul. A day when a undertaking, absolutely polished and punctiliously optimized, will get launched, rating excessive and hovering above efficiency scores in Lighthouse and WebPageTest. There’s a celebration and a wholehearted sense of accomplishment prevailing within the air — fantastically mirrored in retweets and feedback and newsletters and Slack threads.
But as time passes by, the thrill slowly fades away, and pressing changes, much-needed options, and new enterprise necessities creep in. And all of the sudden, earlier than you recognize it, the code base will get slightly bit obese and fragmented, third-party scripts must load just a bit bit earlier, and glossy new dynamic content material finds its approach into the DOM via the backdoors of fourth-party scripts and their uninvited visitors.
We’ve been there at Smashing as nicely. Not many individuals realize it however we’re a really small group of round 12 individuals, lots of whom are working part-time and most of whom are often sporting many various hats on a given day. Whereas efficiency has been our objective for almost a decade now, we by no means actually had a devoted efficiency group.
After the newest redesign in late 2017, it was Ilya Pukhalski on the JavaScript facet of issues (part-time), Michael Riethmueller on the CSS facet of issues (just a few hours per week), and yours actually, enjoying thoughts video games with essential CSS and attempting to juggle just a few too many issues.

Because it occurred, we misplaced observe of efficiency within the busyness of day-to-day routine. We have been designing and constructing issues, organising new merchandise, refactoring the parts, and publishing articles. So by late 2020, issues bought a bit uncontrolled, with yellowish-red Lighthouse scores slowly displaying up throughout the board. We needed to repair that.
That’s The place We Have been
A few of you would possibly know that we’re running on JAMStack, with all articles and pages saved as Markdown recordsdata, Sass recordsdata compiled into CSS, JavaScript cut up into chunks with Webpack and Hugo constructing out static pages that we then serve instantly from an Edge CDN. Again in 2017 we constructed your entire website with Preact, however then have moved to React in 2019 — and use it together with just a few APIs for search, feedback, authentication and checkout.
All the website is built with progressive enhancement in thoughts, which means that you just, expensive reader, can learn each Smashing article in its entirety with out the necessity to boot the appliance in any respect. It’s not very shocking both — ultimately, a broadcast article doesn’t change a lot over time, whereas dynamic items reminiscent of Membership authentication and checkout want the appliance to run.
All the construct for deploying round 2500 articles stay takes round 6 minutes in the mean time. The construct course of by itself has turn into fairly a beast over time as nicely, with essential CSS injects, Webpack’s code splitting, dynamic inserts of promoting and have panels, RSS (re)era, and eventual A/B testing on the sting.
In early 2020, we’ve began with the massive refactoring of the CSS format parts. We by no means used CSS-in-JS or styled-components, however as an alternative a very good ol’ component-based system of Sass-modules which might be compiled into CSS. Again in 2017, your entire format was constructed with Flexbox and rebuilt with CSS Grid and CSS Customized Properties in mid-2019. Nonetheless, some pages wanted particular therapy as a result of new promoting spots and new product panels. So whereas the format was working, it wasn’t working very nicely, and it was fairly tough to keep up.
Moreover, the header with the primary navigation needed to change to accommodate for extra gadgets that we needed to show dynamically. Plus, we needed to refactor some incessantly used parts used throughout the positioning, and the CSS used there wanted some revision as nicely — the publication field being essentially the most notable perpetrator. We began off by refactoring some parts with utility-first CSS however we by no means bought to the purpose that it was used persistently throughout your entire website.
The bigger challenge was the giant JavaScript bundle that — not very surprisingly — was blocking the main-thread for tons of of milliseconds. A giant JavaScript bundle may appear misplaced on {a magazine} that merely publishes articles, however really, there’s loads of scripting occurring behind the scenes.
We have now varied states of parts for authenticated and unauthenticated clients. As soon as you’re signed in, we need to present all merchandise within the remaining worth, and as you add a e book to the cart, we need to preserve a cart accessible with a faucet on a button — it doesn’t matter what web page you’re on. Promoting wants to return in shortly with out inflicting disruptive format shifts, and the identical goes for the native product panels that spotlight our merchandise. Plus a service employee that caches all static property and serves them for repeat views, together with cached variations of articles {that a} reader has already visited.
So all of this scripting needed to occur at some level, and it was draining on the studying expertise even though the script was coming in fairly late. Frankly, we have been painstakingly engaged on the positioning and new parts with out holding a detailed eye on efficiency (and we had just a few different issues to remember for 2020). The turning level got here unexpectedly. Harry Roberts ran his (wonderful) Web Performance Masterclass as a web based workshop with us, and all through your entire workshop, he was utilizing Smashing for example by highlighting points that we had and suggesting options to these points alongside helpful instruments and tips.
All through the workshop, I used to be diligently taking notes and revisiting the codebase. On the time of the workshop, our Lighthouse scores have been 60–68 on the homepage, and round 40-60 on article pages — and clearly worse on cell. As soon as the workshop was over, we set to work.
Figuring out The Bottlenecks
We frequently are inclined to depend on specific scores to get an understanding of how nicely we carry out, but too usually single scores don’t present a full image. As David East eloquently noted in his article, internet efficiency isn’t a single worth; it’s a distribution. Even when an internet expertise is closely and totally an optimized all-around efficiency, it could possibly’t be simply quick. It is perhaps quick to some guests, however in the end it is going to even be slower (or gradual) to some others.
The explanations for it are quite a few, however crucial one is a large distinction in community situations and machine {hardware} the world over. Most of the time we will’t actually affect these issues, so we’ve to make sure that our expertise accommodates them as an alternative.
In essence, our job then is to extend the proportion of snappy experiences and reduce the proportion of sluggish experiences. However for that, we have to get a correct image of what the distribution really is. Now, analytics instruments and efficiency monitoring instruments will present this information when wanted, however we seemed particularly into CrUX, Chrome Person Expertise Report. CrUX generates an outline of efficiency distributions over time, with visitors collected from Chrome customers. A lot of this information associated to Core Net Vitals which Google has introduced again in 2020, and which additionally contribute to and are uncovered in Lighthouse.

We observed that throughout the board, our efficiency regressed dramatically all year long, with specific drops round August and September. As soon as we noticed these charts, we may look again into a number of the PRs we’ve pushed stay again then to review what has really occurred.
It didn’t take some time to determine that simply round these instances we launched a brand new navigation bar stay. That navigation bar — used on all pages — relied on JavaScript to show navigation gadgets in a menu on faucet or on click on, however the JavaScript little bit of it was really bundled inside the app.js bundle. To enhance Time To Interactive, we determined to extract the navigation script from the bundle and serve it inline.
Across the similar time we switched from an (outdated) manually created essential CSS file to an automatic system that was producing essential CSS for each template — homepage, article, product web page, occasion, job board, and so forth — and inline essential CSS in the course of the construct time. But we didn’t actually understand how a lot heavier the mechanically generated essential CSS was. We needed to discover it in additional element.
And in addition across the similar time, we have been adjusting the internet font loading, attempting to push internet fonts extra aggressively with useful resource hints reminiscent of preload. This appears to be backlashing with our efficiency efforts although, as internet fonts have been delaying rendering of the content material, being overprioritized subsequent to the total CSS file.
Now, one of many widespread causes for regression is the heavy price of JavaScript, so we additionally seemed into Webpack Bundle Analyzer and Simon Hearne’s request map to get a visible image of our JavaScript dependencies. It seemed fairly wholesome firstly.

A number of requests have been coming to the CDN, a cookie consent service Cookiebot, Google Analytics, plus our inner providers for serving product panels and customized promoting. It didn’t appear as if there have been many bottlenecks — till we seemed a bit extra intently.
In efficiency work, it’s widespread to have a look at the efficiency of some essential pages — almost certainly the homepage and almost certainly just a few article/product pages. Nonetheless, whereas there is just one homepage, there is perhaps loads of varied product pages, so we have to decide ones which can be consultant of our viewers.
In reality, as we’re publishing fairly just a few code-heavy and design-heavy articles on SmashingMag, over time we’ve accrued actually hundreds of articles that contained heavy GIFs, syntax-highlighted code snippets, CodePen embeds, video/audio embeds, and nested threads of endless feedback.
When introduced collectively, lots of them have been inflicting nothing wanting an explosion in DOM measurement together with extreme most important thread work — slowing down the expertise on hundreds of pages. To not point out that with promoting in place, some DOM parts have been injected late within the web page’s lifecycle inflicting a cascade of favor recalculations and repaints — additionally costly duties that may produce lengthy duties.
All of this wasn’t displaying up within the map we generated for a fairly light-weight article web page within the chart above. So we picked the heaviest pages we had — the almighty homepage, the longest one, the one with many video embeds, and the one with many CodePen embeds — and determined to optimize them as a lot as we may. In spite of everything, if they’re quick, then pages with a single CodePen embed must be sooner, too.
With these pages in thoughts, the map seemed slightly bit in another way. Be aware the massive thick line heading to the Vimeo participant and Vimeo CDN, with 78 requests coming from a Smashing article.

To check the affect on the primary thread, we took a deep-dive into the Efficiency panel in DevTools. Extra particularly, we have been on the lookout for duties that last more than 50ms (highlighted with a purple rectangle in the precise higher nook) and duties that comprise Recalculation kinds (purple bar). The primary would point out costly JavaScript execution, whereas the latter would expose fashion invalidations brought on by dynamic injections of content material within the DOM and suboptimal CSS. This gave us some actionable pointers of the place to start out. For instance, we shortly found that our internet font loading had a big repaint price, whereas JavaScript chunks have been nonetheless heavy sufficient to dam the primary thread.

As a baseline, we seemed very intently at Core Web Vitals, attempting to make sure that we’re scoring nicely throughout all of them. We selected to focus particularly on gradual cell gadgets — with gradual 3G, 400ms RTT and 400kbps switch pace, simply to be on the pessimistic facet of issues. It’s not shocking then that Lighthouse wasn’t very proud of our website both, offering absolutely strong purple scores for the heaviest articles, and tirelessly complaining about unused JavaScript, CSS, offscreen photos and their sizes.

As soon as we had some information in entrance of us, we may deal with optimizing the three heaviest article pages, with a deal with essential (and non-critical) CSS, JavaScript bundle, lengthy duties, internet font loading, format shifts and third-party-embeds. Later we’d additionally revise the codebase to take away legacy code and use new fashionable browser options. It appeared like a number of work forward of was, and certainly we have been fairly busy for the months to return.
Enhancing The Order Of Property In The <head>
Mockingly, the very very first thing we seemed into wasn’t even intently associated to all of the duties we’ve recognized above. Within the efficiency workshop, Harry spent a substantial period of time explaining the order of property within the <head>
of every web page, making some extent that to ship essential content material shortly means being very strategic and attentive about how property are ordered within the supply code.
Now it shouldn’t come as a giant revelation that essential CSS is useful for internet efficiency. Nonetheless, it did come as a little bit of a shock how a lot distinction the order of all the opposite property — useful resource hints, internet font preloading, synchronous and asynchronous scripts, full CSS and metadata — has.
We’ve turned up your entire <head>
the other way up, putting essential CSS earlier than all asynchronous scripts and all preloaded property reminiscent of fonts, photos and so forth. We’ve damaged down the property that we’ll be preconnecting to or preloading by template and file sort, in order that essential photos, syntax highlighting and video embeds shall be requested early just for a sure sort of articles and pages.
Usually, we’ve rigorously orchestrated the order within the <head>
, decreased the variety of preloaded property that have been competing for bandwidth, and targeted on getting essential CSS proper. Should you’d prefer to dive deeper into a number of the essential issues with the <head>
order, Harry highlights them within the article on CSS and Network Performance. This modification alone introduced us round 3–4 Lighthouse rating factors throughout the board.
Transferring From Automated Crucial CSS Again To Handbook Crucial CSS
Transferring the <head>
tags round was a easy a part of the story although. A harder one was the era and administration of critical CSS recordsdata. Again in 2017, we manually handcrafted essential CSS for each template, by accumulating all the kinds required to render the first 1000 pixels in peak throughout all display screen widths. This after all was a cumbersome and barely uninspiring job, to not point out upkeep points for taming a complete household of essential CSS recordsdata and a full CSS file.
So we seemed into choices on automating this course of as part of the construct routine. There wasn’t actually a scarcity of instruments accessible, so we’ve examined just a few and determined to run just a few assessments. We’ve managed to set them up and working fairly shortly. The output appeared to be ok for an automatic course of, so after just a few configuration tweaks, we plugged it in and pushed it to manufacturing. That occurred round July–August final yr, which is properly visualized within the spike and efficiency drop within the CrUX information above. We saved going backwards and forwards with the configuration, usually having troubles with easy issues like including specifically kinds or eradicating others. E.g. cookie consent immediate kinds that aren’t actually included on a web page except the cookie script has initialized.
In October, we’ve launched some main format adjustments to the positioning, and when trying into the essential CSS, we’ve run into precisely the identical points but once more — the generated end result was fairly verbose, and wasn’t fairly what we needed. In order an experiment in late October, all of us bundled our strengths to revisit our essential CSS method and research how a lot smaller a handcrafted essential CSS can be. We took a deep breath and spent days across the code protection instrument on key pages. We grouped CSS guidelines manually and eliminated duplicates and legacy code in each locations — the essential CSS and the primary CSS. It was a much-needed cleanup certainly, as many kinds that have been written again in 2017–2018 have turn into out of date over time.
Because of this, we ended up with three handcrafted essential CSS recordsdata, and with three extra recordsdata which can be presently work in progress:
The recordsdata are inlined within the head of every template, and in the mean time they’re duplicated within the monolithic CSS bundle that incorporates every little thing ever used (or probably not used anymore) on the positioning. In the mean time, we’re trying into breaking down the total CSS bundle into just a few CSS packages, so a reader of the journal wouldn’t obtain kinds from the job board or e book pages, however then when reaching these pages would get a fast render with essential CSS and get the remainder of the CSS for that web page asynchronously — solely on that web page.
Admittedly, handcrafted essential CSS recordsdata weren’t a lot smaller in measurement: we’ve decreased the dimensions of essential CSS recordsdata by round 14%. Nonetheless, they included every little thing we would have liked in the precise order from prime to complete with out duplicates and overriding kinds. This appeared to be a step in the precise route, and it gave us a Lighthouse increase of one other 3–4 factors. We have been making progress.
Altering The Net Font Loading
With font-display
at our fingertips, font loading appears to be an issue previously. Sadly, it isn’t fairly proper in our case. You, expensive readers, appear to go to a variety of articles on Smashing Journal. You additionally incessantly return again to the positioning to learn a one more article — maybe just a few hours or days later, or maybe per week later. One of many points that we had with font-display
used throughout the positioning was that for readers who moved between articles so much, we observed loads of flashes between the fallback font and the online font (which shouldn’t usually occur as fonts can be correctly cached).
That didn’t really feel like a good consumer expertise, so we seemed into choices. On Smashing, we’re utilizing two most important typefaces — Mija for headings and Elena for physique copy. Mija is available in two weights (Common and Daring), whereas Elena is coming in three weights (Common, Italic, Daring). We dropped Elena’s Daring Italic years in the past in the course of the redesign simply because we used it on just some pages. We subset the opposite fonts by eradicating unused characters and Unicode ranges.
Our articles are largely set in textual content, so we’ve found that more often than not on the positioning the Largest Contentful Paint is both the primary paragraph of textual content in an article or the photograph of the writer. That implies that we have to take further care of making certain that the primary paragraph seems shortly in a fallback font, whereas gracefully altering over to the online font with minimal reflows.
Take a detailed take a look at the preliminary loading expertise of the entrance web page (slowed down 3 times):
We had 4 main objectives when determining an answer:
- On the very first go to, render the textual content instantly with a fallback font;
- Match font metrics of fallback fonts and internet fonts to reduce format shifts;
- Load all internet fonts asynchronously and apply them unexpectedly (max. 1 reflow);
- On subsequent visits, render all textual content instantly in internet fonts (with none flashing or reflows).
Initially, we really tried to make use of font-display: swap on font-face
. This appeared to be the only possibility, nevertheless, as talked about above, some readers will go to a variety of pages, so we ended up with a number of flickering with the six fonts that we have been rendering all through the positioning. Additionally, with font-display alone, we couldn’t group requests or repaints.
One other concept was to render every little thing in fallback font on the preliminary go to, then request and cache all fonts asynchronously, and solely on subsequent visits ship internet fonts straight from the cache. The problem with this method was that a variety of readers are coming from search engines like google and yahoo, and at the least a few of them will solely see that one web page — and we didn’t need to render an article in a system font alone.
So what’s then?
Since 2017, we’ve been utilizing the Two-Stage-Render approach for internet font loading which mainly describes two phases of rendering: one with a minimal subset of internet fonts, and the opposite with an entire household of font weights. Again within the day, we created minimal subsets of Mija Daring and Elena Common which have been essentially the most incessantly used weights on the positioning. Each subsets embody solely Latin characters, punctuation, numbers, and some particular characters. These fonts (ElenaInitial.woff2 and MijaInitial.woff2) have been very small in measurement — usually simply round 10–15 KBs in measurement. We serve them within the first stage of font rendering, displaying your entire web page in these two fonts.

We achieve this with a Font Loading API which supplies us details about which fonts have loaded efficiently and which haven’t but. Behind the scenes, it occurs by including a category .wf-loaded-stage1 to the physique, with kinds rendering the content material in these fonts:
.wf-loaded-stage1 article,
.wf-loaded-stage1 promo-box,
.wf-loaded-stage1 feedback {
font-family: ElenaInitial,sans-serif;
}
.wf-loaded-stage1 h1,
.wf-loaded-stage1 h2,
.wf-loaded-stage1 .btn {
font-family: MijaInitial,sans-serif;
}
As a result of font recordsdata are fairly small, hopefully they get via the community fairly shortly. Then because the reader can really begin studying an article, we load full weights of the fonts asynchronously, and add .wf-loaded-stage2 to the physique:
.wf-loaded-stage2 article,
.wf-loaded-stage2 promo-box,
.wf-loaded-stage2 feedback {
font-family: Elena,sans-serif;
}
.wf-loaded-stage2 h1,
.wf-loaded-stage2 h2,
.wf-loaded-stage2 .btn {
font-family: Mija,sans-serif;
}
So when loading a web page, readers are going to get a small subset internet font shortly first, after which we change over to the total font household. Now, by default, these switches between fallback fonts and internet fonts occur randomly, based mostly on no matter comes first via the community. That may really feel fairly disruptive as you have got began studying an article. So as an alternative of leaving it to the browser to resolve when to modify fonts, we group repaints, decreasing the reflow affect to the minimal.
/* Loading internet fonts with Font Loading API to keep away from a number of repaints. With assist by Irina Lipovaya. */
/* Credit score to preliminary work by Zach Leatherman: https://noti.st/zachleat/KNaZEg/the-five-whys-of-web-font-loading-performance#sWkN4u4 */
// If the Font Loading API is supported...
// (If not, we follow fallback fonts)
if ("fonts" in doc) {
// Create new FontFace objects, one for every font
let ElenaRegular = new FontFace(
"Elena",
"url(/fonts/ElenaWebRegular/ElenaWebRegular.woff2) format('woff2')"
);
let ElenaBold = new FontFace(
"Elena",
"url(/fonts/ElenaWebBold/ElenaWebBold.woff2) format('woff2')",
{
weight: "700"
}
);
let ElenaItalic = new FontFace(
"Elena",
"url(/fonts/ElenaWebRegularItalic/ElenaWebRegularItalic.woff2) format('woff2')",
{
fashion: "italic"
}
);
let MijaBold = new FontFace(
"Mija",
"url(/fonts/MijaBold/Mija_Bold-webfont.woff2) format('woff2')",
{
weight: "700"
}
);
// Load all of the fonts however render them directly
// if they've efficiently loaded
let loadedFonts = Promise.all([
ElenaRegular.load(),
ElenaBold.load(),
ElenaItalic.load(),
MijaBold.load()
]).then(outcome => {
outcome.forEach(font => doc.fonts.add(font));
doc.documentElement.classList.add('wf-loaded-stage2');
// Used for repeat views
sessionStorage.foutFontsStage2Loaded = true;
}).catch(error => {
throw new Error(`Error caught: ${error}`);
});
}
Nonetheless, what if the primary small subset of fonts isn’t coming via the community shortly? We’ve observed that this appears to be occurring extra usually than we’d prefer to. In that case, after a timeout of 3s expires, fashionable browsers fall again to a system font (in our font stack it might be Arial), then change over to ElenaInitial or MijaInitial, simply to be converted to full Elena or Mija respectively later. That produced only a bit an excessive amount of flashing at our tasting. We have been excited about eradicating the primary stage render just for gradual networks initially (through Community Data API), however then we determined to take away it altogether.
So in October, we eliminated the subsets altogether, together with the intermediate stage. Every time all weights of each Elena and Mija fonts are efficiently downloaded by the shopper and able to be utilized, we provoke stage 2 and repaint every little thing directly. And to make reflows even much less noticeable, we spent a little bit of time matching fallback fonts and internet fonts. That largely meant making use of barely totally different font sizes and line heights for parts painted within the first seen portion of the web page.
For that, we used font-style-matcher
and (ahem, ahem) just a few magic numbers. That’s additionally the explanation why we initially went with -apple-system and Arial as world fallback fonts; San Francisco (rendered through -apple-system) appeared to be a bit nicer than Arial, but when it’s not accessible, we selected to make use of Arial simply because it’s widely spread across most OSes.
In CSS, it might appear to be this:
.article__summary {
font-family: -apple-system,Arial,BlinkMacSystemFont,Roboto Slab,Droid Serif,Segoe UI,Ubuntu,Cantarell,Georgia,sans-serif;
font-style: italic;
/* Warning: magic numbers forward! */
/* San Francisco Italic and Arial Italic have bigger x-height, in comparison with Elena */
font-size: 0.9213em;
line-height: 1.487em;
}
.wf-loaded-stage2 .article__summary {
font-family: Elena,sans-serif;
font-size: 1em; /* Unique font-size for Elena Italic */
line-height: 1.55em; /* Unique line-height for Elena Italic */
}
This labored pretty nicely. We do show textual content instantly, and internet fonts are available in on the display screen grouped, ideally inflicting precisely one reflow on the primary view, and no reflows altogether on subsequent views.
As soon as the fonts have been downloaded, we retailer them in a service employee’s cache. On subsequent visits we first verify if the fonts are already within the cache. If they’re, we retrieve them from the service employee’s cache and apply them instantly. And if not, we begin throughout with the fallback-web-font-switcheroo.
This resolution decreased the variety of reflows to a minimal (one) on comparatively quick connections, whereas additionally holding the fonts persistently and reliably within the cache. Sooner or later, we sincerely hope to exchange magic numbers with f-mods. Maybe Zach Leatherman would be proud.
Figuring out And Breaking Down The Monolithic JS
Once we studied the primary thread within the DevTools’ Efficiency panel, we knew precisely what we would have liked to do. There have been eight Lengthy Duties that have been taking between 70ms and 580ms, blocking the interface and making it non-responsive. Usually, these have been the scripts costing essentially the most:
- uc.js, a cookie immediate scripting (70ms)
- fashion recalculations brought on by incoming full.css file (176ms) (the essential CSS doesn’t comprise kinds under the 1000px peak throughout all viewports)
- promoting scripts working on load occasion to handle panels, purchasing cart, and so forth. + fashion recalculations (276ms)
- internet font change, fashion recalculations (290ms)
- app.js analysis (580ms)
We targeted on those that have been most dangerous first — so-to-say the longest Lengthy Duties.

The primary one was occurring as a result of costly format recalculations brought on by the change of the fonts (from fallback font to internet font), inflicting over 290ms of additional work (on a quick laptop computer and a quick connection). By eradicating stage one from the font loading alone, we have been in a position to acquire round 80ms again. It wasn’t ok although as a result of have been approach past the 50ms price range. So we began digging deeper.
The principle cause why recalculations occurred was merely due to the massive variations between fallback fonts and internet fonts. By matching the line-height and sizes for fallback fonts and internet fonts, we have been in a position to keep away from many conditions when a line of textual content would wrap on a brand new line within the fallback font, however then get barely smaller and match on the earlier line, inflicting main change within the geometry of your entire web page, and consequently large format shifts. We’ve performed with letter-spacing
and word-spacing
as nicely, however it didn’t produce good outcomes.
With these adjustments, we have been in a position to lower one other 50-80ms, however we weren’t in a position to scale back it under 120ms with out displaying the content material in a fallback font and show the content material within the internet font afterwards. Clearly, it ought to massively have an effect on solely first time guests as consequent web page views can be rendered with the fonts retrieved instantly from the service employee’s cache, with out expensive reflows as a result of font change.
By the way in which, it’s fairly vital to note that in our case, we observed that the majority Lengthy Duties weren’t brought on by large JavaScript, however as an alternative by Format Recalculations and parsing of the CSS, which meant that we would have liked to do a little bit of CSS cleansing, particularly watching out for conditions when kinds are overwritten. Not directly, it was excellent news as a result of we didn’t must cope with advanced JavaScript points that a lot. Nonetheless, it turned out to not be easy as we’re nonetheless cleansing up the CSS this very day. We have been in a position to take away two Lengthy Duties for good, however we nonetheless have just a few excellent ones and fairly a technique to go. Luckily, more often than not we aren’t approach above the magical 50ms threshold.
The a lot larger challenge was the JavaScript bundle we have been serving, occupying the primary thread for a whopping 580ms. Most of this time was spent in booting up app.js which incorporates React, Redux, Lodash, and a Webpack module loader. The one approach to enhance efficiency with this large beast was to interrupt it down into smaller items. So we seemed into doing simply that.
With Webpack, we’ve cut up up the monolithic bundle into smaller chunks with code-splitting, about 30Kb per chunk. We did some bundle.json cleaning and model improve for all manufacturing dependencies, adjusted the browserlistrc setup to deal with the 2 newest browser variations, upgraded to Webpack and Babel to the newest variations, moved to Terser for minification, and used ES2017 (+ browserlistrc) as a goal for script compilation.
We additionally used BabelEsmPlugin to generate fashionable variations of current dependencies. Lastly, we’ve added prefetch hyperlinks to the header for all essential script chunks and refactored the service employee, migrating to Workbox with Webpack (workbox-webpack-plugin).

Bear in mind once we switched to the brand new navigation again in mid-2020, simply to see an enormous efficiency penalty because of this? The rationale for it was fairly easy. Whereas previously the navigation was simply static plain HTML and a little bit of CSS, with the brand new navigation, we would have liked a little bit of JavaScript to behave on opening and shutting of the menu on cell and on desktop. That was inflicting rage clicks whenever you would click on on the navigation menu and nothing would occur, and naturally, had a penalty price in Time-To-Interactive scores in Lighthouse.
We eliminated the script from the bundle and extracted it as a separate script. Moreover, we did the identical factor for different standalone scripts that have been used not often — for syntax highlighting, tables, video embeds and code embeds — and eliminated them from the primary bundle; as an alternative, we granularly load them solely when wanted.

Nonetheless, what we didn’t discover for months was that though we eliminated the navigation script from the bundle, it was loading after your entire app.js bundle was evaluated, which wasn’t actually serving to Time-To-Interactive (see picture above). We fastened it by preloading nav.js and deferring it to execute within the order of look within the DOM, and managed to save lots of one other 100ms with that operation alone. By the tip, with every little thing in place we have been in a position to deliver the duty to round 220ms.

We managed to get some enchancment in place, however nonetheless have fairly a technique to go, with additional React and Webpack optimizations on our to-do listing. In the mean time we nonetheless have three main Lengthy Duties — font change (120ms), app.js execution (220ms) and magnificence recalculations as a result of measurement of full CSS (140ms). For us, it means cleansing up and breaking apart the monolithic CSS subsequent.
It’s value mentioning that these outcomes are actually the best-scenario-outcomes. On a given article web page we’d have a lot of code embeds and video embeds, together with different third-party scripts and buyer’s browser extensions that might require a separate dialog.
Dealing With Third-Events
Luckily, our third-party scripts footprint (and the affect of their buddies’ fourth-party-scripts) wasn’t big from the beginning. However when these third-party scripts accrued, they’d drive efficiency down considerably. This goes particularly for video embedding scripts, but additionally syntax highlighting, promoting scripts, promo panels scripts and any exterior iframe embeds.
Clearly, we defer all of those scripts to start out loading after the DOMContentLoaded occasion, however as soon as they lastly come on stage, they trigger fairly a bit of labor on the primary thread. This exhibits up particularly on article pages, that are clearly the overwhelming majority of content material on the positioning.
The very first thing we did was allocating correct house to all property which can be being injected into the DOM after the preliminary web page render. It meant width
and peak
for all promoting photos and the styling of code snippets. We came upon that as a result of all of the scripts have been deferred, new kinds have been invalidating current kinds, inflicting large format shifts for each code snippet that was displayed. We fastened that by including the required kinds to the essential CSS on the article pages.
We’ve re-established a method for optimizing photos (ideally AVIF or WebP — nonetheless work in progress although). All photos under the 1000px peak threshold are natively lazy-loaded (with <img loading=lazy>
), whereas those on the highest are prioritized (<img loading=keen>
). The identical goes for all third-party embeds.
We changed some dynamic elements with their static counterparts — e.g. whereas a notice about an article saved for offline studying was showing dynamically after the article was added to the service employee’s cache, now it seems statically as we’re, nicely, a bit optimistic and count on it to be occurring in all fashionable browsers.
As of the second of writing, we’re making ready facades for code embeds and video embeds as nicely. Plus, all photos which can be offscreen will get decoding=async
attribute, so the browser has a free reign over when and the way it masses photos offscreen, asynchronously and in parallel.

To make sure that our photos all the time embody width and peak attributes, we’ve additionally modified Harry Roberts’ snippet and Tim Kadlec’s diagnostics CSS to spotlight every time a picture isn’t served correctly. It’s utilized in improvement and modifying however clearly not in manufacturing.
One approach that we used incessantly to trace what precisely is going on because the web page is being loaded, was slow-motion loading.
First, we’ve added a easy line of code to the diagnostics CSS, which supplies a noticeable define for all parts on the web page.
* {
define: 3px strong purple
}

* { define: 3px purple }
and observing the bins because the browser is rendering the web page. (Large preview)Then we report a video of the web page loaded on a gradual and quick connection. Then we rewatch the video by slowing down the playback and transferring again and ahead to establish the place large format shifts occur.
Right here’s the recording of a web page being loaded on a quick connection:
And right here’s the recording of a recording being performed to review what occurs with the format:
By auditing the format shifts this manner, we have been in a position to shortly discover what’s not fairly proper on the web page, and the place large recalculation prices are occurring. As you in all probability have observed, adjusting the line-height
and font-size
on headings would possibly go a protracted technique to keep away from giant shifts.
With these easy adjustments alone, we have been in a position to increase efficiency rating by a whopping 25 Lighthouse factors for the video-heaviest article, and acquire just a few factors for code embeds.
Enhancing The Expertise
We’ve tried to be fairly strategic in just about every little thing from loading internet fonts to serving essential CSS. Nonetheless, we’ve accomplished our greatest to make use of a number of the new applied sciences which have turn into accessible final yr.
We’re planning on utilizing AVIF by default to serve photos on SmashingMag, however we aren’t fairly there but, as lots of our photos are served from Cloudinary (which already has beta help for AVIF), however many are instantly from our CDN but we don’t actually have a logic in place simply but to generate AVIFs on the fly. That might have to be a handbook course of for now.
We’re lazy rendering a number of the offset parts of the web page with content-visibility: auto. For instance, the footer, the feedback part, in addition to the panels approach under the primary 1000px peak threshold, are all rendered later after the seen portion of every web page has been rendered.
We’ve performed a bit with hyperlink rel="prefetch"
and even hyperlink rel="prerender"
(NoPush prefetch) some elements of the web page which can be very doubtless for use for additional navigation — for instance, to prefetch property for the primary articles on the entrance web page (nonetheless in dialogue).
We additionally preload writer photos to cut back the Largest Contentful Paint, and a few key property which can be used on every web page, reminiscent of dancing cat photos (for the navigation) and shadow used for all writer photos. Nonetheless, all of them are preloaded provided that a reader occurs to be on a bigger display screen (>800px), though we’re trying into utilizing Community Data API as an alternative to be extra correct.
We’ve additionally decreased the dimensions of full CSS and all essential CSS recordsdata by eradicating legacy code, refactoring a variety of parts, and eradicating the text-shadow trick that we have been utilizing to realize good underlines with a mix of text-decoration-skip-ink and text-decoration-thickness (lastly!).
Work To Be Executed
We’ve spent a fairly vital period of time working round all of the minor and main adjustments on the positioning. We’ve observed fairly vital enhancements on desktop and a fairly noticeable increase on cell. In the mean time of writing, our articles are scoring on common between 90 and 100 Lighthouse rating on desktop, and round 65-80 on cell.


The rationale for the poor rating on cell is clearly poor Time to Interactive and poor Whole Blocking time as a result of booting of the app and the dimensions of the total CSS file. So there’s nonetheless some work to be accomplished there.
As for the following steps, we’re presently trying into additional decreasing the dimensions of the CSS, and particularly break it down into modules, equally to JavaScript, loading some elements of the CSS (e.g. checkout or job board or books/eBooks) solely when wanted.
We additionally discover choices of additional bundling experimentation on cell to cut back the efficiency affect of the app.js though it appears to be non-trivial in the mean time. Lastly, we’ll be trying into options to our cookie immediate resolution, rebuilding our containers with CSS clamp()
, changing the padding-bottom ratio approach with aspect-ratio
and looking out into serving as many photos as attainable in AVIF.
That’s It, People!
Hopefully, this little case-study shall be helpful to you, and maybe there are one or two strategies that you just would possibly be capable to apply to your undertaking instantly. In the long run, efficiency is all a few sum of all of the tremendous little particulars, that, when added up, make or break your buyer’s expertise.
Whereas we’re very dedicated to getting higher at efficiency, we additionally work on bettering accessibility and the content material of the positioning. So in the event you spot something that’s not fairly proper or something that we may do to additional enhance Smashing Journal, please tell us within the feedback to this text.
Lastly, in the event you’d prefer to keep up to date on articles like this one, please subscribe to our email newsletter for pleasant internet ideas, goodies, instruments and articles, and a seasonal choice of Smashing cats.
