Saturday, October 11, 2025
HomeCSS🤝 Concatenate, 🗜️ Compress, 🗳️ Cache – Harry Roberts – Internet Efficiency Guide

🤝 Concatenate, 🗜️ Compress, 🗳️ Cache – Harry Roberts – Internet Efficiency Guide


Written by on CSS Wizardry.

Desk of Contents
  1. 🤝 Concatenate
  2. 🗜️ Compress
  3. 🗳️ Cache
  4. 📡 Connection
  5. 📱 Consumer
  6. My Recommendation
  7. The Future Is Brighter
    1. Shared Dictionary Compression for HTTP
    2. Compression Dictionaries
  8. tl;dr
  9. Appendix: Check Methodology

I started writing this text in early July 2023 however started to really feel a little bit
underwhelmed by it and so left it unfinished. Nevertheless, after latest and renewed
discussions
across the
relevance and usefulness of construct steps, I made a decision to mud it off and get it
completed.

Let’s go!

When serving and storing information on the internet, there are a selection of various
issues we have to think about with the intention to stability ergonomics,
efficiency, and effectiveness. On this publish, I’m going to interrupt these processes
down into every of:

  1. 🤝 Concatenating our information on the server: Are we going to ship many
    smaller information, or are we going to ship one monolithic file? The previous makes
    for an easier construct step, however is it quicker?
  2. 🗜️ Compressing them over the community: Which compression algorithm, if
    any, will we use? What’s the availability, configurability, and efficacy of
    every?
  3. 🗳️ Caching them on the different finish: How lengthy ought to we cache information on
    a consumer’s machine? And do any of our earlier selections dictate our choices?

🤝 Concatenate

Concatenation might be the trickiest bit to get proper as a result of, regardless that
the three Cs occur so as, selections we make later will affect
selections we make again right here. We have to suppose in each instructions proper now.

Again within the HTTP/1.1 world, we have been solely in a position to fetch six sources at a time
from a given origin. Given this limitation, it was advantageous to have fewer
information: if we wanted to obtain 18 information, that’s three separate chunks of labor;
if we may someway convey that quantity down to 6, it’s just one discrete chunk
of labor. This gave rise to heavy bundling and concatenation—why obtain three
CSS information (half of our funds) if we may compress them into one?

On condition that 66% of all
web sites
(and 77% of all
requests
) are operating
HTTP/2, I cannot talk about concatenation methods for HTTP/1.1 on this
article. In the event you are nonetheless operating HTTP/1.1, my solely recommendation is to improve to
HTTP/2.

With the introduction of HTTP/2, issues modified. As a substitute of being restricted to
solely six parallel requests to a given origin, we got the flexibility to open
a connection that may very well be reused infinitely. Instantly, we may make much more
than six requests at a time, so bundling and concatenation turned far much less
related. An anti sample, even.

Or did it?

It seems H/2 acts extra like H/1.1 than you may
suppose…

As an experiment, I took the CSS Wizardry homepage and crudely
added Bootstrap. In a single take a look at, I concatenated all of it into one huge file, and the
different had the library break up into 12 information. I’m measuring when the final
stylesheet arrives, which is denoted by the vertical purple line. It will
be known as css_time.

Learn the whole take a look at methodology.

Plotted on the identical horizontal axis of 1.6s, the waterfalls communicate for
themselves:

201ms of cumulative latency; 109ms of cumulative obtain. (View full measurement.)

With one big file, we obtained a 1,094ms css_time and transferred 18.4KB of
CSS
.

4,362ms of cumulative latency; 240ms of cumulative obtain. (View full measurement.)

With many small information, as ‘really useful’ in HTTP/2-world, we obtained a 1,524ms
css_time
and transferred 60KB of CSS. Put one other approach, the HTTP/2 approach
was about 1.4× slower and about 3.3× heavier.

What may clarify this phenomenon?

Once we speak about downloading information, we—usually talking—have two issues to
think about: latency and bandwidth. Within the waterfall charts above, we discover we
have each mild and darkish inexperienced within the CSS responses: the sunshine inexperienced might be
thought of latency, whereas the darkish inexperienced is after we’re truly downloading
knowledge. As a rule, latency stays fixed whereas obtain time is proportional to
filesize. Discover simply how way more mild inexperienced (particularly in comparison with darkish) we
see within the many-files model of Bootstrap in comparison with the one-big-file.

