{"id":389,"date":"2017-09-06T22:10:12","date_gmt":"2017-09-06T19:10:12","guid":{"rendered":"https:\/\/iamakulov.com\/notes\/?p=389"},"modified":"2020-05-11T11:19:36","modified_gmt":"2020-05-11T08:19:36","slug":"caching","status":"publish","type":"post","link":"https:\/\/iamakulov.com\/notes\/caching\/","title":{"rendered":"Short basics of caching"},"content":{"rendered":"\n<p>I\u2019ve finally configured caching on my site. Here\u2019s what you need to know.<\/p>\n<h1 id=\"headers\">Headers<a href=\"https:\/\/iamakulov.com\/notes\/caching\/#headers\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<p>There\u2019re 4 headers that enable caching: <code>Cache-Control<\/code>\/<code>Expires<\/code> and <code>Last-Modified<\/code>\/<code>ETag<\/code>. The former two are primary; they enable caching and instruct the browser on how long it should save a resource. The latter two are secondary and optional; browsers use them when the cached resource gets expired to check if it has changed. (If it hasn\u2019t, browsers just take the expired one and keeps using it.)<\/p>\n<p>In practice, you choose 2 of these headers: one primary and one secondary \u2013 and use them together. Using all four headers isn\u2019t very practical \u2013 the browser will rely on only two of them.<\/p>\n<p>I recommend choosing <code>Cache-Control<\/code> and <code>ETag<\/code>. <code>Cache-Control<\/code> lets you configure the caching details that <code>Expires<\/code> can\u2019t. <code>ETag<\/code>, according to MDN, <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Last-Modified\">is more reliable<\/a> than <code>Last-Modified<\/code>.<\/p>\n<div class=\"bordered-highlight\">Use Cache-Control and ETag<\/div>\n<h1 id=\"lifecycle\">Lifecycle<a href=\"https:\/\/iamakulov.com\/notes\/caching\/#lifecycle\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<p>Imagine you have a file called <code>pic.gif<\/code>. <a href=\"https:\/\/giphy.com\/gifs\/out-panda-trash-ssk1XJXWaL8E8\/\">This one<\/a>:<\/p>\n<p><iframe loading=\"lazy\" src=\"https:\/\/giphy.com\/embed\/lcySndwSDLxC4eOU86\" width=\"480\" height=\"294\" frameBorder=\"0\" class=\"giphy-embed\" allowFullScreen><\/iframe><\/p>\n<p>This is how its lifecycle will look:<\/p>\n<ol>\n<li>\n<p><mark>The browser requests <code>pic.gif<\/code>.<\/mark> The server sends the response with, for example, these caching headers:<\/p>\n<pre><code class=\"nohighlight\">Cache-Control: max-age=60\nETag: deadbeef123\n<\/code><\/pre>\n<\/li>\n<li><mark>The user refreshes the page.<\/mark> If less than 60 seconds have passed (60 is a value from <code>Cache-Control: max-age<\/code>), the browser doesn\u2019t make any requests and just takes <code>pic.gif<\/code> from the cache.<\/li>\n<li><mark>The user refreshes the page.<\/mark> If more than 60 seconds have passed (60 is a value from <code>Cache-Control: max-age<\/code>), the browser sends a request for <code>pic.gif<\/code> and attaches the <code>If-None-Match: deadbeef123<\/code> header. (<code>deadbeef123<\/code> is a value from the <code>ETag<\/code> header that the browser has received.)\n<p>When the server receives the request, it reads <code>pic.gif<\/code> and calculates its <code>ETag<\/code> (<code>ETag<\/code> is a hash which changes when the file changes). And then:<\/p>\n<ul>\n<li>if the calculated <code>ETag<\/code> is still <code>deadbeef123<\/code>, the file hasn\u2019t changed. The server returns an empty response with the <code>304 Not Modified<\/code> status.<\/li>\n<li>if the calculated <code>ETag<\/code> is different, the file has changed. The server returns the content of <code>pic.gif<\/code> with the <code>200 OK<\/code> status and new <code>Cache-Control<\/code> and <code>ETag<\/code> headers.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<div class=\"bordered-highlight\">Servers respond with either 304 Not Modified or with a new resource<\/div>\n<h1 id=\"total-immutability\">Total immutability<a href=\"https:\/\/iamakulov.com\/notes\/caching\/#total-immutability\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<p>Usually, on the third step (\u2191), when the resource expires, the browser makes a request to the server. If the file has changed, the browser downloads the new version. But if it hasn\u2019t, the browser receives <code>304 Not Modified<\/code> and doesn\u2019t download anything. That\u2019s nice, but the extra request to the server is still there.<\/p>\n<figure id=\"attachment_676\" aria-describedby=\"caption-attachment-676\" style=\"width: 1920px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-676\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o.png\" alt=\"\" width=\"1920\" height=\"348\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o.png 1920w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o-300x54.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o-768x139.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o-1024x186.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o-1200x218.png 1200w\" sizes=\"(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption id=\"caption-attachment-676\" class=\"wp-caption-text\">Each network request brings a delay<\/figcaption><\/figure>\n<p>If you know that the resource will <em>never<\/em> change, and checking this doesn\u2019t make any sense, you can send the <code>immutable<\/code> value in the <code>Cache-Control<\/code> header:<\/p>\n<pre><code class=\"nohighlight\">Cache-Control: max-age=60, immutable<\/code><\/pre>\n<p>or simply<\/p>\n<pre><code class=\"nohighlight\">Cache-Control: immutable<\/code><\/pre>\n<p>After this, the browser will always consider the cached resource as valid and will never send a verification request for it. It\u2019s like if you set <code>max-age<\/code> to 1000 years.<\/p>\n<p><code>Cache-Control: immutable<\/code> is a new header value. At the moment, <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Cache-Control#Browser_compatibility\">it works only in Firefox and Edge<\/a>.<\/p>\n<div class=\"bordered-highlight\">Use Cache-Control: immutable to prevent any additional requests<\/div>\n<h1 id=\"versioning\">Versioning<a href=\"https:\/\/iamakulov.com\/notes\/caching\/#versioning\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<p>On my site (except this blog), I enabled <code>Cache-Control: immutable<\/code> for all images, styles, and scripts. To force the browser to re-download the file if it changes, I append the last change date to the file name:<\/p>\n<pre><code class=\"nohighlight\">\/images\/pic.gif?hash=1499433448<\/code><\/pre>\n<p>This way, the browser will send a request for the file only when the file (and, therefore, its name) changes.<\/p>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'><div class='annotated-element__annotation'><\/div><div class='annotated-element__content'><p>This approach\u2014including a dynamic value into the file name\u2014is called versioning. Versioning is a common practice, and I recommend enabling it in your app.<\/p><\/div><\/div>\n<p>Unlike versioning, Cache-Control: immutable hasn\u2019t become a common practice yet. However, <a href=\"https:\/\/code.facebook.com\/posts\/557147474482256\/this-browser-tweak-saved-60-of-requests-to-facebook\/\">it\u2019s already being used by e.g. Facebook<\/a>, so you can try enabling it too.<\/p>\n<div class=\"bordered-highlight\">Use versioning, it\u2019s a common practice<\/div>\n<h1 id=\"summing-up\">Summing up<a href=\"https:\/\/iamakulov.com\/notes\/caching\/#summing-up\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<ul>\n<li>Use <code>Cache-Control<\/code> and <code>ETag<\/code> headers for caching<\/li>\n<li>Implement versioning for cache invalidation<\/li>\n<li>Try <code>Cache-Control: immutable<\/code> if you have resources that will never change<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>I\u2019ve finally configured caching on my site. Here\u2019s what you need to know. Headers# There\u2019re 4 headers that enable caching: Cache-Control\/Expires and Last-Modified\/ETag. The former two are primary; they enable caching and instruct the browser on how long it should save a resource. The latter two are secondary and optional; browsers use them when the &hellip; <a href=\"https:\/\/iamakulov.com\/notes\/caching\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Short basics of caching&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v23.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Short basics of caching - Ivan Akulov\u2019s blog<\/title>\n<meta name=\"description\" content=\"What headers to enable, how resource lifecycle looks, and more\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/iamakulov.com\/notes\/caching\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Short basics of caching\" \/>\n<meta property=\"og:description\" content=\"What headers to enable, how resource lifecycle looks, and more\" \/>\n<meta property=\"og:url\" content=\"https:\/\/iamakulov.com\/notes\/caching\/\" \/>\n<meta property=\"og:site_name\" content=\"Ivan Akulov\u2019s blog\" \/>\n<meta property=\"article:publisher\" content=\"http:\/\/facebook.com\/iamakulov.page\" \/>\n<meta property=\"article:published_time\" content=\"2017-09-06T19:10:12+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2020-05-11T08:19:36+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/ApplicationFrameHost_2017-09-06_21-57-03.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Ivan Akulov\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:title\" content=\"Short basics of caching\" \/>\n<meta name=\"twitter:description\" content=\"What headers to enable, how resource lifecycle looks, and more\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/ApplicationFrameHost_2017-09-06_21-57-03.png\" \/>\n<meta name=\"twitter:creator\" content=\"@iamakulov\" \/>\n<meta name=\"twitter:site\" content=\"@iamakulov\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Ivan Akulov\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"3 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/iamakulov.com\/notes\/caching\/\",\"url\":\"https:\/\/iamakulov.com\/notes\/caching\/\",\"name\":\"Short basics of caching - Ivan Akulov\u2019s blog\",\"isPartOf\":{\"@id\":\"https:\/\/iamakulov.com\/notes\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/iamakulov.com\/notes\/caching\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/iamakulov.com\/notes\/caching\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o.png\",\"datePublished\":\"2017-09-06T19:10:12+00:00\",\"dateModified\":\"2020-05-11T08:19:36+00:00\",\"author\":{\"@id\":\"https:\/\/iamakulov.com\/notes\/#\/schema\/person\/ebf7b61bf573e7be5fe438f50ebd9b81\"},\"description\":\"What headers to enable, how resource lifecycle looks, and more\",\"breadcrumb\":{\"@id\":\"https:\/\/iamakulov.com\/notes\/caching\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/iamakulov.com\/notes\/caching\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/iamakulov.com\/notes\/caching\/#primaryimage\",\"url\":\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o.png\",\"contentUrl\":\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o.png\",\"width\":1920,\"height\":348,\"caption\":\"Each network request brings a delay\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/iamakulov.com\/notes\/caching\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/iamakulov.com\/notes\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Short basics of caching\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/iamakulov.com\/notes\/#website\",\"url\":\"https:\/\/iamakulov.com\/notes\/\",\"name\":\"Ivan Akulov\u2019s blog\",\"description\":\"Ivan Akulov writes about his front-end experience, React, webpack, and performance optimizations.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/iamakulov.com\/notes\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/iamakulov.com\/notes\/#\/schema\/person\/ebf7b61bf573e7be5fe438f50ebd9b81\",\"name\":\"Ivan Akulov\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/iamakulov.com\/notes\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/f68e4cd477cef1577c339e6f09736d3a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/f68e4cd477cef1577c339e6f09736d3a?s=96&d=mm&r=g\",\"caption\":\"Ivan Akulov\"},\"description\":\"I'm a software engineer specializing in web performance, JavaScript, and React. I\u2019m also a Google Developer Expert. I work at Framer.\",\"sameAs\":[\"http:\/\/iamakulov.com\",\"https:\/\/x.com\/iamakulov\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Short basics of caching - Ivan Akulov\u2019s blog","description":"What headers to enable, how resource lifecycle looks, and more","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/iamakulov.com\/notes\/caching\/","og_locale":"en_US","og_type":"article","og_title":"Short basics of caching","og_description":"What headers to enable, how resource lifecycle looks, and more","og_url":"https:\/\/iamakulov.com\/notes\/caching\/","og_site_name":"Ivan Akulov\u2019s blog","article_publisher":"http:\/\/facebook.com\/iamakulov.page","article_published_time":"2017-09-06T19:10:12+00:00","article_modified_time":"2020-05-11T08:19:36+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/ApplicationFrameHost_2017-09-06_21-57-03.png","type":"image\/png"}],"author":"Ivan Akulov","twitter_card":"summary_large_image","twitter_title":"Short basics of caching","twitter_description":"What headers to enable, how resource lifecycle looks, and more","twitter_image":"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/ApplicationFrameHost_2017-09-06_21-57-03.png","twitter_creator":"@iamakulov","twitter_site":"@iamakulov","twitter_misc":{"Written by":"Ivan Akulov","Est. reading time":"3 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/iamakulov.com\/notes\/caching\/","url":"https:\/\/iamakulov.com\/notes\/caching\/","name":"Short basics of caching - Ivan Akulov\u2019s blog","isPartOf":{"@id":"https:\/\/iamakulov.com\/notes\/#website"},"primaryImageOfPage":{"@id":"https:\/\/iamakulov.com\/notes\/caching\/#primaryimage"},"image":{"@id":"https:\/\/iamakulov.com\/notes\/caching\/#primaryimage"},"thumbnailUrl":"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o.png","datePublished":"2017-09-06T19:10:12+00:00","dateModified":"2020-05-11T08:19:36+00:00","author":{"@id":"https:\/\/iamakulov.com\/notes\/#\/schema\/person\/ebf7b61bf573e7be5fe438f50ebd9b81"},"description":"What headers to enable, how resource lifecycle looks, and more","breadcrumb":{"@id":"https:\/\/iamakulov.com\/notes\/caching\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/iamakulov.com\/notes\/caching\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/iamakulov.com\/notes\/caching\/#primaryimage","url":"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o.png","contentUrl":"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2017\/09\/19956841_827899250709270_7413083530252584766_o.png","width":1920,"height":348,"caption":"Each network request brings a delay"},{"@type":"BreadcrumbList","@id":"https:\/\/iamakulov.com\/notes\/caching\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/iamakulov.com\/notes\/"},{"@type":"ListItem","position":2,"name":"Short basics of caching"}]},{"@type":"WebSite","@id":"https:\/\/iamakulov.com\/notes\/#website","url":"https:\/\/iamakulov.com\/notes\/","name":"Ivan Akulov\u2019s blog","description":"Ivan Akulov writes about his front-end experience, React, webpack, and performance optimizations.","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/iamakulov.com\/notes\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/iamakulov.com\/notes\/#\/schema\/person\/ebf7b61bf573e7be5fe438f50ebd9b81","name":"Ivan Akulov","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/iamakulov.com\/notes\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/f68e4cd477cef1577c339e6f09736d3a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f68e4cd477cef1577c339e6f09736d3a?s=96&d=mm&r=g","caption":"Ivan Akulov"},"description":"I'm a software engineer specializing in web performance, JavaScript, and React. I\u2019m also a Google Developer Expert. I work at Framer.","sameAs":["http:\/\/iamakulov.com","https:\/\/x.com\/iamakulov"]}]}},"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/posts\/389"}],"collection":[{"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/comments?post=389"}],"version-history":[{"count":17,"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/posts\/389\/revisions"}],"predecessor-version":[{"id":1447,"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/posts\/389\/revisions\/1447"}],"wp:attachment":[{"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/media?parent=389"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/categories?post=389"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/tags?post=389"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}