{"id":83276,"date":"2022-08-04T07:47:54","date_gmt":"2022-08-04T05:47:54","guid":{"rendered":"https:\/\/rmarketingdigital.com\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/"},"modified":"2021-07-06T10:06:42","modified_gmt":"2021-07-06T08:06:42","slug":"techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone","status":"publish","type":"post","link":"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/","title":{"rendered":"Techniques to make a web app load fast, even on a feature phone"},"content":{"rendered":"<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_86 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewbox=\"0 0 24 24\" version=\"1.2\" baseprofile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/#Capturing-the-status-quo\" >Capturing the status quo<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/#Waterfall-analysis\" >Waterfall analysis<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/#Reducing-connection-count\" >Reducing connection count<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/#Parallelizing-loads\" >Parallelizing loads<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/#Results\" >Results<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/#Prerendering\" >Prerendering<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/#Inlining\" >Inlining<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/#Aggressive-code-splitting\" >Aggressive code splitting<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/#More-Sleight-of-Hand\" >More Sleight of Hand<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniques-to-make-a-web-app-load-fast-even-on-a-feature-phone\/#Conclusion\" >Conclusion<\/a><\/li><\/ul><\/nav><\/div>\n<p><\/p>\n<div>\n<header class=\"w-article-header\">\n<p class=\"w-article-header__subhead w-mb--non\">\n          How we used code splitting, code inlining, and server-side rendering in PROXX.\n        <\/p>\n<p>\n          <time>Sep 23, 2019<\/time><\/p>\n<\/header>\n<p>At Google I \/ O 2019 Mariko, Jake, and I shipped <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/proxx.app\">PROXX<\/a>, a modern Minesweeper-clone for the web. Something that sets PROXX apart is the focus on accessibility (you can play it with a screenreader!) And the ability to run as well on a feature phone as on a high-end desktop device. Feature phones are constrained in multiple ways:<\/p>\n<ul>\n<li>Weak CPUs<\/li>\n<li>Weak or non-existent GPUs<\/li>\n<li>Small screens without touch input<\/li>\n<li>Very limited amounts of memory<\/li>\n<\/ul>\n<p>But they run a modern browser and are very affordable. For this reason, feature phones are making a resurgence in emerging markets. Their price point allows a whole new audience, who previously couldn&#039;t afford it, to come online and make use of the modern web. <strong><a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/www.counterpointresearch.com\/more-than-a-billion-feature-phones-to-be-sold-over-next-three-years\/\">For 2019 it is projected that around 400 million feature phones will be sold in India alone<\/a><\/strong>, so users on feature phones might become a significant portion of your audience. In addition to that, connection speeds akin to 2G are the norm in emerging markets. How did we manage to make PROXX work well under feature phone conditions?<\/p>\n<figure class=\"w-figure\">\n  <video controls=\"\" loop=\"\" muted=\"\" preload=\"metadata\" class=\"w-screenshot\" poster=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/social_supercut_poster.jpg\" src=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/social_supercut_x264.mp4\" type=\"video\/mp4; codecs=h264\"><\/video><figcaption class=\"w-figcaption w-figcaption--fullbleed\">\n    PROXX gameplay.<br \/>\n  <\/figcaption><\/figure>\n<p>Performance is important, and that includes both loading performance and runtime performance. It has been shown that <strong>good performance correlates with increased user retention, improved conversions and \u2014 most importantly \u2014 increased inclusivity.<\/strong> <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/twitter.com\/malchata\">Jeremy wagner<\/a> has much more data and insight on <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/developers.google.com\/web\/fundamentals\/performance\/why-performance-matters\/\">why performance matters<\/a>.<\/p>\n<p>This is part 1 of a two-part series. <strong>Part 1 focuses on loading performance<\/strong>, and part 2 will focus on runtime performance.<\/p>\n<h2 id=\"capturing-the-status-quo\"><span class=\"ez-toc-section\" id=\"Capturing-the-status-quo\"><\/span>Capturing the status quo <span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Testing your loading performance on a <em>real<\/em> device is critical. If you don&#039;t have a real device at hand, I recommend <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/webpagetest.org\">WebPageTest<\/a> (WPT), specifically the <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/webpagetest.org\/easy\">&quot;Simple&quot; setup<\/a>. <strong>WPT runs a battery of loading tests on a <em>real<\/em> device with an emulated 3G connection.<\/strong><\/p>\n<p>3G is a good speed to measure. While you might be used to 4G, LTE or soon even 5G, the reality of mobile internet looks quite different. Maybe you&#039;re on a train, at a conference, at a concert, or on a flight. What you&#039;ll be experiencing there is most likely closer to 3G, and sometimes even worse.<\/p>\n<p>That being said, we&#039;re going to focus on 2G in this article because PROXX is explicitly targeting feature phones and emerging markets in its target audience. Once WebPageTest has run its test, you get a waterfall (equivalent to what you see in DevTools) as well as a filmstrip at the top. The film strip shows what your user sees while your app is loading. On 2G, the loading experience of the unoptimized version of PROXX is pretty bad:<\/p>\n<figure class=\"w-figure\">\n  <video controls=\"\" muted=\"\" preload=\"metadata\" class=\"w-screenshot\" poster=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/stupid-proxx-load-poster.jpg\" src=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/stupid-proxx-load.mp4\" type=\"video\/mp4; codecs=h264\"><\/video><figcaption class=\"w-figcaption w-figcaption--fullbleed\">\n    The filmstrip video shows what the user sees when PROXX is loading on a real, low-end device over an emulated 2G connection.<br \/>\n  <\/figcaption><\/figure>\n<p>When loaded over 3G, the user sees 4 seconds of white nothingness. <strong>Over 2G the user sees absolutely nothing for over 8 seconds.<\/strong> If you read <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/developers.google.com\/web\/fundamentals\/performance\/why-performance-matters\/\">why performance matters<\/a> you know that we have now lost a good portion of our potential users due to impatience. The user needs to download all of the 62 KB of JavaScript for anything to appear on screen. The silver lining in this scenario is that the second anything appears on screen it is also interactive. Or is it?<\/p>\n<figure class=\"w-figure\">\n  <picture><source srcset=\"https:\/\/web.dev\/proxx-first-render.webp\" type=\"image\/webp\"><img alt='proxx-first-render-4341069' src='https:\/\/rmarketingdigital.com\/wp-content\/uploads\/2020\/09\/proxx-first-render-4341069.jpg'><br \/>\n  <\/source><\/picture><figcaption class=\"w-figcaption\">\n<p>The First Meaningful Paint in the unoptimized version of PROXX is <em>technically<\/em> interactive but useless to the user.<\/p>\n<\/figcaption><\/figure>\n<p>After about 62 KB of gzip&#039;d JS has been downloaded and the DOM has been generated, the user gets to see our app. The app is <em>technically<\/em> interactive. Looking at the visual, however, shows a different reality. The web fonts are still loading in the background and until they are ready the user can see no text. While this state qualifies as a First Meaningful Paint (FMP), it surely does not qualify as properly interactive, as the user can&#039;t tell what any of the inputs are about. It takes another second on 3G and 3 seconds on 2G until the app is ready to go. <strong>All in all, the app takes 6 seconds on 3G and 11 seconds on 2G to become interactive.<\/strong><\/p>\n<h2 id=\"waterfall-analysis\"><span class=\"ez-toc-section\" id=\"Waterfall-analysis\"><\/span>Waterfall analysis <span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Now that we know <em>que<\/em> the user sees, we need to figure out the <em>why<\/em>. For this we can look at the waterfall and analyze why resources are loading too late. In our 2G trace for PROXX we can see two major red flags:<\/p>\n<ol>\n<li>There are multiple, multi-colored thin lines.<\/li>\n<li>JavaScript files form a chain. For example, the second resource only starts loading once the first resource is finished, and the third resource only starts when the second resource is finished.<\/li>\n<\/ol>\n<figure class=\"w-figure\">\n  <picture>\n    <img alt='waterfall_opt-8683308' src='https:\/\/rmarketingdigital.com\/wp-content\/uploads\/2020\/09\/waterfall_opt-8683308.png'>\n  <\/picture><figcaption class=\"w-figcaption\">\n    The waterfall gives insight into which resources are loading when and how long they take.<br \/>\n  <\/figcaption><\/figure>\n<h3 id=\"reducing-connection-count\"><span class=\"ez-toc-section\" id=\"Reducing-connection-count\"><\/span>Reducing connection count <span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Each thin line (<code>dns<\/code>, <code>connect<\/code>, <code>ssl<\/code>) stands for the creation of a new HTTP connection. Setting up a new connection is costly as it takes around 1s on 3G and roughly 2.5s on 2G. In our waterfall we see a new connection for:<\/p>\n<ul>\n<li>Request #1: Our <code>index.html<\/code><\/li>\n<li>Request #5: The font styles from <code>fonts.googleapis.com<\/code><\/li>\n<li>Request #8: Google Analytics<\/li>\n<li>Request #9: A font file from <code>fonts.gstatic.com<\/code><\/li>\n<li>Request #14: The Web App Manifest<\/li>\n<\/ul>\n<p>The new connection for <code>index.html<\/code> is unavoidable. The browser <em>have you<\/em> to create a connection to our server to get the contents. The new connection for Google Analytics could be avoided by inlining something like <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/minimalanalytics.com\">Minimal Analytics<\/a>, but Google Analytics is not blocking our app from rendering or becoming interactive, so we don&#039;t really care about how fast it loads. Ideally, Google Analytics should be loaded in idle time, when everything else has already loaded. That way it won&#039;t take up bandwidth or processing power during the initial load. The new connection for the web app manifest is <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/fetch.spec.whatwg.org\/#connections\">prescribed by the fetch spec<\/a>, as the manifest has to be loaded over a non-credentialed connection. Again, the web app manifest doesn&#039;t block our app from rendering or becoming interactive, so we don&#039;t need to care that much.<\/p>\n<p>The two fonts and their styles, however, are a problem as they block rendering and also interactivity. If we look at the CSS that is delivered by <code>fonts.googleapis.com<\/code>, it&#039;s just two <code>@ font-face<\/code> rules, one for each font. The font <em>styles<\/em> are so small in fact, that we decided to inline it into our HTML, removing one unnecessary connection. To avoid the cost of the connection setup for the font <em>files<\/em>, we can copy them to our own server.<\/p>\n<div class=\"w-aside w-aside--note\">\n<p><strong>Note:<\/strong> Copying CSS or font files to your own server is okay when using <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/fonts.google.com\">Google Fonts<\/a>. Other font providers might have different rules. Please check with your font provider&#039;s terms of service!<\/p>\n<\/div>\n<h3 id=\"parallelizing-loads\"><span class=\"ez-toc-section\" id=\"Parallelizing-loads\"><\/span>Parallelizing loads <span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Looking at the waterfall, we can see that once the first JavaScript file is done loading, new files start loading immediately. This is typical for module dependencies. Our main module probably has static imports, so the JavaScript cannot run until those imports are loaded. The important thing to realize here is that these kinds of dependencies are known at build time. We can make use of <code><\/code> tags to make sure all dependencies start loading the second we receive our HTML.<\/p>\n<h3 id=\"results\"><span class=\"ez-toc-section\" id=\"Results\"><\/span>Results <span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Let&#039;s take a look at what our changes have achieved. It&#039;s important to not change any other variables in our test setup that could skew the results, so we will be using <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/webpagetest.org\/easy\">WebPageTest&#039;s simple setup<\/a> for the rest of this article and look at the filmstrip:<\/p>\n<figure class=\"w-figure\">\n  <video controls=\"\" muted=\"\" preload=\"metadata\" class=\"w-screenshot\" poster=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/preload-proxx-load-poster.jpg\" src=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/preload-proxx-load.mp4\" type=\"video\/mp4; codecs=h264\"><\/video><figcaption class=\"w-figcaption w-figcaption--fullbleed\">\n    We use WebPageTest&#039;s filmstrip to see what our changes have achieved.<br \/>\n  <\/figcaption><\/figure>\n<p><strong>These changes reduced our TTI from 11 to 8.5<\/strong>, which is roughly the 2.5s of connection setup time we aimed to remove. Well done us.<\/p>\n<h2 id=\"prerendering\"><span class=\"ez-toc-section\" id=\"Prerendering\"><\/span>Prerendering <span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>While we just reduced our TTI, we haven&#039;t really affected the eternally long white screen the user has to endure for 8.5 seconds. Arguably <strong>the biggest improvements for FMP can be achieved by sending styled markup in your <code>index.html<\/code><\/strong>. Common techniques to achieve this are prerendering and server-side rendering, which are closely related and are explained in <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/developers.google.com\/web\/updates\/2019\/02\/rendering-on-the-web\">Rendering on the Web<\/a>. Both techniques run the web app in Node and serialize the resulting DOM to HTML. Server-side rendering does this per request on the, well, server side, while prerendering does this at build time and stores the output as your new <code>index.html<\/code>. Since PROXX is a <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/jamstack.org\/\">JAMStack<\/a> app and has no server side, we decided to implement prerendering.<\/p>\n<p>There are many ways to implement a prerenderer. In PROXX we chose to use <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/pptr.dev\">Puppeteer<\/a>, which starts Chrome without any UI and allows you to remote control that instance with a Node API. We use this to inject our markup and our JavaScript and then read back the DOM as a string of HTML. Because we are using <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/github.com\/css-modules\/css-modules\">CSS Modules<\/a>, we get CSS inlining of the styles that we need for free.<\/p>\n<p><web-copy-code><\/p>\n<pre class=\"language-js\"><code class=\"language-js\">  <span class=\"token keyword\">const<\/span> browser <span class=\"token operator\">=<\/span> <span class=\"token keyword\">await<\/span> puppeteer<span class=\"token punctuation\">.<\/span><span class=\"token function\">launch<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token keyword\">const<\/span> page <span class=\"token operator\">=<\/span> <span class=\"token keyword\">await<\/span> browser<span class=\"token punctuation\">.<\/span><span class=\"token function\">newPage<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token keyword\">await<\/span> page<span class=\"token punctuation\">.<\/span><span class=\"token function\">setContent<\/span><span class=\"token punctuation\">(<\/span>rawIndexHTML<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token keyword\">await<\/span> page<span class=\"token punctuation\">.<\/span><span class=\"token function\">evaluate<\/span><span class=\"token punctuation\">(<\/span>codeToRun<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token keyword\">const<\/span> renderedHTML <span class=\"token operator\">=<\/span> <span class=\"token keyword\">await<\/span> page<span class=\"token punctuation\">.<\/span><span class=\"token function\">content<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br>browser<span class=\"token punctuation\">.<\/span><span class=\"token function\">close<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token keyword\">await<\/span> <span class=\"token function\">writeFile<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"index.html\"<\/span><span class=\"token punctuation\">,<\/span> renderedHTML<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<p><\/web-copy-code><\/p>\n<p>With this in place, we can expect an improvement for our FMP. We still need to load and execute the same amount of JavaScript as before, so we shouldn&#039;t expect TTI to change much. If anything, our <code>index.html<\/code> has gotten bigger and might push back our TTI a bit. There&#039;s only one way to find out: running WebPageTest.<\/p>\n<figure class=\"w-figure\">\n  <video controls=\"\" muted=\"\" preload=\"metadata\" class=\"w-screenshot\" poster=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/ssr-proxx-load-poster.jpg\" src=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/ssr-proxx-load.mp4\" type=\"video\/mp4; codecs=h264\"><\/video><figcaption class=\"w-figcaption w-figcaption--fullbleed\">\n    The filmstrip shows a clear improvement for our FMP metric. TTI is mostly unaffected.<br \/>\n  <\/figcaption><\/figure>\n<p><strong>Our First Meaningful Paint has moved from 8.5 seconds to 4.9 seconds,<\/strong> a massive improvement. Our TTI still happens at around 8.5 seconds so it has been largely unaffected by this change. What we did here is a <em>perceptual<\/em> change. Some might even call it a sleight of hand. By rendering an intermediate visual of the game, we are changing the perceived loading performance for the better.<\/p>\n<h2 id=\"inlining\"><span class=\"ez-toc-section\" id=\"Inlining\"><\/span>Inlining <span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Another metric that both DevTools and WebPageTest give us is Time To First Byte (TTFB). This is the time it takes from the first byte of the request being sent to the first byte of the response being received. This time is also often called a Round Trip Time (RTT), although technically there is a difference between these two numbers: RTT does not include the processing time of the request on the server side. <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/developers.google.com\/web\/tools\/chrome-devtools\/network\/reference#timing-preview\">DevTools<\/a> and WebPageTest visualize TTFB with a light color within the request \/ response block.<\/p>\n<figure class=\"w-figure\">\n  <picture>\n<\/picture><figcaption class=\"w-figcaption\">\n    The light section of a request signifies the request is waiting to receive the first byte of the response.<br \/>\n  <\/figcaption><\/figure>\n<p>Looking at our waterfall, we can see that the <strong>all of requests spend the <em>majority<\/em> of their time waiting<\/strong> for the first byte of the response to arrive.<\/p>\n<p>This problem was what HTTP \/ 2 Push was originally conceived for. The app developer <em>knows<\/em> that certain resources are needed and can <em>push<\/em> them down the wire. By the time the client realizes that it needs to fetch additional resources, they are already in the browser&#039;s caches. <strong>HTTP \/ 2 Push turned out to be too hard to get right and is considered discouraged.<\/strong> This problem space will be revisited during the standardization of HTTP \/ 3. For now, <strong>the easiest solution is to <em>inline<\/em> all the critical resources<\/strong> at the expense of caching efficiency.<\/p>\n<p>Our critical CSS is already inlined thanks to CSS Modules and our Puppeteer-based prerenderer. For JavaScript we need to inline our critical modules <em>and their dependencies<\/em>. This task has varying difficulty, based on the bundler that you&#039;re using.<\/p>\n<div class=\"w-aside w-aside--note\">\n<p><strong>Note:<\/strong> In this step we also subset our font files to contain only the glyphs that we need for our landing page. I am not going to go into detail on this step as it is not easily abstracted and sometimes not even practical. We still load the full font files lazily, but they are not needed for the initial render.<\/p>\n<\/div>\n<figure class=\"w-figure\">\n  <video controls=\"\" muted=\"\" preload=\"metadata\" class=\"w-screenshot\" poster=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/ssr-proxx-load-poster.jpg\" src=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/ssr-proxx-load.mp4\" type=\"video\/mp4; codecs=h264\"><\/video><figcaption class=\"w-figcaption w-figcaption--fullbleed\">\n    With the inlining of our JavaScript we have reduced our TTI from 8.5s to 7.2s.<br \/>\n  <\/figcaption><\/figure>\n<p>This shaved 1 second off our TTI. We have now reached the point where our <code>index.html<\/code> contains everything that is needed for the initial render and becoming interactive. The HTML can render while it is still downloading, creating our FMP. The moment the HTML is done parsing and executing, the app is interactive.<\/p>\n<h2 id=\"aggressive-code-splitting\"><span class=\"ez-toc-section\" id=\"Aggressive-code-splitting\"><\/span>Aggressive code splitting <span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Yes, our <code>index.html<\/code> contains everything that is needed to become interactive. But on closer inspection it turns out it also contains everything else. Our <code>index.html<\/code> is around 43 KB. Let&#039;s put that in relation to what the user can interact with at the start: We have a form to configure the game containing a couple of components, a start button and probably some code to persist and load user settings. That&#039;s pretty much it. 43 KB seems like a lot.<\/p>\n<figure class=\"w-figure\">\n  <picture><source srcset=\"https:\/\/web.dev\/proxx-loaded.webp\" type=\"image\/webp\"><img alt='proxx-loaded-1500619' src='https:\/\/rmarketingdigital.com\/wp-content\/uploads\/2020\/09\/proxx-loaded-1500619.jpg'><br \/>\n  <\/source><\/picture><figcaption class=\"w-figcaption\">\n    The landing page of PROXX. Only critical components are used here.<br \/>\n  <\/figcaption><\/figure>\n<p>To understand where our bundle size is coming from we can use a <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/npm.im\/source-map-explorer\">source map explorer<\/a> or an equivalent tool to break down what the bundle consists of. As predicted, our bundle contains the game logic, the rendering engine, the win screen, the lose screen and a bunch of utilities. Only a small subset of these modules are needed for the landing page. Moving everything that is not strictly required for interactivity into a lazily-loaded module will decrease TTI <em>significantly<\/em>.<\/p>\n<figure class=\"w-figure\">\n  <picture>\n<\/picture><figcaption class=\"w-figcaption\">\n    Analyzing the contents of PROXX&#039;s `index.html` shows a lot of unneeded resources. Critical resources are highlighted.<br \/>\n  <\/figcaption><\/figure>\n<p>What we need to do is code split. Code splitting breaks apart your monolithic bundle into smaller parts that can be lazy-loaded on-demand. Popular bundlers like <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/webpack.js.org\">Webpack<\/a>, <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/rollupjs.org\">Rollup<\/a>, and <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/parceljs.org\">Parcel<\/a> support code splitting by using dynamic <code>import ()<\/code>. The bundler will analyze your code and <em>inline<\/em> all modules that are imported <em>statically<\/em>. Everything that you import <em>dynamically<\/em> will be put into its own file and will only be fetched from the network once the <code>import ()<\/code> call gets executed. Of course hitting the network has a cost and should only be done if you have the time to spare. <strong>The mantra here is to statically import the modules that are <em>critically<\/em> needed at load time and dynamically load everything else.<\/strong> But you shouldn&#039;t wait to the very last moment to lazy-load modules that are definitely going to be used. <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/twitter.com\/philwalton\">Phil Walton<\/a>&#039;s <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/philipwalton.com\/articles\/idle-until-urgent\/\">Idle Until Urgent<\/a> is a great pattern for a healthy middle ground between lazy loading and eager loading.<\/p>\n<p>In PROXX we created a <code>lazy.js<\/code> file that statically imports everything that we <em>no<\/em> need. In our main file, we can then <em>dynamically<\/em> import <code>lazy.js<\/code>. However, some of our <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/preactjs.com\">Preact<\/a> components ended up in <code>lazy.js<\/code>, which turned out to be a bit of a complication as Preact can&#039;t handle lazily-loaded components out of the box. For this reason we wrote a little <code>deferred<\/code> component wrapper that allows us to render a placeholder until the actual component has loaded.<\/p>\n<p><web-copy-code><\/p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">export<\/span> <span class=\"token keyword\">default<\/span> <span class=\"token keyword\">function<\/span> <span class=\"token function\">deferred<\/span><span class=\"token punctuation\">(<\/span><span class=\"token parameter\">componentPromise<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br><span class=\"token keyword\">return<\/span> <span class=\"token keyword\">class<\/span> <span class=\"token class-name\">Deferred<\/span> <span class=\"token keyword\">extends<\/span> <span class=\"token class-name\">Component<\/span> <span class=\"token punctuation\">{<\/span><br><span class=\"token function\">constructor<\/span><span class=\"token punctuation\">(<\/span><span class=\"token parameter\">props<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br><span class=\"token keyword\">super<\/span><span class=\"token punctuation\">(<\/span>props<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token keyword\">Este<\/span><span class=\"token punctuation\">.<\/span>state <span class=\"token operator\">=<\/span> <span class=\"token punctuation\">{<\/span><br>LoadedComponent<span class=\"token operator\">:<\/span> <span class=\"token keyword\">undefined<\/span><br><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">;<\/span><br>componentPromise<span class=\"token punctuation\">.<\/span><span class=\"token function\">then<\/span><span class=\"token punctuation\">(<\/span><span class=\"token parameter\">component<\/span> <span class=\"token operator\">=&gt;<\/span> <span class=\"token punctuation\">{<\/span><br><span class=\"token keyword\">Este<\/span><span class=\"token punctuation\">.<\/span><span class=\"token function\">setState<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">{<\/span> LoadedComponent<span class=\"token operator\">:<\/span> component <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token punctuation\">}<\/span><p><span class=\"token function\">render<\/span><span class=\"token punctuation\">(<\/span><span class=\"token parameter\"><span class=\"token punctuation\">{<\/span> loaded<span class=\"token punctuation\">,<\/span> loading <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token punctuation\">{<\/span> LoadedComponent <span class=\"token punctuation\">}<\/span><\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br><span class=\"token keyword\">if<\/span> <span class=\"token punctuation\">(<\/span>LoadedComponent<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br><span class=\"token keyword\">return<\/span> <span class=\"token function\">loaded<\/span><span class=\"token punctuation\">(<\/span>LoadedComponent<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token punctuation\">}<\/span><br><span class=\"token keyword\">return<\/span> <span class=\"token function\">loading<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token punctuation\">}<\/span><br><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">;<\/span><br><span class=\"token punctuation\">}<\/span><\/p><\/code><\/pre>\n<p><\/web-copy-code><\/p>\n<p>With this in place, we can use a Promise of a component in our <code>render ()<\/code> functions. For example, the <code><\/code> component, which renders the animated background image, will be replaced by an empty <code>&lt;div&gt;<\/code> while the component is loading. Once the component is loaded and ready to use, the <code>&lt;div&gt;<\/code> will be replaced with the actual component.<\/p>\n<p><web-copy-code><\/p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const<\/span> NebulaDeferred <span class=\"token operator\">=<\/span> <span class=\"token function\">deferred<\/span><span class=\"token punctuation\">(<\/span><br><span class=\"token keyword\">import<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"\/components\/nebula\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">.<\/span><span class=\"token function\">then<\/span><span class=\"token punctuation\">(<\/span><span class=\"token parameter\">m<\/span> <span class=\"token operator\">=&gt;<\/span> m<span class=\"token punctuation\">.<\/span>default<span class=\"token punctuation\">)<\/span><br><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><p><span class=\"token keyword\">return<\/span> <span class=\"token punctuation\">(<\/span><br><span class=\"token operator\">&lt;<\/span>NebulaDeferred<br>loading<span class=\"token operator\">=<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token operator\">=&gt;<\/span> <span class=\"token operator\">&lt;<\/span>div <span class=\"token operator\">\/<\/span><span class=\"token operator\">&gt;<\/span><span class=\"token punctuation\">}<\/span><br>loaded<span class=\"token operator\">=<\/span><span class=\"token punctuation\">{<\/span><span class=\"token parameter\">Nebula<\/span> <span class=\"token operator\">=&gt;<\/span> <span class=\"token operator\">&lt;<\/span>Nebula <span class=\"token operator\">\/<\/span><span class=\"token operator\">&gt;<\/span><span class=\"token punctuation\">}<\/span><br><span class=\"token operator\">\/<\/span><span class=\"token operator\">&gt;<\/span><br><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/p><\/code><\/pre>\n<p><\/web-copy-code><\/p>\n<p>With all of this in place, we reduced our <code>index.html<\/code> to a mere 20 KB, less than half of the original size. What effect does this have on FMP and TTI? WebPageTest will tell!<\/p>\n<figure class=\"w-figure\">\n  <video controls=\"\" muted=\"\" preload=\"metadata\" class=\"w-screenshot\" poster=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/optimized-proxx-load-poster.jpg\" src=\"https:\/\/storage.googleapis.com\/web-dev-assets\/js-heavy-sites\/optimized-proxx-load.mp4\" type=\"video\/mp4; codecs=h264\"><\/video><figcaption class=\"w-figcaption w-figcaption--fullbleed\">\n    The filmstrip confirms: Our TTI is now at 5.4s. A drastic improvement from our original 11s.<br \/>\n  <\/figcaption><\/figure>\n<p>Our FMP and TTI are only 100ms apart, as it is only a matter of parsing and executing the inlined JavaScript. After just 5.4s on 2G, the app is completely interactive. All the other, less essential modules are loaded in the background.<\/p>\n<h2 id=\"more-sleight-of-hand\"><span class=\"ez-toc-section\" id=\"More-Sleight-of-Hand\"><\/span>More Sleight of Hand <span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If you look at our list of critical modules above, you&#039;ll see that the rendering engine is not part of the critical modules. Of course, the game cannot start until we have our rendering engine to render the game. We could disable the \u00abStart\u00bb button until our rendering engine is ready to start the game, but in our experience the user usually takes long enough to configure their game settings that this isn&#039;t necessary. Most of the time the rendering engine and the other remaining modules are done loading by the time the user presses \u00abStart\u00bb. In the rare case that the user is quicker than their network connection, we show a simple loading screen that waits for the remaining modules to finish.<\/p>\n<h2 id=\"conclusion\"><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion <span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Measuring is important. To avoid spending time on problems that are not real, we recommend to always measure first before implementing optimizations. Additionally, measurements should be done on <em>real<\/em> devices on a 3G connection or on <a rel=\"nofollow noopener noreferrer\" target=\"_blank\" href=\"https:\/\/webpagetest.org\/easy\">WebPageTest<\/a> if no real device is at hand.<\/p>\n<p>The filmstrip can give insight into how loading your app <em>feels<\/em> for the user. The waterfall can tell you what resources are responsible for potentially long loading times. Here&#039;s a checklist of things you can do to improve loading performance:<\/p>\n<ul>\n<li>Deliver as many assets as possible over one connection.<\/li>\n<li>Preload or even inline resources that are required for the first render and interactivity.<\/li>\n<li>Prerender your app to improve perceived loading performance.<\/li>\n<li>Make use of aggressive code splitting to reduce the amount of code needed for interactivity.<\/li>\n<\/ul>\n<p>Stay tuned for part 2 where we discuss how to optimize runtime performance on hyper-constrained devices.<\/p>\n<p>    <web-feedback additional-questions=\"\"><\/web-feedback>\n  <\/div>\n<p><script async src=\"\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><\/p>","protected":false},"excerpt":{"rendered":"<p>How we used code splitting, code inlining, and server-side rendering in PROXX. Sep 23, 2019 At Google I\/O 2019 Mariko, Jake, and I shipped PROXX, a modern Minesweeper-clone for the\u2026<\/p>","protected":false},"author":1,"featured_media":83277,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1356],"tags":[],"class_list":["post-83276","post","type-post","status-publish","format-standard","has-post-thumbnail","category-dev"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.0 (Yoast SEO v28.2) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Techniques to make a web app load fast, even on a feature phone - R Marketing Digital<\/title>\n<meta name=\"description\" content=\"How we used code splitting, code inlining, and server-side rendering in PROXX. Sep 23, 2019 At Google I\/O 2019 Mariko, Jake, and I shipped PROXX, a modern\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Techniques to make a web app load fast, even on a feature phone\" \/>\n<meta property=\"og:description\" content=\"How we used code splitting, code inlining, and server-side rendering in PROXX. Sep 23, 2019 At Google I\/O 2019 Mariko, Jake, and I shipped PROXX, a modern\" \/>\n<meta property=\"og:url\" content=\"https:\/\/rmarketingdigital.com\/en\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/\" \/>\n<meta property=\"og:site_name\" content=\"R Marketing Digital\" \/>\n<meta property=\"article:published_time\" content=\"2022-08-04T05:47:54+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/rmarketingdigital.com\/wp-content\/uploads\/2020\/09\/hero-4373669.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"600\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"R Marketing Digital\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/de\\\/dev\\\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/de\\\/dev\\\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\\\/\"},\"author\":{\"name\":\"R Marketing Digital\",\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/#\\\/schema\\\/person\\\/7c0d9782b2977b840b3740f00c69a73e\"},\"headline\":\"Techniques to make a web app load fast, even on a feature phone\",\"datePublished\":\"2022-08-04T05:47:54+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/de\\\/dev\\\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\\\/\"},\"wordCount\":2831,\"image\":{\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/de\\\/dev\\\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/rmarketingdigital.com\\\/wp-content\\\/uploads\\\/2020\\\/09\\\/hero-4373669.jpg\",\"articleSection\":[\"Dev\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/de\\\/dev\\\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\\\/\",\"url\":\"https:\\\/\\\/rmarketingdigital.com\\\/de\\\/dev\\\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\\\/\",\"name\":\"Techniques to make a web app load fast, even on a feature phone - R Marketing Digital\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/de\\\/dev\\\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/de\\\/dev\\\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/rmarketingdigital.com\\\/wp-content\\\/uploads\\\/2020\\\/09\\\/hero-4373669.jpg\",\"datePublished\":\"2022-08-04T05:47:54+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/#\\\/schema\\\/person\\\/7c0d9782b2977b840b3740f00c69a73e\"},\"description\":\"How we used code splitting, code inlining, and server-side rendering in PROXX. Sep 23, 2019 At Google I\\\/O 2019 Mariko, Jake, and I shipped PROXX, a modern\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/rmarketingdigital.com\\\/de\\\/dev\\\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/de\\\/dev\\\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\\\/#primaryimage\",\"url\":\"https:\\\/\\\/rmarketingdigital.com\\\/wp-content\\\/uploads\\\/2020\\\/09\\\/hero-4373669.jpg\",\"contentUrl\":\"https:\\\/\\\/rmarketingdigital.com\\\/wp-content\\\/uploads\\\/2020\\\/09\\\/hero-4373669.jpg\",\"width\":1200,\"height\":600,\"caption\":\"hero-4373669-1498311-jpg\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/#website\",\"url\":\"https:\\\/\\\/rmarketingdigital.com\\\/\",\"name\":\"R Marketing Digital\",\"description\":\"Agencia SEO | Publicidad redes sociales | Community Management\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/rmarketingdigital.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/rmarketingdigital.com\\\/#\\\/schema\\\/person\\\/7c0d9782b2977b840b3740f00c69a73e\",\"name\":\"R Marketing Digital\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/c5599c203e0120b8ccbff4666af86f35f9b3dcad468a8bc6295313a3964a80be?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/c5599c203e0120b8ccbff4666af86f35f9b3dcad468a8bc6295313a3964a80be?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/c5599c203e0120b8ccbff4666af86f35f9b3dcad468a8bc6295313a3964a80be?s=96&d=mm&r=g\",\"caption\":\"R Marketing Digital\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Techniques to make a web app load fast, even on a feature phone - R Marketing Digital","description":"How we used code splitting, code inlining, and server-side rendering in PROXX. Sep 23, 2019 At Google I \/ O 2019 Mariko, Jake, and I shipped PROXX, a modern","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:\/\/rmarketingdigital.com\/en\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/","og_locale":"en_US","og_type":"article","og_title":"Techniques to make a web app load fast, even on a feature phone","og_description":"How we used code splitting, code inlining, and server-side rendering in PROXX. Sep 23, 2019 At Google I\/O 2019 Mariko, Jake, and I shipped PROXX, a modern","og_url":"https:\/\/rmarketingdigital.com\/en\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/","og_site_name":"R Marketing Digital","article_published_time":"2022-08-04T05:47:54+00:00","og_image":[{"width":1200,"height":600,"url":"https:\/\/rmarketingdigital.com\/wp-content\/uploads\/2020\/09\/hero-4373669.jpg","type":"image\/jpeg"}],"author":"R Marketing Digital","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/rmarketingdigital.com\/de\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/#article","isPartOf":{"@id":"https:\/\/rmarketingdigital.com\/de\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/"},"author":{"name":"R Marketing Digital","@id":"https:\/\/rmarketingdigital.com\/#\/schema\/person\/7c0d9782b2977b840b3740f00c69a73e"},"headline":"Techniques to make a web app load fast, even on a feature phone","datePublished":"2022-08-04T05:47:54+00:00","mainEntityOfPage":{"@id":"https:\/\/rmarketingdigital.com\/de\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/"},"wordCount":2831,"image":{"@id":"https:\/\/rmarketingdigital.com\/de\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/#primaryimage"},"thumbnailUrl":"https:\/\/rmarketingdigital.com\/wp-content\/uploads\/2020\/09\/hero-4373669.jpg","articleSection":["Dev"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/rmarketingdigital.com\/de\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/","url":"https:\/\/rmarketingdigital.com\/de\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/","name":"Techniques to make a web app load fast, even on a feature phone - R Marketing Digital","isPartOf":{"@id":"https:\/\/rmarketingdigital.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/rmarketingdigital.com\/de\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/#primaryimage"},"image":{"@id":"https:\/\/rmarketingdigital.com\/de\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/#primaryimage"},"thumbnailUrl":"https:\/\/rmarketingdigital.com\/wp-content\/uploads\/2020\/09\/hero-4373669.jpg","datePublished":"2022-08-04T05:47:54+00:00","author":{"@id":"https:\/\/rmarketingdigital.com\/#\/schema\/person\/7c0d9782b2977b840b3740f00c69a73e"},"description":"How we used code splitting, code inlining, and server-side rendering in PROXX. Sep 23, 2019 At Google I \/ O 2019 Mariko, Jake, and I shipped PROXX, a modern","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/rmarketingdigital.com\/de\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/rmarketingdigital.com\/de\/dev\/techniken-mit-denen-eine-web-app-schnell-und-sogar-auf-einem-feature-telefon-geladen-werden-kann\/#primaryimage","url":"https:\/\/rmarketingdigital.com\/wp-content\/uploads\/2020\/09\/hero-4373669.jpg","contentUrl":"https:\/\/rmarketingdigital.com\/wp-content\/uploads\/2020\/09\/hero-4373669.jpg","width":1200,"height":600,"caption":"hero-4373669-1498311-jpg"},{"@type":"WebSite","@id":"https:\/\/rmarketingdigital.com\/#website","url":"https:\/\/rmarketingdigital.com\/","name":"R Digital Marketing","description":"SEO Agency | Advertising social networks | Community Management","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/rmarketingdigital.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/rmarketingdigital.com\/#\/schema\/person\/7c0d9782b2977b840b3740f00c69a73e","name":"R Digital Marketing","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/c5599c203e0120b8ccbff4666af86f35f9b3dcad468a8bc6295313a3964a80be?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/c5599c203e0120b8ccbff4666af86f35f9b3dcad468a8bc6295313a3964a80be?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/c5599c203e0120b8ccbff4666af86f35f9b3dcad468a8bc6295313a3964a80be?s=96&d=mm&r=g","caption":"R Marketing Digital"}}]}},"_links":{"self":[{"href":"https:\/\/rmarketingdigital.com\/en\/wp-json\/wp\/v2\/posts\/83276","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rmarketingdigital.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rmarketingdigital.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rmarketingdigital.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rmarketingdigital.com\/en\/wp-json\/wp\/v2\/comments?post=83276"}],"version-history":[{"count":0,"href":"https:\/\/rmarketingdigital.com\/en\/wp-json\/wp\/v2\/posts\/83276\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rmarketingdigital.com\/en\/wp-json\/wp\/v2\/media\/83277"}],"wp:attachment":[{"href":"https:\/\/rmarketingdigital.com\/en\/wp-json\/wp\/v2\/media?parent=83276"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rmarketingdigital.com\/en\/wp-json\/wp\/v2\/categories?post=83276"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rmarketingdigital.com\/en\/wp-json\/wp\/v2\/tags?post=83276"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}