This isn’t a brand new phenomenon—a shopper of mine suffered the identical drawback in
July
, and the Khan
Academy bumped into the identical
situation
in 2015!

If we take some quite simple figures, we will quickly mannequin the purpose with numbers…

Say we’ve one file that takes 1,000ms to obtain with 100ms of
latency
. Downloading this one file takes:

(1 × 1000ms) + (1 × 100ms) = 1,100ms

Let’s say we chunk that file into 10 information, thus 10 requests every taking
a tenth of a second, now we’ve:

(10 × 100ms) + (10 × 100ms) = 2,000ms

As a result of we added ‘9 extra cases of latency’, we’ve pushed the general time
from 1.1s to 2s.

In our particular examples above, the one-big-file sample incurred 201ms of
latency, whereas the many-files strategy accrued 4,362ms by comparability.
That’s virtually 22× extra!

It’s price noting that, for probably the most half, the rise is parallelised,
so whereas it quantities to 22× extra total latency, it wasn’t back-to-back.

It will get worse. As compression favours bigger information, the general measurement of the ten
smaller information will likely be better than the unique one file. Add to that the
browser’s scheduling mechanisms
, we’re
unlikely to dispatch all 10 requests on the similar time.

So, it appears to be like like one big file is quickest possibility, proper? What extra do we want
to know? We must always simply bundle every thing into one, no?

As I mentioned earlier than, we’ve just a few extra issues to juggle suddenly right here. We’d like
to be taught a little bit bit extra about the remainder of our setup earlier than we will make
a remaining choice about our concatenation technique.

🗜️ Compress

The above exams have been run with Brotli compression. What occurs after we
alter our compression technique?

As of
2022
,
roughly:

  • 28% of compressible responses have been Brotli encoded;
  • 46% have been Gzipped;
  • 25% have been, worryingly, not compressed in any respect.

What may every of those approaches imply for us?

Compression Bundling css_time (ms)
None One file 4,204
Many information 3,663
Gzip One file 1,190
Many information 1,485
Brotli One file 1,094
Many information 1,524
In the event you can’t compress your information, splitting them out is
quicker.

Considered a little bit extra visually:

(View full
measurement.)

These numbers inform us that:

  • at low (or no) compression, many smaller information is quicker than one massive
    one;
  • at medium compression, one massive file is marginally quicker than many
    smaller
    ones;
  • at greater compression, one massive file is markedly quicker than many smaller
    ones.

Principally, the extra aggressive your capability to compress, the higher you’ll fare
with bigger information. It is because, at current, algorithms like Gzip and Brotli
change into more practical the extra historic knowledge they need to play with. In different
phrases, bigger information compress greater than smaller ones.

This exhibits us the sheer energy and significance of compression, so guarantee you’ve gotten
one of the best setup potential on your infrastructure. In the event you’re not at the moment
compressing your textual content belongings, that could be a bug and wishes addressing. Don’t optimise
to a sub-optimal state of affairs.

This appears to be like like one other level in favour of serving one-big-file, proper?

🗳️ Cache

Caching is one thing I’ve been obsessive about
currently
, however for the
static belongings we’re discussing at present, we don’t must know a lot apart from:
cache every thing as aggressively as potential.

Every of your bundles requires a novel fingerprint, e.g. foremost.af8a22.css.
When you’ve achieved this, caching is a straightforward case of storing the file ceaselessly,
immutably:

Cache-Management: max-age=2147483648, immutable
  • max-age=2147483648: This
    directive
    instructs all caches
    to retailer the response for the utmost potential time. We’re all used to
    seeing max-age=31536000, which is one yr. That is completely cheap and
    sensible for nearly any static content material, but when the file actually is immutable,
    we’d as nicely shoot for ceaselessly. Within the 32-bit world, ceaselessly is
    2,147,483,648.
    seconds, or 68 years.
  • immutable: This
    directive
    instructs caches
    that the file’s content material will by no means change, and due to this fact to by no means trouble
    revalidating the file as soon as its max-age is met. You’ll be able to solely add this
    directive to responses which can be fingerprinted (e.g. foremost.af8a22.css)

