serving compromised or malicious //AN_Xml:payloads. It doesn’t bear thinking about!</p> //AN_Xml: //AN_Xml:<h3 id="mitigation-subresource-integrity">Mitigation: Subresource Integrity</h3> //AN_Xml: //AN_Xml:<p>To the credit of all of the providers referenced so far in this article, they do //AN_Xml:all make use of <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity">Subresource //AN_Xml:Integrity</a> //AN_Xml:(SRI). SRI is a mechanism by which the provider supplies a hash (technically, //AN_Xml:a hash that is then Base64 encoded) of the exact file that you both expect and //AN_Xml:intend to use. The browser can then check that the file you received is indeed //AN_Xml:the one you requested.</p> //AN_Xml: //AN_Xml:<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://code.jquery.com/jquery-3.4.1.slim.min.js"</span> //AN_Xml: <span class="na">integrity=</span><span class="s">"sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="</span> //AN_Xml: <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">&gt;&lt;AN_Scri&gt;</span></code></pre></figure> //AN_Xml: //AN_Xml:<p>Again, if you absolutely must link to an externally hosted static asset, make //AN_Xml:sure it’s SRI-enabled. You can add SRI yourself using <a href="https://www.srihash.org/">this handy //AN_Xml:generator</a>.</p> //AN_Xml: //AN_Xml:<h2 id="penalty-network-negotiation">Penalty: Network Negotiation</h2> //AN_Xml: //AN_Xml:<p>One of the biggest and most immediate penalties we pay is the cost of opening //AN_Xml:new TCP connections. Every new origin we need to visit needs a connection //AN_Xml:opening, and that can be very costly: DNS resolution, TCP handshakes, and TLS //AN_Xml:negotiation all add up, and the story gets worse the higher the latency of the //AN_Xml:connection is.</p> //AN_Xml: //AN_Xml:<p>I’m going to use an example taken straight from Bootstrap’s own <a href="https://getbootstrap.com/docs/4.3/getting-started/introduction/">Getting //AN_Xml:Started</a>. They //AN_Xml:instruct users to include these following four files:</p> //AN_Xml: //AN_Xml:<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"</span> <span class="na">integrity=</span><span class="s">"..."</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">&gt;</span> //AN_Xml:<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://code.jquery.com/jquery-3.3.1.slim.min.js"</span> <span class="na">integrity=</span><span class="s">"..."</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">&gt;&lt;AN_Scri&gt;</span> //AN_Xml:<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"</span> <span class="na">integrity=</span><span class="s">"..."</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">&gt;&lt;AN_Scri&gt;</span> //AN_Xml:<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"</span> <span class="na">integrity=</span><span class="s">"..."</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">&gt;&lt;AN_Scri&gt;</span></code></pre></figure> //AN_Xml: //AN_Xml:<p>These four files are hosted across three different origins, so we’re going to //AN_Xml:need to open three TCP connections. How much does that cost?</p> //AN_Xml: //AN_Xml:<p>Well, on a reasonably fast connection, hosting these static assets off-site is //AN_Xml:311ms, or 1.65×, slower than hosting them ourselves.</p> //AN_Xml: //AN_Xml:<figure> //AN_Xml:<img src="/wp-content/uploads/2019/05/wpt-off-site-cable.png" alt="" loading="lazy" /> //AN_Xml:<figcaption>By linking to three different origins in order to serve static //AN_Xml:assets, we cumulatively lose a needless 805ms to network negotiation. <a href="https://www.webpagetest.org/result/190531_FY_618f9076491312ef625cf2b1a51167ae/3/details/">Full //AN_Xml:test.</a></figcaption> //AN_Xml:</figure> //AN_Xml: //AN_Xml:<p>Okay, so not exactly terrifying, but Trainline, a client of mine, found that by //AN_Xml:reducing latency by 300ms, <a href="https://wpostats.com/2016/05/04/trainline-spending.html">customers spent an extra £8m //AN_Xml:a year</a>. This is //AN_Xml:a pretty quick way to make eight mill.</p> //AN_Xml: //AN_Xml:<figure> //AN_Xml:<img src="/wp-content/uploads/2019/05/wpt-self-hosted-cable.png" alt="" loading="lazy" /> //AN_Xml:<figcaption>By simply moving our assets onto the host domain, we completely //AN_Xml:remove any extra connection overhead. <a href="https://www.webpagetest.org/result/190531_FX_f7d7b8ae511b02aabc7fa0bbef0e37bc/3/details/">Full //AN_Xml:test.</a></figcaption> //AN_Xml:</figure> //AN_Xml: //AN_Xml:<p>On a slower, higher-latency connection, the story is much, much worse. Over 3G, //AN_Xml:the externally-hosted version comes in at an eye-watering <strong>1.765s slower</strong>. //AN_Xml:I thought this was meant to make our site faster?!</p> //AN_Xml: //AN_Xml:<figure> //AN_Xml:<img src="/wp-content/uploads/2019/05/wpt-off-site-3g.png" alt="" loading="lazy" /> //AN_Xml:<figcaption>On a high latency connection, network overhead totals a whopping //AN_Xml:5.037s. All completely avoidable. <a href="https://www.webpagetest.org/result/190531_XE_a95eebddd2346f8bb572cecf4a8dae68/3/details/">Full //AN_Xml:test.</a></figcaption> //AN_Xml:</figure> //AN_Xml: //AN_Xml:<p>Moving the assets onto our own infrastructure brings load times down from around //AN_Xml:5.4s to just 3.6s.</p> //AN_Xml: //AN_Xml:<figure> //AN_Xml:<img src="/wp-content/uploads/2019/05/wpt-self-hosted-3g.png" alt="" loading="lazy" /> //AN_Xml:<figcaption>By self-hosting our static assets, we don’t need to open any more //AN_Xml:connections. <a href="https://www.webpagetest.org/result/190531_ZF_4d76740567ec1eba1e6ec67acfd57627/1/details/">Full //AN_Xml:test.</a></figcaption> //AN_Xml:</figure> //AN_Xml: //AN_Xml:<p>If this isn’t already a compelling enough reason to self-host your static //AN_Xml:assets, I’m not sure what is!</p> //AN_Xml: //AN_Xml:<h3 id="mitigation-preconnect">Mitigation: <code class="language-plaintext highlighter-rouge">preconnect</code></h3> //AN_Xml: //AN_Xml:<p>Naturally, my whole point here is that you should not host any static assets //AN_Xml:off-site if you’re otherwise able to self-host them. However, if your hands are //AN_Xml:somehow tied, then you can use <a href="https://speakerdeck.com/csswizardry/more-than-you-ever-wanted-to-know-about-resource-hints?slide=28">a <code class="language-plaintext highlighter-rouge">preconnect</code> Resource //AN_Xml:Hint</a> //AN_Xml:to preemptively open a TCP connection to the specified origin(s):</p> //AN_Xml: //AN_Xml:<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;head&gt;</span> //AN_Xml: //AN_Xml: ... //AN_Xml: //AN_Xml: <span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"preconnect"</span> <span class="na">href=</span><span class="s">"https://code.jquery.com"</span> <span class="nt">/&gt;</span> //AN_Xml: //AN_Xml: ... //AN_Xml: //AN_Xml:<span class="nt">&lt;/head&gt;</span></code></pre></figure> //AN_Xml: //AN_Xml:<p>For bonus points, deploying these as <a href="https://andydavies.me/blog/2019/03/22/improving-perceived-performance-with-a-link-rel-equals-preconnect-http-header/">HTTP //AN_Xml:headers</a> //AN_Xml:will be even faster.</p> //AN_Xml: //AN_Xml:<p><strong>N.B.</strong> Even if you do implement <code class="language-plaintext highlighter-rouge">preconnect</code>, you’re still only going to make //AN_Xml:a small dent in your lost time: you still need to open the relevant connections, //AN_Xml:and, especially on high latency connections, it’s unlikely that you’re ever //AN_Xml:going to fully pay off the overhead upfront.</p> //AN_Xml: //AN_Xml:<h2 id="penalty-loss-of-prioritisation">Penalty: Loss of Prioritisation</h2> //AN_Xml: //AN_Xml:<p>The second penalty comes in the form of a protocol-level optimisation that we //AN_Xml:miss out on the moment we split content across domains. If you’re running over //AN_Xml:HTTP/2—which, by now, you should be—you get access to prioritisation. All //AN_Xml:streams (ergo, resources) within the same TCP connection carry a priority, and //AN_Xml:the browser and server work in tandem to build a dependency tree of all of these //AN_Xml:prioritised streams so that we can return critical assets sooner, and perhaps //AN_Xml:delay the delivery of less important ones.</p> //AN_Xml: //AN_Xml:<p><small>To fully understand the benefits of prioritisation, <a href="https://calendar.perfplanet.com/2018/http2-prioritization/">Pat Meenan’s //AN_Xml:post</a> on the topic //AN_Xml:serves as a good primer.</small></p> //AN_Xml: //AN_Xml:<p><small><strong>N.B.</strong> Technically, owing to H/2’s <a href="https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/">connection //AN_Xml:coalescence</a>, //AN_Xml:requests can be prioritised against each other over different domains as long as //AN_Xml:they share the same IP address.</small></p> //AN_Xml: //AN_Xml:<p>If we split our assets across multiple domains, we have to open up several //AN_Xml:unique TCP connections. We cannot cross-reference any of the priorities within //AN_Xml:these connections, so we lose the ability to deliver assets in a considered and //AN_Xml:well designed manner.</p> //AN_Xml: //AN_Xml:<p>Compare the two HTTP/2 dependency trees for both the off-site and self-hosted //AN_Xml:versions respectively:</p> //AN_Xml: //AN_Xml:<figure> //AN_Xml:<img src="/wp-content/uploads/2019/05/wpt-dep-tree-off-site.png" alt="" loading="lazy" /> //AN_Xml:<figcaption>Notice how we need to build new dependency trees per //AN_Xml:origin? Stream IDs 1 and 3 keep reoccurring.</figcaption> //AN_Xml:</figure> //AN_Xml: //AN_Xml:<figure> //AN_Xml:<img src="/wp-content/uploads/2019/05/wpt-dep-tree-self-hosted.png" alt="" loading="lazy" /> //AN_Xml:<figcaption>By hosting all content under the same origin, we can build one, more //AN_Xml:complete dependency tree. Every stream has a unique ID as they’re all in the //AN_Xml:same tree.</figcaption> //AN_Xml:</figure> //AN_Xml: //AN_Xml:<p><small>Fun fact: Stream IDs with an odd number were initiated by the client; //AN_Xml:those with an even number were initiated by the server. I honestly don’t think //AN_Xml:I’ve ever seen an even-numbered ID in the wild.</small></p> //AN_Xml: //AN_Xml:<p>If we serve as much content as possible from one domain, we can let H/2 do its //AN_Xml:thing and prioritise assets more completely in the hopes of better-timed //AN_Xml:responses.</p> //AN_Xml: //AN_Xml:<h2 id="penalty-caching">Penalty: Caching</h2> //AN_Xml: //AN_Xml:<p>By and large, static asset hosts seem to do pretty well at establishing //AN_Xml:long-lived <code class="language-plaintext highlighter-rouge">max-age</code> directives. This makes sense, as static assets at versioned //AN_Xml:URLs (as above) will never change. This makes it very safe and sensible to //AN_Xml:enforce a reasonably aggressive cache policy.</p> //AN_Xml: //AN_Xml:<p>That said, this isn’t always the case, and by self-hosting your assets you can //AN_Xml:design <a href="https://csswizardry.com/2019/03/cache-control-for-civilians/">much more bespoke caching //AN_Xml:strategies</a>.</p> //AN_Xml: //AN_Xml:<h2 id="myth-cross-domain-caching">Myth: Cross-Domain Caching</h2> //AN_Xml: //AN_Xml:<p>A more interesting take is the power of cross-domain caching of assets. That is //AN_Xml:to say, if lots and lots of sites link to the same CDN-hosted version of, say, //AN_Xml:jQuery, then surely users are likely to already have that exact file on their //AN_Xml:machine already? Kinda like peer-to-peer resource sharing. This is one of the //AN_Xml:most common arguments I hear in favour of using a third-party static asset //AN_Xml:provider.</p> //AN_Xml: //AN_Xml:<p>Unfortunately, there seems to be no published evidence that backs up these //AN_Xml:claims: there is nothing to suggest that this is indeed the case. Conversely, //AN_Xml:<a href="https://discuss.httparchive.org/t/analyzing-resource-age-by-content-type/1659">recent //AN_Xml:research</a> //AN_Xml:by <a href="https://twitter.com/paulcalvano">Paul Calvano</a> hints that the opposite might //AN_Xml:be the case:</p> //AN_Xml: //AN_Xml:<blockquote> //AN_Xml: <p>There is a significant gap in the 1st vs 3rd party resource age of CSS and web //AN_Xml:fonts. 95% of first party fonts are older than 1 week compared to 50% of 3rd //AN_Xml:party fonts which are less than 1 week old! This makes a strong case for self //AN_Xml:hosting web fonts!</p> //AN_Xml:</blockquote> //AN_Xml: //AN_Xml:<p>In general, third party content seems to be less-well cached than first party //AN_Xml:content.</p> //AN_Xml: //AN_Xml:<p>Even more importantly, <a href="https://andydavies.me/blog/2018/09/06/safari-caching-and-3rd-party-resources/">Safari has completely disabled this //AN_Xml:feature</a> //AN_Xml:for fear of abuse where privacy is concerned, so the shared cache technique //AN_Xml:cannot work for, at the time of writing, <a href="http://gs.statcounter.com/">16% of users //AN_Xml:worldwide</a>.</p> //AN_Xml: //AN_Xml:<p>In short, although nice in theory, there is no evidence that cross-domain //AN_Xml:caching is in any way effective.</p> //AN_Xml: //AN_Xml:<h2 id="myth-access-to-a-cdn">Myth: Access to a CDN</h2> //AN_Xml: //AN_Xml:<p>Another commonly touted benefit of using a static asset provider is that they’re //AN_Xml:likely to be running beefy infrastructure with CDN capabilities: globally //AN_Xml:distributed, scalable, low-latency, high availability.</p> //AN_Xml: //AN_Xml:<p>While this is absolutely true, if you care about performance, you should be //AN_Xml:running your own content from a CDN already. With the price of modern hosting //AN_Xml:solutions being what they are (this site is fronted by Cloudflare which is //AN_Xml:free), there’s very little excuse for not serving your own assets from one.</p> //AN_Xml: //AN_Xml:<p>Put another way: if you think you need a CDN for your jQuery, you’ll need a CDN //AN_Xml:for everything. Go and get one.</p> //AN_Xml: //AN_Xml:<h2 id="self-host-your-static-assets">Self-Host Your Static Assets</h2> //AN_Xml: //AN_Xml:<p>There really is very little reason to leave your static assets on anyone else’s //AN_Xml:infrastructure. The perceived benefits are often a myth, and even if they //AN_Xml:weren’t, the trade-offs simply aren’t worth it. Loading assets from multiple //AN_Xml:origins is demonstrably slower. Take ten minutes over the next few days to audit //AN_Xml:your projects, and fetch any off-site static assets under your own control.</p> //AN_Xml: //AN_Xml: Fri, 31 May 2019 21:10:11 +0000 //AN_Xml: https://csswizardry.com/2019/05/self-host-your-static-assets/ //AN_Xml: https://csswizardry.com/2019/05/self-host-your-static-assets/ //AN_Xml: //AN_Xml: //AN_Xml: //AN_Xml: //AN_Xml: