{"id":1065,"date":"2018-04-27T10:49:53","date_gmt":"2018-04-27T07:49:53","guid":{"rendered":"https:\/\/iamakulov.com\/notes\/?p=1065"},"modified":"2024-01-08T06:47:02","modified_gmt":"2024-01-08T03:47:02","slug":"walmart","status":"publish","type":"post","link":"https:\/\/iamakulov.com\/notes\/walmart\/","title":{"rendered":"Case study: analyzing the Walmart site performance"},"content":{"rendered":"<p>Walmart is one of the top USA e-commerce retailers. In 2016, they were <a href=\"http:\/\/wwd.com\/business-news\/financial\/amazon-walmart-top-ecommerce-retailers-10383750\/\">the second after Amazon by sales<\/a>.<\/p>\n<p><mark>In e-commerce, the conversion is directly affected by how fast the site loads.<\/mark> For many e-commerce companies, making the site faster by 1 second <a href=\"https:\/\/wpostats.com\/tags\/conversion\/\">increased the conversion 1.05, 1.1, or even 1.2 times<\/a>. That\u2019s because the slower the site, the more users abandon it before it loads, and the lesser is the conversion.<\/p>\n<p>Unfortunately, the Walmart site is pretty slow. In my tests, the content of the product page <a href=\"https:\/\/www.webpagetest.org\/video\/compare.php?tests=180328_TW_cd44041d0eea54c5dcb3e05e1f914257-r%3A1-c%3A0&amp;thumbSize=200&amp;ival=100&amp;end=visual\">becomes visible only at the third second<\/a>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1283\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43.png\" alt=\"\" width=\"1863\" height=\"408\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43.png 1863w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43-300x66.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43-768x168.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43-1024x224.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43-1200x263.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/p>\n<p>In comparison, for Amazon, the content gets visible <a href=\"https:\/\/www.webpagetest.org\/video\/compare.php?tests=180328_4Y_18f7887c9336f043b70ff6358bb56421-r%3A1-c%3A0&amp;thumbSize=200&amp;ival=100&amp;end=visual\">at 1.4 seconds<\/a>. The customer sees the product they came for twice faster!<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1295\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_13-01-43-1.png\" alt=\"\" width=\"1875\" height=\"420\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_13-01-43-1.png 1875w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_13-01-43-1-300x67.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_13-01-43-1-768x172.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_13-01-43-1-1024x229.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_13-01-43-1-1200x269.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/p>\n<p>Let\u2019s analyze the Walmart\u2019s site and see how we can improve the performance \u2013 and help Walmart earn more! I\u2019ll use <a href=\"https:\/\/www.walmart.com\/ip\/Nokia-Microsoft-Lumia-635-4G-LTE-Unlocked-8GB-Bright-Yellow\/407834961\">the Lumia 635 product page<\/a> as an example.<\/p>\n<h1 id=\"fix-the-invisible-text\">Fix the invisible text<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#fix-the-invisible-text\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<p>The first issue with the page is that it gets more or less rendered at 2.3s, but the text isn\u2019t visible until 3.0s:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1298\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-30_21-12-46.png\" alt=\"\" width=\"1863\" height=\"354\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-30_21-12-46.png 1863w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-30_21-12-46-300x57.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-30_21-12-46-768x146.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-30_21-12-46-1024x195.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-30_21-12-46-1200x228.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/p>\n<\/div>\n<\/div>\n<p>This happens because Walmart uses a custom font, and by default, Chrome and Firefox won\u2019t render the text until the font is loaded. This is how it looks live:<\/p>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'>\n<p>See how the page stays without the text for a second?<\/p>\n<p>(Network throttled with the \u201cFast 3G\u201d preset in Chrome DevTools)<\/p>\n<\/div>\n<div class='annotated-element__content'>\n<div style=\"width: 840px;\" class=\"wp-video\"><video class=\"wp-video-shortcode\" id=\"video-1065-1\" width=\"840\" height=\"460\" loop preload=\"metadata\" controls=\"controls\"><source type=\"video\/mp4\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/without-fd.mp4?_=1\" \/><a href=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/without-fd.mp4\">https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/without-fd.mp4<\/a><\/video><\/div>\n<\/div>\n<\/div>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<p>Browsers delay rendering the text to prevent a flash of unstyled text (FOUT). However, this makes the content invisible for longer \u2013 and likely decreases the conversion!<\/p>\n<\/div>\n<\/div>\n<p>To change this behavior, we can add the <code>font-display: optional<\/code> rule to the <code>@font-face<\/code> styles. <code>font-display<\/code> controls how the custom font is applied. In our case, it tells the browser to just use a fallback font if the custom one is not cached:<\/p>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<pre><code class=\"css\">\/* https:\/\/ll-us-i5.wal.co\/...\/BogleWeb.css *\/\n@font-face {\nfont-family: \"BogleWeb\";\n\/* ... *\/\nfont-display: optional;\n}\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Now, when a customer visits the page for the first time, they will see the text immediately, rendered in a fallback font. The browser will download the custom font in the background and use it for subsequent pages. The current page won\u2019t get the custom font \u2013 this prevents the FOUT:<\/p>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'>\n<p>Now the text is visible immediately.<\/p>\n<p>(Network throttled with the \u201cFast 3G\u201d preset in Chrome DevTools. The CSS file was substituted with Fiddler)<\/p>\n<\/div>\n<div class='annotated-element__content'>\n<div style=\"width: 840px;\" class=\"wp-video\"><video class=\"wp-video-shortcode\" id=\"video-1065-2\" width=\"840\" height=\"460\" loop preload=\"metadata\" controls=\"controls\"><source type=\"video\/mp4\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/with-fd.mp4?_=2\" \/><a href=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/with-fd.mp4\">https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/with-fd.mp4<\/a><\/video><\/div>\n<\/div>\n<\/div>\n<div class='snippet'>\n<h2 id=\"side-note-single-page-apps\">Side note: single-page apps<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#side-note-single-page-apps\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h2>\n<p>With <code>font-display: optional<\/code>, the font won\u2019t be applied until the user reloads the page. Keep this in mind if you have a single-page app: navigating across routes there won\u2019t make the font active.<\/div>\n<h1 id=\"optimize-javascript\">Optimize JavaScript<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#optimize-javascript\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<p>Another issue is that the page downloads around 2 MBs of gzipped JavaScript. That\u2019s <em>a lot<\/em>:<\/p>\n<\/div>\n<\/div>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1138\" style=\"border: 1px solid #ccc;\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_15-27-53.png\" alt=\"\" width=\"1222\" height=\"466\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_15-27-53.png 1222w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_15-27-53-300x114.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_15-27-53-768x293.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_15-27-53-1024x390.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_15-27-53-1200x458.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/p>\n<p>JavaScript code is minified, so I\u2019m only able to analyze it on the surface. Here\u2019s what I found.<\/p>\n<h2 id=\"bundle-defer\" id=\"bundle-defer\" id=\"bundle-defer\">Use <code>defer<\/code> for the first bundle<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#bundle-defer\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h2>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<p>Most of <code>&lt;script&gt;<\/code> tags on the page have either the <code>async<\/code> or the <code>defer<\/code> attribute. This is good because the browser can render the page not waiting for these scripts to download:<\/p>\n<\/div>\n<\/div>\n<figure id=\"attachment_1143\" aria-describedby=\"caption-attachment-1143\" style=\"width: 1409px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1143\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_16-12-44.png\" alt=\"\" width=\"1409\" height=\"838\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_16-12-44.png 1409w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_16-12-44-300x178.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_16-12-44-768x457.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_16-12-44-1024x609.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/03\/chrome_2018-03-31_16-12-44-1200x714.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption id=\"caption-attachment-1143\" class=\"wp-caption-text\">The page has more scripts in different places, so that\u2019s just an example<\/figcaption><\/figure>\n<p>However, one large file \u2013 <code>bundle.3p.min-[hash].js<\/code>, 112.3 kB gzipped \u2013 doesn\u2019t have either of these attributes. If it takes a while to download (e.g., the customer is on a bad connection), the page will stay blank until the script is fully loaded. Not cool!<\/p>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'>To be honest, the bad connection could delay any non-deferred script, even the smallest one. So I\u2019d try to defer as many scripts as I can<\/div>\n<div class='annotated-element__content'>\n<p>To solve this, add the <code>defer<\/code> attribute to this script tag too. As soon as all JavaScript that relies on <code>bundle.3p.min-[hash].js<\/code> is also deferred (which seems to be the case), the code will keep working fine.<\/p>\n<\/div>\n<\/div>\n<div class='snippet'>\n<h3 id=\"side-note-performance-marks\">Side note: performance marks<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#side-note-performance-marks\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h3>\n<p>In the screenshot above, there\u2019s code that likely measures the time the bundle takes executing:<\/p>\n<pre><code class=\"html\">&lt;script&gt;_wml.perf.mark(\"before-bundle\")&lt;\/script&gt;\n&lt;script src=\"https:\/\/ll-us-i5.wal.co\/dfw\/[hash]\/v1\/standard_js.bundle.[hash].js\" id=\"bundleJs\" defer&gt;&lt;\/script&gt;\n&lt;script&gt;_wml.perf.mark(\"after-bundle\")&lt;\/script&gt;\n<\/code><\/pre>\n<p>This code doesn\u2019t work as expected: because of <code>defer<\/code>, the bundle executes <em>after<\/em> both of these inline scripts. Just in case somebody from Walmart is reading this.<\/div>\n<h2 id=\"load-non-important-code-only-when-necessary\">Load non-important code only when necessary<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#load-non-important-code-only-when-necessary\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h2>\n<p>Chrome DevTools have the \u201cCoverage\u201d tab that analyzes how much CSS and JS is unused. If we open the tab, reload the page and click around a bit to run the most important JavaScript, we\u2019ll see that around 40-60% of JS still hasn\u2019t executed:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1195\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-43-35.png\" alt=\"\" width=\"1423\" height=\"847\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-43-35.png 1423w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-43-35-300x179.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-43-35-768x457.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-43-35-1024x610.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-43-35-1200x714.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/p>\n<p>This code likely includes modals, popups and other components that aren\u2019t rendered straight when the customer opens the page. They are a good candidate to be loaded only when actually needed. This might save us a few hundred kBs of JS.<\/p>\n<p>This is how you load components dynamically with React and webpack:<\/p>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<pre><code class=\"js\">import React from 'react';\n\nclass FeedbackButton extends React.Component {\nhandleButtonClick() {\n\/\/ \u2193&nbsp;Here, import() will make webpack split FeedbackModal\n\/\/ into a separate file\n\/\/ and download it only when import() is called\nimport('..\/FeedbackModal\/').then(module =&gt; {\nthis.setState({ FeedbackModal: module.default });\n});\n}\n\nrender() {\nconst FeedbackModal = this.state.FeedbackModal;\n\nreturn &lt;React.Fragment&gt;\n&lt;button onClick={this.handleButtonClick}&gt;\nProvide feedback!\n&lt;\/button&gt;\n{FeedbackModal &amp;&amp; &lt;FeedbackModal \/&gt;}\n&lt;\/React.Fragment&gt;;\n}\n};\n<\/code><\/pre>\n<\/div>\n<\/div>\n<h2 id=\"dont-serve-babel-polyfill-in-modern-browsers\">Don\u2019t serve <code>babel-polyfill<\/code> in modern browsers<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#dont-serve-babel-polyfill-in-modern-browsers\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h2>\n<p>If we look into <code>standard_js.bundle.[hash].js<\/code>, we\u2019ll notice that it includes <code>babel-polyfill<\/code>:<\/p>\n<figure id=\"attachment_1174\" aria-describedby=\"caption-attachment-1174\" style=\"width: 1415px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1174\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_01-38-03.png\" alt=\"\" width=\"1415\" height=\"842\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_01-38-03.png 1415w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_01-38-03-300x179.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_01-38-03-768x457.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_01-38-03-1024x609.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_01-38-03-1200x714.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption id=\"caption-attachment-1174\" class=\"wp-caption-text\">Pretty easy to find by searching for \u201cbabel\u201d<\/figcaption><\/figure>\n<p><code>babel-polyfill<\/code> weights 32.9 kB gzipped and takes 170 ms to download on Fast 3G:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1175\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_02-06-07.png\" alt=\"\" width=\"1415\" height=\"842\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_02-06-07.png 1415w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_02-06-07-300x179.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_02-06-07-768x457.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_02-06-07-1024x609.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_02-06-07-1200x714.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/p>\n<p>By not shipping this polyfill in modern browsers, we could make the page fully interactive 170 ms earlier! And this is fairly easy to do:<\/p>\n<ul>\n<li>either use an external service that serves polyfills based on <code>User-Agent<\/code>, like <a href=\"https:\/\/polyfill.io\/v2\/docs\/\">polyfill.io<\/a>,<\/li>\n<li>or build a second bundle without polyfills and serve it using <code>&lt;script type=\"module\"&gt;<\/code>, like in <a href=\"https:\/\/philipwalton.com\/articles\/deploying-es2015-code-in-production-today\/\">the Philip Walton\u2019s article<\/a>.<\/li>\n<\/ul>\n<h2 id=\"dont-load-polyfills-multiple-times\">Don\u2019t load polyfills multiple times<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#dont-load-polyfills-multiple-times\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h2>\n<p>Another problem is that the <code>Object.assign<\/code> polyfill is served in 3 files simultaneously:<\/p>\n<figure class='below-entry-meta'>\n<div class='fotorama--wp' data-link='file'data-ids='1186,1187,1188'data-itemtag='dl'data-icontag='dt'data-captiontag='dd'data-columns='0'data-size='large'data-width='840'data-auto='false'data-max-width='100%'data-ratio='1.68'>\n<div id='gallery-1' class='gallery galleryid-1065 gallery-columns-0 gallery-size-thumbnail'>\n<dl class='gallery-item'>\n<dt class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-25-46-1024x610.png' data-full='https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-25-46.png'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-25-46-150x150.png\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" \/><\/a>\n\t\t\t<\/dt>\n<\/dl>\n<dl class='gallery-item'>\n<dt class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-25-55-1024x610.png' data-full='https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-25-55.png'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-25-55-150x150.png\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" \/><\/a>\n\t\t\t<\/dt>\n<\/dl>\n<dl class='gallery-item'>\n<dt class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-26-10-1024x610.png' data-full='https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-26-10.png'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-08_03-26-10-150x150.png\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" \/><\/a>\n\t\t\t<\/dt>\n<\/dl><\/div>\n<\/div>\n<\/figure>\n<p>The polyfill is small on its own, but this might be a sign that more modules are duplicated across the bundles. I\u2019d try looking into that if I had access to sources.<\/p>\n<h2 id=\"remove-node-js-polyfills\">Remove Node.js polyfills<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#remove-node-js-polyfills\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h2>\n<p>By default, webpack bundles polyfills for Node.js-specific functions when it sees them used. Theoretically, this is useful: if a library relies on <a href=\"https:\/\/nodejs.org\/api\/timers.html#timers_setimmediate_callback_args\"><code>setImmediate<\/code><\/a> or <a href=\"https:\/\/nodejs.org\/api\/buffer.html\"><code>Buffer<\/code><\/a> which are only available in Node.js, it will still work in a browser thanks to the polyfill. In practice, however, I\u2019ve seen the following happen:<\/p>\n<pre><code class=\"js\">\/\/ node_modules\/random-library\/index.js\nconst func = () =&gt; { ... };\n\nif (typeof setImmediate !== 'undefined') {\n\/\/ \u2191 Webpack decides that `setImmediate` is used\n\/\/ and adds the polyfill\nsetImmediate(func);\n} else {\nsetTimeout(func, 0);\n}\n<\/code><\/pre>\n<p>The library is adapted to work in the browser, but because webpack sees that it references <code>setImmediate<\/code>, it bundles the polyfill.<\/p>\n<p>Node polyfills are small (a few kBs minified), so removing them usually doesn\u2019t make sense. Still, it\u2019s a good candidate to optimize if we were squeezing the last milliseconds from the page. Removing them is super easy (but needs to be tested \u2013 what if some code really needs them?):<\/p>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<pre><code class=\"js\">\/\/ webpack.config.js\nmodule.exports = {\nnode: false,\n};\n<\/code><\/pre>\n<\/div>\n<\/div>\n<h1 id=\"decrease-render-blocking-css\" id=\"decrease-render-blocking-css\" id=\"decrease-render-blocking-css\">Decrease the render-blocking CSS<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#decrease-render-blocking-css\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<p>Apart from JS, page rendering is also blocked by CSS. The browser won\u2019t render the page until all CSS (and JS) files are downloaded.<\/p>\n<p>The Walmart page initially depends on two CSS files. In my tests, the largest of them takes even longer to download than the JS bundle \u2013 so it blocks rendering even after the script got downloaded and executed:<\/p>\n<figure id=\"attachment_1215\" aria-describedby=\"caption-attachment-1215\" style=\"width: 1430px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1215\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_01-37-56-1.png\" alt=\"\" width=\"1430\" height=\"854\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_01-37-56-1.png 1430w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_01-37-56-1-300x179.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_01-37-56-1-768x459.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_01-37-56-1-1024x612.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_01-37-56-1-1200x717.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption id=\"caption-attachment-1215\" class=\"wp-caption-text\">Notice how the page stays blank (look into \u201cFrames\u201d in the bottom half of the image) until the CSS is fully downloaded<\/figcaption><\/figure>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<p>How to solve this? We can go the way Guardian went in 2013:<\/p>\n<\/div>\n<\/div>\n<ol>\n<li>Find the critical CSS and extract it into a separate file. \u201cCritical\u201d means \u201cThe page looks funny without it\u201d.\n<p>Tools like <a href=\"https:\/\/github.com\/pocketjoso\/penthouse\">Penthouse<\/a> or <a href=\"https:\/\/github.com\/addyosmani\/critical\">Critical<\/a> might be useful here. I\u2019d also tune the result manually to exclude content that\u2019s above the fold but is not very important (e.g., header navigation):<\/p>\n<figure id=\"attachment_1234\" aria-describedby=\"caption-attachment-1234\" style=\"width: 1770px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1234\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_02-47-21-2.png\" alt=\"\" width=\"1770\" height=\"980\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_02-47-21-2.png 1770w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_02-47-21-2-300x166.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_02-47-21-2-768x425.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_02-47-21-2-1024x567.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-09_02-47-21-2-1200x664.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption id=\"caption-attachment-1234\" class=\"wp-caption-text\">We can show this a couple seconds later in exchange for faster overall rendering<\/figcaption><\/figure>\n<\/li>\n<li>When serving the initial HTML, only load the critical CSS.<\/li>\n<li>Once the page is more or less downloaded (e.g., when <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Events\/DOMContentLoaded\">the <code>DOMContentLoaded<\/code> event<\/a> happens), dynamically add the remaining CSS:\n<pre><code class=\"js\">document.addEventListener('DOMContentLoaded', () =&gt; {\nconst styles = ['https:\/\/i5.walmartimages.com\/...\/style.css', ...];\nstyles.forEach((path) =&gt; {\nconst link = document.createElement('link');\nlink.rel = 'stylesheet';\nlink.href = path;\ndocument.head.appendChild(link);\n});\n});\n<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>If we get this right, we\u2019ll be able to render the page several hundred milliseconds earlier.<\/p>\n<h1 id=\"remove-duplicated-styles\">Remove duplicated styles<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#remove-duplicated-styles\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<p>In total, the Walmart page downloads three CSS files: one with the font definitions (<code>BogleWeb.css<\/code>) and two with the app styles (<code>standard_css.style.[hash].css<\/code> and <code>style.[hash].css<\/code>). The latter two seemed pretty similar, so I removed all the content except selectors and tried to compare the files.<\/p>\n<p>Guess what? There\u2019re 3400 common selectors among these files \u2013 and these selectors mostly have common styles! For the perspective, the first file has around 7900 selectors total, and the second one has around 4400:<\/p>\n<figure id=\"attachment_1252\" aria-describedby=\"caption-attachment-1252\" style=\"width: 1553px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1252\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/Code_2018-04-10_19-36-32-1.png\" alt=\"\" width=\"1553\" height=\"839\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/Code_2018-04-10_19-36-32-1.png 1553w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/Code_2018-04-10_19-36-32-1-300x162.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/Code_2018-04-10_19-36-32-1-768x415.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/Code_2018-04-10_19-36-32-1-1024x553.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/Code_2018-04-10_19-36-32-1-1200x648.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption id=\"caption-attachment-1252\" class=\"wp-caption-text\">The <code>grep<\/code> command is <a href=\"https:\/\/stackoverflow.com\/a\/27960271\">from StackOverflow<\/a><\/figcaption><\/figure>\n<p>That\u2019s a good area to optimize. This won\u2019t affect time to first paint if we <a href=\"#decrease-render-blocking-css\">decrease the render-blocking CSS<\/a> properly, but these CSS files will still load faster!<\/p>\n<h1 id=\"add-a-service-worker-to-cache-assets\">Add a service worker to cache assets<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#add-a-service-worker-to-cache-assets\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<p>The Walmart site is not a single-page app. This means that, on different pages, the customer has to download different styles and scripts. This makes every other page load longer, especially if the customer visits the site rarely.<\/p>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<p>We can improve that by creating a service worker. A service worker is a script that runs in the background even when the site is closed. It can make the app work offline, send notifications, and so on.<\/p>\n<\/div>\n<\/div>\n<p>With Walmart, we can create a service worker that caches site resources in the background even before the user needs them. There\u2019re multiple ways to do this; the concrete one depends on the Walmart infrastructure. A good example of one approach is available <a href=\"https:\/\/github.com\/GoogleChrome\/samples\/blob\/gh-pages\/service-worker\/prefetch\/service-worker.js\">in the GoogleChrome repo<\/a>.<\/p>\n<div class='snippet'>\n<h2 id=\"side-note-notifications\">Side note: notifications<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#side-note-notifications\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h2>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<p>With service workers, we also get the ability to send notifications to customers! This should be used with caution \u2013 or we can annoy them \u2013 but this can increase engagement too. Good examples of notifications are \u201cThe product you saved for later got a discount\u201d or \u201cJohn Ford replied to your question about iPhone 8\u201d.<\/p>\n<\/div>\n<\/div>\n<p>To learn more, see <a href=\"https:\/\/developers.google.com\/web\/fundamentals\/push-notifications\/\">the WebFundamentals\u2019 guide into web push notifications<\/a>.<\/div>\n<h1 id=\"other-ideas\">Other ideas<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#other-ideas\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<p>There\u2019s still a room for further optimizations. Here\u2019re some things that might also help \u2013 but we need to confirm that on the real app:<\/p>\n<ul>\n<li><strong>Using the local storage for caching large dependencies.<\/strong> The local storage seems to be several times faster than the HTTP cache. We might store large dependencies in the local storage to load them quicker:https:\/\/twitter.com\/iamakulov\/status\/981950528027611137\n<p><em>Update: see <a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#comment-21103\">the Nolan Lawson\u2019s great comment<\/a> on local storage drawbacks.<\/em><\/li>\n<li><strong>Improving the time to first byte.<\/strong> Occasionally, the server spends too much time serving static resources. See the long green bars? That\u2019s the time spent waiting for the server:\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1279\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_01-44-08.png\" alt=\"\" width=\"1454\" height=\"857\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_01-44-08.png 1454w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_01-44-08-300x177.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_01-44-08-768x453.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_01-44-08-1024x604.png 1024w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_01-44-08-1200x707.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/p>\n<p>These delays are non-deterministic \u2013 I\u2019ve seen them pretty often during the analysis, but they keep happening with different resources every time \u2013 so this <em>might<\/em> be a network issue. Still, I\u2019ve noticed them in WebPageTest results too.<\/li>\n<li>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'><strong>Enabling Brotli compression.<\/strong> When you download a text resource from a server, the server would usually compress it with GZip and serve the compressed version. The browser will decompress it later, once received. This compression makes the text <a href=\"https:\/\/css-tricks.com\/the-difference-between-minification-and-gzipping\/\">several times smaller<\/a>.<\/p>\n<p>Apart from GZip, there\u2019s also Brotli \u2013 a pretty new compression algorithm <a href=\"https:\/\/certsimple.com\/blog\/nginx-brotli\">which compresses text 15-20% better<\/a>. Right now, all text resources on the Walmart page are compressed with GZip. It makes sense to try Brotli to see if it improves the average download time.<\/div>\n<\/div>\n<\/li>\n<\/ul>\n<h1 id=\"bonus-increase-the-product-image-quality\"><span class=\"bonus-badge\">Bonus<\/span> Increase the product image quality<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#bonus-increase-the-product-image-quality\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<p>That\u2019s kinda related to performance too.<\/p>\n<p>To reduce the size of the images, Walmart compresses them on the server side. The client specifies the image dimensions it expects to receive, and the server sends the appropriate image:<\/p>\n<pre><code class=\"no-highlight\">https:\/\/i5.walmartimages.com\/[hash].jpeg?odnHeight=&odnWidth=&odnBg=\n<\/code><\/pre>\n<p>In most cases, this is great. However, for the primary product images, this has a negative effect. When buying an expensive gadget, I often make a final decision by visiting the product page to see the gadget, to imagine how it looks in my hands. But when I come to the Walmart site, I see a low-quality image with compression artifacts:<\/p>\n<figure id=\"attachment_1267\" aria-describedby=\"caption-attachment-1267\" style=\"width: 1066px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1267\" style=\"border: 1px solid #ccc;\" src=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_00-23-32.png\" alt=\"\" width=\"1066\" height=\"866\" srcset=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_00-23-32.png 1066w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_00-23-32-300x244.png 300w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_00-23-32-768x624.png 768w, https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/chrome_2018-04-11_00-23-32-1024x832.png 1024w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption id=\"caption-attachment-1267\" class=\"wp-caption-text\">See yourself <a href=\"https:\/\/www.walmart.com\/ip\/Nokia-Microsoft-Lumia-635-4G-LTE-Unlocked-8GB-Bright-Yellow\/407834961\">on WebArchive<\/a><\/figcaption><\/figure>\n<p>I\u2019d optimize this part for UX instead of performance \u2013 and serve images in a better quality. We can still keep the size difference minimal:<\/p>\n<ul>\n<li><strong>Try a different encoding algorithm.<\/strong> WebP is 30% smaller than JPEG <a href=\"https:\/\/images.guide\/#how-does-webp-perform\">given the same compression level<\/a>. MozJPEG is an optimized JPEG encoder that works everywhere and <a href=\"https:\/\/images.guide\/#how-far-have-we-come-from-the-jpeg\">has significantly less compression artifacts<\/a>.<\/li>\n<li><strong>Use progressive images.<\/strong> Usually, during loading, images are rendered top-to-bottom: you see the top part of the image first, and then it fills<\/li>\n<li>\n<div class='annotated-element annotated-element_has-code_no annotated-element_keep-size_no annotated-element_mobile-direction_content-then-annotation'>\n<div class='annotated-element__annotation'><\/div>\n<div class='annotated-element__content'>\n<p>&nbsp;<\/p>\n<p><strong>Use the <code>&lt;picture&gt;<\/code> tag to stay compatible with different browsers.<\/strong> For example, we could serve WebP for Chrome and JPEG for other browsers:<\/p>\n<\/div>\n<\/div>\n<pre><code class=\"html\">&lt;picture&gt;\n&lt;source srcset=\"https:\/\/i5.walmartimages.com\/[hash].webp?...\" type=\"image\/webp\"&gt;\n&lt;img src=\"https:\/\/i5.walmartimages.com\/[hash].jpeg?...\"&gt;\n&lt;\/picture&gt;\n<\/code><\/pre>\n<\/li>\n<li><strong>Serve Retina images with <code>&lt;source srcset&gt;<\/code>.<\/strong> Like this:\n<pre><code class=\"html\">&lt;picture&gt;\n&lt;source\nsrcset=\"https:\/\/i5.walmartimages.com\/[hash].webp?odnHeight=450&amp;odnWidth=450,\nhttps:\/\/i5.walmartimages.com\/[hash].webp?odnHeight=900&amp;odnWidth=900 2x\"\ntype=\"image\/webp\"\n&gt;\n&lt;img\nsrc=\"https:\/\/i5.walmartimages.com\/[hash].jpeg?odnHeight=450&amp;odnWidth=450\"\nsrcset=\"https:\/\/i5.walmartimages.com\/[hash].jpeg?odnHeight=900&amp;odnWidth=900 2x\"\n&gt;\n&lt;\/picture&gt;\n<\/code><\/pre>\n<\/li>\n<\/ul>\n<h1 id=\"summing-up\">Summing up<a href=\"https:\/\/iamakulov.com\/notes\/walmart\/#summing-up\" class=\"heading-link\" aria-label=\"Link to this section\" title=\"Link to this section\">#<\/a><\/h1>\n<p>So, to optimize the product page on the Walmart site, we can:<\/p>\n<ul>\n<li>Fix the invisible text with <code>font-display: optional<\/code><\/li>\n<li>Use <code>defer<\/code> for the large JavaScript bundle<\/li>\n<li>Load non-important code with webpack\u2019s <code>import<\/code><\/li>\n<li>Remove polyfills in modern browsers<\/li>\n<li>Decrease render-blocking CSS<\/li>\n<li>Remove duplicated styles<\/li>\n<li>Add a service worker for caching assets in background<\/li>\n<\/ul>\n<p>With these tricks, we can render the product page earlier by at least 400-600 ms. If we apply similar improvements to the whole site, we can increase orders by at least 3\u20136% \u2013 and help Walmart earn more.<\/p>\n<p><em>Thanks to <a href=\"https:\/\/twitter.com\/kurtextrem\">Jacob Gro\u00df<\/a>, <a href=\"https:\/\/twitter.com\/iamstarkov\">Vladimir Starkov<\/a>, and <a href=\"https:\/\/twitter.com\/theKashey\">Anton Korzunov<\/a> (in no particular order) for reviewing this post.<\/em><\/p>\n<p style=\"display: none;\"><!-- A terrible hack. Fuck WordPress for adding brs in arbitrary places --><br \/>\n<script>\/\/ <![CDATA[\ndocument.querySelectorAll('.wp-video').forEach(video => { const children = Array.from(video.children); children.filter(node => node.nodeName.toLowerCase() === 'br').forEach(node => node.remove()); });\n\/\/ ]]><\/script><\/p>\n<style>\n.post-1065 .bonus-badge {\nfont-weight: normal;\nfont-size: 0.7em;\nborder: 1px solid black;\npadding: 2px 4px;\nborder-radius: 2px;\ntext-transform: uppercase;\nletter-spacing: 1px;\nvertical-align: 2px;\n}\n<\/style>\n","protected":false},"excerpt":{"rendered":"<p>Walmart is one of the top USA e-commerce retailers. In 2016, they were the second after Amazon by sales. In e-commerce, the conversion is directly affected by how fast the site loads. For many e-commerce companies, making the site faster by 1 second increased the conversion 1.05, 1.1, or even 1.2 times. That\u2019s because the &hellip; <a href=\"https:\/\/iamakulov.com\/notes\/walmart\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Case study: analyzing the Walmart site performance&#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":[15,12],"class_list":["post-1065","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-performance","tag-webpack"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v23.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Case study: analyzing the Walmart site performance - Ivan Akulov\u2019s blog<\/title>\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\/walmart\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Case study: analyzing the Walmart site performance\" \/>\n<meta property=\"og:description\" content=\"Let\u2019s look how to make the product page faster (&amp; increase the conversion)\" \/>\n<meta property=\"og:url\" content=\"https:\/\/iamakulov.com\/notes\/walmart\/\" \/>\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=\"2018-04-27T07:49:53+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-01-08T03:47:02+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/facebook.png\" \/>\n\t<meta property=\"og:image:width\" content=\"2400\" \/>\n\t<meta property=\"og:image:height\" content=\"1260\" \/>\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=\"Case study: analyzing the Walmart site performance\" \/>\n<meta name=\"twitter:description\" content=\"Let\u2019s look how to make the product page faster (&amp; increase the conversion)\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/twitter.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=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/iamakulov.com\/notes\/walmart\/\",\"url\":\"https:\/\/iamakulov.com\/notes\/walmart\/\",\"name\":\"Case study: analyzing the Walmart site performance - Ivan Akulov\u2019s blog\",\"isPartOf\":{\"@id\":\"https:\/\/iamakulov.com\/notes\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/iamakulov.com\/notes\/walmart\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/iamakulov.com\/notes\/walmart\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43.png\",\"datePublished\":\"2018-04-27T07:49:53+00:00\",\"dateModified\":\"2024-01-08T03:47:02+00:00\",\"author\":{\"@id\":\"https:\/\/iamakulov.com\/notes\/#\/schema\/person\/ebf7b61bf573e7be5fe438f50ebd9b81\"},\"breadcrumb\":{\"@id\":\"https:\/\/iamakulov.com\/notes\/walmart\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/iamakulov.com\/notes\/walmart\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/iamakulov.com\/notes\/walmart\/#primaryimage\",\"url\":\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43.png\",\"contentUrl\":\"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43.png\",\"width\":1863,\"height\":408},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/iamakulov.com\/notes\/walmart\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/iamakulov.com\/notes\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Case study: analyzing the Walmart site performance\"}]},{\"@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\/aec4e9e944911b58f2c3d14b7f9e5412a217e5738359c3f52e824b4de2b2263c?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/aec4e9e944911b58f2c3d14b7f9e5412a217e5738359c3f52e824b4de2b2263c?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":"Case study: analyzing the Walmart site performance - Ivan Akulov\u2019s blog","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\/walmart\/","og_locale":"en_US","og_type":"article","og_title":"Case study: analyzing the Walmart site performance","og_description":"Let\u2019s look how to make the product page faster (& increase the conversion)","og_url":"https:\/\/iamakulov.com\/notes\/walmart\/","og_site_name":"Ivan Akulov\u2019s blog","article_publisher":"http:\/\/facebook.com\/iamakulov.page","article_published_time":"2018-04-27T07:49:53+00:00","article_modified_time":"2024-01-08T03:47:02+00:00","og_image":[{"width":2400,"height":1260,"url":"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/facebook.png","type":"image\/png"}],"author":"Ivan Akulov","twitter_card":"summary_large_image","twitter_title":"Case study: analyzing the Walmart site performance","twitter_description":"Let\u2019s look how to make the product page faster (& increase the conversion)","twitter_image":"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/twitter.png","twitter_creator":"@iamakulov","twitter_site":"@iamakulov","twitter_misc":{"Written by":"Ivan Akulov","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/iamakulov.com\/notes\/walmart\/","url":"https:\/\/iamakulov.com\/notes\/walmart\/","name":"Case study: analyzing the Walmart site performance - Ivan Akulov\u2019s blog","isPartOf":{"@id":"https:\/\/iamakulov.com\/notes\/#website"},"primaryImageOfPage":{"@id":"https:\/\/iamakulov.com\/notes\/walmart\/#primaryimage"},"image":{"@id":"https:\/\/iamakulov.com\/notes\/walmart\/#primaryimage"},"thumbnailUrl":"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43.png","datePublished":"2018-04-27T07:49:53+00:00","dateModified":"2024-01-08T03:47:02+00:00","author":{"@id":"https:\/\/iamakulov.com\/notes\/#\/schema\/person\/ebf7b61bf573e7be5fe438f50ebd9b81"},"breadcrumb":{"@id":"https:\/\/iamakulov.com\/notes\/walmart\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/iamakulov.com\/notes\/walmart\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/iamakulov.com\/notes\/walmart\/#primaryimage","url":"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43.png","contentUrl":"https:\/\/iamakulov.com\/notes2\/wp-content\/uploads\/2018\/04\/ApplicationFrameHost_2018-03-31_12-53-43.png","width":1863,"height":408},{"@type":"BreadcrumbList","@id":"https:\/\/iamakulov.com\/notes\/walmart\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/iamakulov.com\/notes\/"},{"@type":"ListItem","position":2,"name":"Case study: analyzing the Walmart site performance"}]},{"@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\/aec4e9e944911b58f2c3d14b7f9e5412a217e5738359c3f52e824b4de2b2263c?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/aec4e9e944911b58f2c3d14b7f9e5412a217e5738359c3f52e824b4de2b2263c?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\/1065","targetHints":{"allow":["GET"]}}],"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=1065"}],"version-history":[{"count":237,"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/posts\/1065\/revisions"}],"predecessor-version":[{"id":1469,"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/posts\/1065\/revisions\/1469"}],"wp:attachment":[{"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/media?parent=1065"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/categories?post=1065"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/iamakulov.com\/notes\/wp-json\/wp\/v2\/tags?post=1065"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}