All static belongings—offered they are fingerprinted—can safely carry such an
aggressive Cache-Management header as they’re very simple to cache bust. Which
brings me properly on to…

The necessary a part of this part is cache busting.

We’ve seen how heavily-concatenated information compress higher, thus obtain
quicker, however how does that have an effect on our caching technique?

Whereas monolithic bundles is likely to be quicker total for first-time visits, they
endure one big downfall: even a tiny, one-character change to the bundle would
require {that a} consumer redownload your entire file simply to entry one trivial
change. Think about having to fetch a several-hundred kilobyte CSS file throughout
once more for the sake of adjusting one hex code:

  .c-btn {
-   background-color: #C0FFEE;
+   background-color: #BADA55;
  }

That is the chance with monolithic bundles: discrete updates can carry loads of
redundancy. That is additional exacerbated if you happen to launch very steadily: whereas
caching for 68 years and releasing 10+ instances a day is completely secure, it’s loads
of churn, and we don’t wish to retransmit the identical unchanged bytes time and again
once more.

Subsequently, the simplest bundling technique would err on the facet of
as few bundles as potential to take advantage of compression and scheduling, however
sufficient bundles to separate out high- and low-rate of change elements of your codebase
in order to hit probably the most optimum caching technique. It’s a balancing act for certain.

📡 Connection

One factor we haven’t checked out is the impression of community speeds on these
outcomes. Let’s introduce a fourth CConnection.

I ran the entire exams over the next connection varieties:

  • 3G: 1.6 Mbps downlink, 768 Kbps uplink, 150ms RTT
  • 4G: 9 Mbps downlink, 9 Mbps uplink, 170ms RTT
  • Cable: 5 Mbps downlink, 1 Mbps uplink, 28ms RTT
  • Fibre: 20 Mbps downlink, 5 Mbps uplink, 4ms RTT
All variants start to converge on an analogous timing as community velocity
improves. (View full
measurement.)

This knowledge exhibits us that:

  1. the distinction between no-compression and any compression is huge, particularly
    at slower connection speeds;

    • the helpfulness of compression decreases as connection velocity will increase;
  2. many smaller information is quicker in any respect connection speeds if compression is
    unavailable;
  3. one huge file is quicker in any respect connection speeds so long as it’s compressed;
    • one huge file is simply marginally quicker than many small information over Gzip, however
      quicker nonetheless, and;
    • one huge file over Brotli is markedly quicker than many small information.

Once more, no compression shouldn’t be a viable possibility and ought to be thought of
a bug—please don’t design your bundling technique across the absence of
compression.

That is one other nod within the course of preferring fewer, bigger information.

📱 Consumer

There’s a fifth C! The Consumer.

All the pieces we’ve checked out to date has involved itself with community efficiency.
What about what occurs within the browser?

Once we run JavaScript, we’ve three foremost steps:

  1. Parse: the browser parses the JavaScript to create an AST.
  2. Compile: the parsed code is compiled into optimised bytecode.
  3. Execute: the code is now executed, and does no matter we wished it to do.

Bigger information will inherently have greater parse and compile instances, however aren’t
essentially slower to execute. It’s extra about what your JavaScript is doing
relatively than the dimensions of the file itself: it’s potential to jot down a tiny file that
has a far greater runtime value than a file 100 instances bigger.

The problem right here is extra about delivery an applicable quantity of code full-stop,
and fewer about the way it’s bundled.

For example, I’ve a shopper with a 2.4MB foremost bundle (sadly
that isn’t a typo) which takes lower than 10ms to compile on a mid-tier
cell machine.

My Recommendation

  • Ship as little as you will get away with within the first place.
    • It’s higher to ship no code than it’s to compress 1MB right down to 50KB.
  • In the event you’re operating HTTP/1.1, strive improve to HTTP/2 or 3.
  • In case you have no compression, get that mounted earlier than you do anything.
  • In the event you’re utilizing Gzip, strive improve to Brotli.
  • When you’re on Brotli, plainly bigger information fare higher over the
    community.

    • Go for fewer and bigger bundles.
  • The bundles you do find yourself with ought to, ideally, be based mostly loosely on charge or
    chance of change.

