A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Self-Host Your Static Assets (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="https://csswizardry.com/2019/05/self-host-your-static-assets/">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. Self-Host Your Static Assets (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="https://csswizardry.com/2019/05/self-host-your-static-assets/">Source originale du contenu</a></h3>
  445. <details>
  446. <summary>Table of Contents</summary>
  447. <ol>
  448. <li><a href="#what-am-i-talking-about">What Am I Talking About?</a></li>
  449. <li><a href="#risk-slowdowns-and-outages">Risk: Slowdowns and Outages</a></li>
  450. <li><a href="#risk-service-shutdowns">Risk: Service Shutdowns</a></li>
  451. <li><a href="#risk-security-vulnerabilities">Risk: Security Vulnerabilities</a>
  452. <ol>
  453. <li><a href="#mitigation-subresource-integrity">Mitigation: Subresource Integrity</a></li>
  454. </ol>
  455. </li>
  456. <li><a href="#penalty-network-negotiation">Penalty: Network Negotiation</a>
  457. <ol>
  458. <li><a href="#mitigation-preconnect">Mitigation: <code class="highlighter-rouge">preconnect</code></a></li>
  459. </ol>
  460. </li>
  461. <li><a href="#penalty-loss-of-prioritisation">Penalty: Loss of Prioritisation</a></li>
  462. <li><a href="#penalty-caching">Penalty: Caching</a></li>
  463. <li><a href="#myth-cross-domain-caching">Myth: Cross-Domain Caching</a></li>
  464. <li><a href="#myth-access-to-a-cdn">Myth: Access to a CDN</a></li>
  465. <li><a href="#self-host-your-static-assets">Self-Host Your Static Assets</a></li>
  466. </ol>
  467. </details>
  468. <p>One of the quickest wins—and one of the first things I recommend my clients
  469. do—to make websites faster can at first seem counter-intuitive: you should
  470. self-host all of your static assets, forgoing others’ CDNs/infrastructure. In
  471. this short and hopefully very straightforward post, I want to outline the
  472. disadvantages of hosting your static assets ‘off-site’, and the overwhelming
  473. benefits of hosting them on your own origin.</p>
  474. <h2 id="what-am-i-talking-about">What Am I Talking About?</h2>
  475. <p>It’s not uncommon for developers to link to static assets such as libraries or
  476. plugins that are hosted at a public/CDN URL. A classic example is jQuery, that
  477. we might link to like so:</p>
  478. <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"&gt;&lt;/script&gt;
  479. </code></pre></div></div>
  480. <p>There are a number of perceived benefits to doing this, but my aim later in this
  481. article is to either debunk these claims, or show how other costs vastly
  482. outweigh them.</p>
  483. <ul>
  484. <li><strong>It’s convenient.</strong> It requires very little effort or brainpower to include
  485. files like this. Copy and paste a line of HTML and you’re done. Easy.</li>
  486. <li><strong>We get access to a CDN.</strong> <code class="highlighter-rouge">code.jquery.com</code> is served by
  487. <a href="https://www.stackpath.com/products/cdn/">StackPath</a>, a CDN. By linking to
  488. assets on this origin, we get CDN-quality delivery, free!</li>
  489. <li><strong>Users might already have the file cached.</strong> If <code class="highlighter-rouge">website-a.com</code> links to
  490. <code class="highlighter-rouge">https://code.jquery.com/jquery-3.3.1.slim.min.js</code>, and a user goes from there
  491. to <code class="highlighter-rouge">website-b.com</code> who also links to
  492. <code class="highlighter-rouge">https://code.jquery.com/jquery-3.3.1.slim.min.js</code>, then the user will already
  493. have that file in their cache.</li>
  494. </ul>
  495. <h2 id="risk-slowdowns-and-outages">Risk: Slowdowns and Outages</h2>
  496. <p>I won’t go into too much detail in this post, because I have a <a href="https://csswizardry.com/2017/07/performance-and-resilience-stress-testing-third-parties/">whole
  497. article</a>
  498. on the subject of third party resilience and the risks associated with slowdowns
  499. and outages. Suffice to say, if you have any critical assets served by third
  500. party providers, and that provider is suffering slowdowns or, heaven forbid,
  501. outages, it’s pretty bleak news for you. You’re going to suffer, too.</p>
  502. <p>If you have any render-blocking CSS or synchronous JS hosted on third party
  503. domains, go and bring it onto your own infrastructure <em>right now</em>. Critical
  504. assets are far too valuable to leave on someone else’s servers.</p>
  505. <h2 id="risk-service-shutdowns">Risk: Service Shutdowns</h2>
  506. <p>A far less common occurrence, but what happens if a provider decides they need
  507. to shut down the service? This is exactly what <a href="https://rawgit.com">Rawgit</a> did
  508. in October 2018, yet (at the time of writing) a crude GitHub code search still
  509. yielded <a href="https://github.com/search?q=rawgit&amp;type=Code">over a million
  510. references</a> to the now-sunset
  511. service, and almost 20,000 live sites are still linking to it!</p>
  512. <figure>
  513. <img src="/wp-content/uploads/2019/05/big-query-rawgit.jpg" alt=""/>
  514. <figcaption>Many thanks to <a href="https://twitter.com/paulcalvano">Paul
  515. Calvano</a> who very kindly <a href="https://bigquery.cloud.google.com/savedquery/226352634162:7c27aa5bac804a6687f58db792c021ee">queried
  516. the HTTPArchive</a> for me.</figcaption>
  517. </figure>
  518. <h2 id="risk-security-vulnerabilities">Risk: Security Vulnerabilities</h2>
  519. <p>Another thing to take into consideration is the simple question of trust. If
  520. we’re bringing content from external sources onto our page, we have to hope that
  521. the assets that arrive are the ones we were expecting them to be, and that
  522. they’re doing only what we expected them to do.</p>
  523. <p>Imagine the damage that would be caused if someone managed to take control of
  524. a provider such as <code class="highlighter-rouge">code.jquery.com</code> and began serving compromised or malicious
  525. payloads. It doesn’t bear thinking about!</p>
  526. <h3 id="mitigation-subresource-integrity">Mitigation: Subresource Integrity</h3>
  527. <p>To the credit of all of the providers referenced so far in this article, they do
  528. all make use of <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity">Subresource
  529. Integrity</a>
  530. (SRI). SRI is a mechanism by which the provider supplies a hash (technically,
  531. a hash that is then Base64 encoded) of the exact file that you both expect and
  532. intend to use. The browser can then check that the file you received is indeed
  533. the one you requested.</p>
  534. <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
  535. integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="
  536. crossorigin="anonymous"&gt;&lt;/script&gt;
  537. </code></pre></div></div>
  538. <p>Again, if you absolutely must link to an externally hosted static asset, make
  539. sure it’s SRI-enabled. You can add SRI yourself using <a href="https://www.srihash.org/">this handy
  540. generator</a>.</p>
  541. <h2 id="penalty-network-negotiation">Penalty: Network Negotiation</h2>
  542. <p>One of the biggest and most immediate penalties we pay is the cost of opening
  543. new TCP connections. Every new origin we need to visit needs a connection
  544. opening, and that can be very costly: DNS resolution, TCP handshakes, and TLS
  545. negotiation all add up, and the story gets worse the higher the latency of the
  546. connection is.</p>
  547. <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
  548. Started</a>. They
  549. instruct users to include these following four files:</p>
  550. <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="..." crossorigin="anonymous"&gt;
  551. &lt;script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="..." crossorigin="anonymous"&gt;&lt;/script&gt;
  552. &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="..." crossorigin="anonymous"&gt;&lt;/script&gt;
  553. &lt;script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="..." crossorigin="anonymous"&gt;&lt;/script&gt;
  554. </code></pre></div></div>
  555. <p>These four files are hosted across three different origins, so we’re going to
  556. need to open three TCP connections. How much does that cost?</p>
  557. <p>Well, on a reasonably fast connection, hosting these static assets off-site is
  558. 311ms, or 1.65×, slower than hosting them ourselves.</p>
  559. <figure>
  560. <img src="/wp-content/uploads/2019/05/wpt-off-site-cable.png" alt=""/>
  561. <figcaption>By linking to three different origins in order to serve static
  562. assets, we cumulatively lose a needless 805ms to network negotiation. <a href="https://www.webpagetest.org/result/190531_FY_618f9076491312ef625cf2b1a51167ae/3/details/">Full
  563. test.</a></figcaption>
  564. </figure>
  565. <p>Okay, so not exactly terrifying, but Trainline, a client of mine, found that by
  566. reducing latency by 300ms, <a href="https://wpostats.com/2016/05/04/trainline-spending.html">customers spent an extra £8m
  567. a year</a>. This is
  568. a pretty quick way to make eight mill.</p>
  569. <figure>
  570. <img src="/wp-content/uploads/2019/05/wpt-self-hosted-cable.png" alt=""/>
  571. <figcaption>By simply moving our assets onto the host domain, we completely
  572. remove any extra connection overhead. <a href="https://www.webpagetest.org/result/190531_FX_f7d7b8ae511b02aabc7fa0bbef0e37bc/3/details/">Full
  573. test.</a></figcaption>
  574. </figure>
  575. <p>On a slower, higher-latency connection, the story is much, much worse. Over 3G,
  576. the externally-hosted version comes in at an eye-watering <strong>1.765s slower</strong>.
  577. I thought this was meant to make our site faster?!</p>
  578. <figure>
  579. <img src="/wp-content/uploads/2019/05/wpt-off-site-3g.png" alt=""/>
  580. <figcaption>On a high latency connection, network overhead totals a whopping
  581. 5.037s. All completely avoidable. <a href="https://www.webpagetest.org/result/190531_XE_a95eebddd2346f8bb572cecf4a8dae68/3/details/">Full
  582. test.</a></figcaption>
  583. </figure>
  584. <p>Moving the assets onto our own infrastructure brings load times down from around
  585. 5.4s to just 3.6s.</p>
  586. <figure>
  587. <img src="/wp-content/uploads/2019/05/wpt-self-hosted-3g.png" alt=""/>
  588. <figcaption>By self-hosting our static assets, we don’t need to open any more
  589. connections. <a href="https://www.webpagetest.org/result/190531_ZF_4d76740567ec1eba1e6ec67acfd57627/1/details/">Full
  590. test.</a></figcaption>
  591. </figure>
  592. <p>If this isn’t already a compelling enough reason to self-host your static
  593. assets, I’m not sure what is!</p>
  594. <h3 id="mitigation-preconnect">Mitigation: <code class="highlighter-rouge">preconnect</code></h3>
  595. <p>Naturally, my whole point here is that you should not host any static assets
  596. off-site if you’re otherwise able to self-host them. However, if your hands are
  597. 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="highlighter-rouge">preconnect</code> Resource
  598. Hint</a>
  599. to preemptively open a TCP connection to the specified origin(s):</p>
  600. <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;head&gt;
  601. ...
  602. &lt;link rel="preconnect" href="https://code.jquery.com" /&gt;
  603. ...
  604. &lt;/head&gt;
  605. </code></pre></div></div>
  606. <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
  607. headers</a>
  608. will be even faster.</p>
  609. <p><strong>N.B.</strong> Even if you do implement <code class="highlighter-rouge">preconnect</code>, you’re still only going to make
  610. a small dent in your lost time: you still need to open the relevant connections,
  611. and, especially on high latency connections, it’s unlikely that you’re ever
  612. going to fully pay off the overhead upfront.</p>
  613. <h2 id="penalty-loss-of-prioritisation">Penalty: Loss of Prioritisation</h2>
  614. <p>The second penalty comes in the form of a protocol-level optimisation that we
  615. miss out on the moment we split content across domains. If you’re running over
  616. HTTP/2—which, by now, you should be—you get access to prioritisation. All
  617. streams (ergo, resources) within the same TCP connection carry a priority, and
  618. the browser and server work in tandem to build a dependency tree of all of these
  619. prioritised streams so that we can return critical assets sooner, and perhaps
  620. delay the delivery of less important ones.</p>
  621. <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
  622. coalescence</a>,
  623. requests can be prioritised against each other over different domains as long as
  624. they share the same IP address.</small></p>
  625. <p>If we split our assets across multiple domains, we have to open up several
  626. unique TCP connections. We cannot cross-reference any of the priorities within
  627. these connections, so we lose the ability to deliver assets in a considered and
  628. well designed manner.</p>
  629. <p>Compare the two HTTP/2 dependency trees for both the off-site and self-hosted
  630. versions respectively:</p>
  631. <figure>
  632. <img src="/wp-content/uploads/2019/05/wpt-dep-tree-off-site.png" alt=""/>
  633. <figcaption>Notice how we need to build new dependency trees per
  634. origin? Stream IDs 1 and 3 keep reoccurring.</figcaption>
  635. </figure>
  636. <figure>
  637. <img src="/wp-content/uploads/2019/05/wpt-dep-tree-self-hosted.png" alt=""/>
  638. <figcaption>By hosting all content under the same origin, we can build one, more
  639. complete dependency tree. Every stream has a unique ID as they’re all in the
  640. same tree.</figcaption>
  641. </figure>
  642. <p><small>Fun fact: Stream IDs with an odd number were initiated by the client;
  643. those with an even number were initiated by the server. I honestly don’t think
  644. I’ve ever seen an even-numbered ID in the wild.</small></p>
  645. <p>If we serve as much content as possible from one domain, we can let H/2 do its
  646. thing and prioritise assets more completely in the hopes of better-timed
  647. responses.</p>
  648. <h2 id="penalty-caching">Penalty: Caching</h2>
  649. <p>By and large, static asset hosts seem to do pretty well at establishing
  650. long-lived <code class="highlighter-rouge">max-age</code> directives. This makes sense, as static assets at versioned
  651. URLs (as above) will never change. This makes it very safe and sensible to
  652. enforce a reasonably aggressive cache policy.</p>
  653. <p>That said, this isn’t always the case, and by self-hosting your assets you can
  654. design <a href="https://csswizardry.com/2019/03/cache-control-for-civilians/">much more bespoke caching
  655. strategies</a>.</p>
  656. <h2 id="myth-cross-domain-caching">Myth: Cross-Domain Caching</h2>
  657. <p>A more interesting take is the power of cross-domain caching of assets. That is
  658. to say, if lots and lots of sites link to the same CDN-hosted version of, say,
  659. jQuery, then surely users are likely to already have that exact file on their
  660. machine already? Kinda like peer-to-peer resource sharing. This is one of the
  661. most common arguments I hear in favour of using a third-party static asset
  662. provider.</p>
  663. <p>Unfortunately, there seems to be no published evidence that backs up these
  664. claims: there is nothing to suggest that this is indeed the case. Conversely,
  665. <a href="https://discuss.httparchive.org/t/analyzing-resource-age-by-content-type/1659">recent
  666. research</a>
  667. by <a href="https://twitter.com/paulcalvano">Paul Calvano</a> hints that the opposite might
  668. be the case:</p>
  669. <blockquote>
  670. <p>There is a significant gap in the 1st vs 3rd party resource age of CSS and web
  671. fonts. 95% of first party fonts are older than 1 week compared to 50% of 3rd
  672. party fonts which are less than 1 week old! This makes a strong case for self
  673. hosting web fonts!</p>
  674. </blockquote>
  675. <p>In general, third party content seems to be less-well cached than first party
  676. content.</p>
  677. <p>Even more importantly, <a href="https://andydavies.me/blog/2018/09/06/safari-caching-and-3rd-party-resources/">Safari has completely disabled this
  678. feature</a>
  679. for fear of abuse where privacy is concerned, so the shared cache technique
  680. cannot work for, at the time of writing, <a href="http://gs.statcounter.com/">16% of users
  681. worldwide</a>.</p>
  682. <p>In short, although nice in theory, there is no evidence that cross-domain
  683. caching is in any way effective.</p>
  684. <h2 id="myth-access-to-a-cdn">Myth: Access to a CDN</h2>
  685. <p>Another commonly touted benefit of using a static asset provider is that they’re
  686. likely to be running beefy infrastructure with CDN capabilities: globally
  687. distributed, scalable, low-latency, high availability.</p>
  688. <p>While this is absolutely true, if you care about performance, you should be
  689. running your own content from a CDN already. With the price of modern hosting
  690. solutions being what they are (this site is fronted by Cloudflare which is
  691. free), there’s very little excuse for not serving your own assets from one.</p>
  692. <p>Put another way: if you think you need a CDN for your jQuery, you’ll need a CDN
  693. for everything. Go and get one.</p>
  694. <h2 id="self-host-your-static-assets">Self-Host Your Static Assets</h2>
  695. <p>There really is very little reason to leave your static assets on anyone else’s
  696. infrastructure. The perceived benefits are often a myth, and even if they
  697. weren’t, the trade-offs simply aren’t worth it. Loading assets from multiple
  698. origins is demonstrably slower. Take ten minutes over the next few days to audit
  699. your projects, and fetch any off-site static assets under your own control.</p>
  700. </article>
  701. </section>
  702. <nav id="jumpto">
  703. <p>
  704. <a href="/david/blog/">Accueil du blog</a> |
  705. <a href="https://csswizardry.com/2019/05/self-host-your-static-assets/">Source originale</a> |
  706. <a href="/david/stream/2019/">Accueil du flux</a>
  707. </p>
  708. </nav>
  709. <footer>
  710. <div>
  711. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  712. <p>
  713. Bonjour/Hi!
  714. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  715. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  716. </p>
  717. <p>
  718. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  719. </p>
  720. <p>
  721. Voici quelques articles choisis :
  722. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  723. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  724. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  725. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  726. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  727. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  728. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  729. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  730. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  731. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  732. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  733. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  734. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  735. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  736. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  737. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  738. </p>
  739. <p>
  740. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  741. </p>
  742. <p>
  743. Je ne traque pas ta navigation mais mon
  744. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  745. conserve des logs d’accès.
  746. </p>
  747. </div>
  748. </footer>
  749. <script type="text/javascript">
  750. ;(_ => {
  751. const jumper = document.getElementById('jumper')
  752. jumper.addEventListener('click', e => {
  753. e.preventDefault()
  754. const anchor = e.target.getAttribute('href')
  755. const targetEl = document.getElementById(anchor.substring(1))
  756. targetEl.scrollIntoView({behavior: 'smooth'})
  757. })
  758. })()
  759. </script>