In case you have every thing in place, then:

  • Bundle infrequently-changing points of your app into fewer, bigger bundles.
  • As you encounter parts that seem much less globally, or change extra
    steadily, start splitting out into smaller information.
  • Fingerprint all of them and cache them
    ceaselessly
    .
  • Total, err on the facet of fewer bundles.

For instance:

<head>

  <script src=vendor.1a3f5b7d.js   sort=module></script>
  <script src=app.8e2c4a6f.js      sort=module></script>
  <script src=dwelling.d6b9f0c7.js     sort=module></script>
  <script src=carousel.5fac239e.js sort=module></script>

</head>
  • vendor.js is required by each web page and doubtless updates very
    sometimes: we shouldn’t power customers to redownload it any time we make
    a change to any first-party JS.
  • app.js can be wanted by each web page however in all probability updates extra usually than
    vendor.js: we should always in all probability cache these two individually.
  • dwelling.js is simply wanted on the house web page: there’s no level bundling it
    into app.js which might be fetched on each web page.
  • carousel.js is likely to be wanted just a few pages, however not sufficient to warrant
    bundling it into app.js: discrete adjustments to parts shouldn’t require
    fetching all of app.js once more.

The Future Is Brighter

The rationale we’re erring on the facet of fewer, bigger bundles is that
currently-available compression algorithms work by compressing a file in opposition to
itself. The bigger a file is, the extra historic knowledge there may be to compress
subsequent chunks of the file in opposition to, and as compression favours repetition,
the possibility of recurring phrases will increase the bigger the file will get. It’s form of
self-fulfilling.

Shared Dictionary Compression for HTTP

There was an try at compressing information in opposition to predefined, exterior
dictionaries in order that even small information would have a a lot bigger dataset obtainable
to be compressed in opposition to. Shared Dictionary Compression for HTTP (SDHC) was
pioneered by Google, and it labored by:

…utilizing pre-negotiated dictionaries to ‘heat up’ its inside state previous to
encoding or decoding. These might both be already saved domestically, or uploaded
from a supply after which cached.
SDHC

Sadly, SDHC was eliminated in Chrome 59 in
2017
. Had it labored out,
we’d have been in a position to forgo bundling years in the past.

Compression Dictionaries

Buddies Patrick Meenan and Yoav
Weiss
have restarted work on implementing
an SDCH-like exterior dictionary mechanism, however with much more sturdy
implementation to keep away from the problems encountered with earlier makes an attempt.

Whereas work could be very a lot in its infancy, it’s extremely thrilling. You’ll be able to learn
the explainer, or
the
Web-Draft

already. We will count on Origin Trials
as we communicate
.

The early
outcomes

of this work present nice promise, so this is one thing to sit up for, however
widespread and ubiquitous assist a approach off but…

tl;dr

Within the present panorama, bundling remains to be a really efficient technique. Bigger
information compress way more successfully and thus obtain quicker in any respect connection
speeds. Additional, queueing, scheduling, and latency work in opposition to us in
a many-file setup.

Nevertheless, one big bundle would restrict our capability to make use of an efficient caching
technique, so start to conservatively break up out into bundles which can be ruled
largely by how usually they’re more likely to change. Keep away from resending unchanged bytes.

Future platform options will pave the way in which for simplified construct steps, however even
one of the best compression on the planet gained’t sidestep the way in which HTTP’s scheduling
mechanisms work.

Bundling is right here to remain for some time.


Appendix: Check Methodology

To start with, I as making an attempt to proxy the efficiency of every by taking the
First Contentful Paint milestone. Nevertheless, within the spirit of measuring what
I impression, not what
I affect
,
I made a decision to lean on the Person Timing
API

and drop a efficiency.mark() after
the final stylesheet:

<hyperlink rel=stylesheet href=...>

<script>
  const css_time = efficiency.mark('css_time');
</script>

I can then decide this up in WebPageTest utilizing
their Customized Metrics:

[css_time]
return css_time.startTime

Now, I can append ?medianMetric=css_time to the WebPageTest outcome URL and
robotically view probably the most
consultant

of the take a look at runs. You may also see this knowledge in WebPageTest’s Plot Full
Outcomes
view:

For the one-big-file model, outliers have been pushing 1.5s. (View full measurement.)



Did this assist? We will do far more!



RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments