{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"588b2b03-a726-5249-9d5a-975640153a62","frontmatter":{"category":"Architecture","title":"Evolution of SPA approach at AUTO1","date":"2019-04-11","summary":"Building single-page applications","thumbnail":null,"authorName":"Oleg Tudoran","authorDescription":"Software Engineer","authorAvatar":{"relativePath":"pages/spa-evolution/avatar.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAUCAQT/xAAXAQEBAQEAAAAAAAAAAAAAAAAAAgED/9oADAMBAAIQAxAAAAGtmN2aro5nj0dJA//EABwQAAIBBQEAAAAAAAAAAAAAAAABEQIDEBMhEv/aAAgBAQABBQLaK+p30kjfcPp6Z//EABcRAAMBAAAAAAAAAAAAAAAAAAAQERL/2gAIAQMBAT8Bhl//xAAXEQADAQAAAAAAAAAAAAAAAAAAEBES/9oACAECAQE/AaaUP//EABkQAAIDAQAAAAAAAAAAAAAAAAAhARAgMv/aAAgBAQAGPwIZ1GXX/8QAHRAAAgIBBQAAAAAAAAAAAAAAAREAIUEQMXGBkf/aAAgBAQABPyHgJTehCyAUh4MuNDE70MruqIpz/9oADAMBAAIAAwAAABAbNwD/xAAYEQEBAAMAAAAAAAAAAAAAAAABABFBcf/aAAgBAwEBPxACXVqy3//EABgRAQADAQAAAAAAAAAAAAAAAAABEUFR/9oACAECAQE/EJko1Tj/xAAaEAEBAQEBAQEAAAAAAAAAAAABEQAhgTHh/9oACAEBAAE/EL/XKmAfiXH40r0QHrolRSpPNF7z5hXq9cpZiC65CQeXf//Z","width":50,"height":50,"src":"/static/24ab1ea711b3329ac0f7f0d8386d0c6c/d2d31/avatar.jpg","srcSet":"/static/24ab1ea711b3329ac0f7f0d8386d0c6c/d2d31/avatar.jpg 1x,\n/static/24ab1ea711b3329ac0f7f0d8386d0c6c/0b804/avatar.jpg 1.5x,\n/static/24ab1ea711b3329ac0f7f0d8386d0c6c/753c3/avatar.jpg 2x,\n/static/24ab1ea711b3329ac0f7f0d8386d0c6c/31ca8/avatar.jpg 3x"}}},"headerImage":{"relativePath":"pages/spa-evolution/spa-winding-road-evolution.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACaklEQVQozw2OZ1PaAACG86faa+/as67YskfIImQnBDNJAiEMCUuECso6AZHW0dOrrVQ5279XvrxfnnvueYGQ3CQKA8Gb6Z3v7vDBmz415qvm5aq9WHWWz/2b197Na/vqpT5bVSaPhcGD3b+vz/70b/8dL9fA0fl1d/5zfLee3/+d3K17i1/10Y9MbUK5A7o04qtTo3ML26NtrvEWL74nSjt8M2EPtZMbsb4E+MJXodivnn3rzH87Z/eCt8CcEZofJnKjsHEeUHtIbsx6C/rokvGupNZ17vyxfPEiNa6CYgXYpgogX92cJ4tD4/TOnawq05cNzg+f0rUFXRwqJ7dH0+fmYl0aPWitqd4Ya14PzegBggHCQh7SmrjdTdlt1u2KlTO9NdNaF1Su6SczPoJLSAahFyBRjVKMD0VDWAKmUJonw9EDAOIyEU4P0iqI8Qc49wVjIIaP0WwoSQZwMpxM4QwXhyEYhZNEgqJgkUdlkRRIIhYOASRPc4cyRNF+BAslUVwgERZLHzKaIpIExhKYpUhGms1KnMKmGCyuCmRek1gSTuIQEE0EOIHQVKladqQMAyEggn1S+IQixiUupkuIqeBaOinzhCmzriXbskCnDmhmi2I+Ahwb1mTEyXLLSa/u2ji+pWT85Rxlm5BhBFs1tl3n81ZEVXatrP+0XRz3yq4TVZXPDLMHSBzGUgFD8Q1PiycNK5eNVYuYZfo0bU/X90ulgOcFSyW/ae7JGVCSdlTtg5Pfr5QjbiECaGnYVJKTvrcJDzrVbtPYaDT7huXeieJHw9g1s6CugZYFmsZmtx1nx3XBWjV+XCf+A7RwItURocOQAAAAAElFTkSuQmCC","width":1200,"height":630,"src":"/static/f11171542efa1c6ff84e967a0df8a206/6e93c/spa-winding-road-evolution.png","srcSet":"/static/f11171542efa1c6ff84e967a0df8a206/6e93c/spa-winding-road-evolution.png 1x"}}}},"html":"<p>Here, at <a href=\"https://auto1-group.com\">AUTO1 Group</a>, <strong>we are growing</strong>. Not only in terms of company size, number of employees, revenues, etc, but also in terms of database size, amount of servers, lines of code in applications, amount of accessible pages. For supporting this growing codebase we are adopting new approaches and new technologies. Different innovations were already introduced, but today I want to tell You about switch of a paradigm from MVC to SPA, and how implementation of SPA approach evolved.</p>\n<h2>MVC approach</h2>\n<p>We have our internal application, called <em>Admin</em>, which is heavily used by hundreds of colleagues in many countries. Historically, it was written as a monolith php application, based on Silex (and later Symfony) frameworks (some overview is present in <a href=\"https://auto1.tech/vigolante/\">this blog article</a>). <em>Admin</em> application had architectural layers, which fell into a concept of <em>Model-View-Controller</em> (<a href=\"https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller\">MVC</a>). The <em>View</em> part, which is in essence the HTML code of all pages, was prepared on web-server using TWIG templates. Usually, one developer was working on both back-end and front-end implementations of web-page. That was called <code class=\"language-text\">Full Stack development</code>.</p>\n<p>Downside of such approach became obvious when first HTML tables with complex internal structure were implemented. Lets take as an example a page that displays hundreds of cars, provides pagination, possibility to edit data, shows aggregated results, etc. Such page consists of static components (navigational menu, list of available locales, information about current user, html-template, that renders appearance of the page, etc) and dynamic components (information about cars). This kind of web-page is <code class=\"language-text\">hard to build for a single person</code>. Knowing that development cycle of such complex pages could be split between several software engineers, we divided responsibilities between back- and front-end developers. We decided to generate the <code class=\"language-text\">View</code> part of MVC architecture in client's browser instead of application's server.</p>\n<p>And this was exactly the case, that fitted well into SPA paradigm.</p>\n<h2>SPA paradigm</h2>\n<p><em>SPA</em> stands for <em>Single-Page Applications</em> - an architectural paradigm, that divides elements of the page into static and dynamic.  Static elements of the page (such as page layout, navigational menu, user information) should be loaded into client's browser only once per user session. The page layout rarely changes.  On the contrary, dynamic components of the page can be updated continuously.</p>\n<p>Regardless of the technologies involved for rendering a page in client's browser, main difference between SPA and non-SPA approaches is whether a browser <code class=\"language-text\">requests each time full page content</code> (including static content, such as layout, menus, etc), or it <code class=\"language-text\">requests only the important (dynamic) content</code>. This difference can be seen in a diagram below.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/99908d8f36bcb222572f33850246a4f6/c9722/TraditionalVsSPAPageLifecycle.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 111.53415453527435%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,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'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"Traditional vs SPA\"\n        title=\"Traditional Page Lifecycle vs SPA Lifecycle\"\n        src=\"/static/99908d8f36bcb222572f33850246a4f6/40fad/TraditionalVsSPAPageLifecycle.png\"\n        srcset=\"/static/99908d8f36bcb222572f33850246a4f6/707e9/TraditionalVsSPAPageLifecycle.png 148w,\n/static/99908d8f36bcb222572f33850246a4f6/649e0/TraditionalVsSPAPageLifecycle.png 295w,\n/static/99908d8f36bcb222572f33850246a4f6/40fad/TraditionalVsSPAPageLifecycle.png 590w,\n/static/99908d8f36bcb222572f33850246a4f6/b3fef/TraditionalVsSPAPageLifecycle.png 885w,\n/static/99908d8f36bcb222572f33850246a4f6/c9722/TraditionalVsSPAPageLifecycle.png 893w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>First picture represents traditional request where full page contents are passed from web-server to a browser on each request. Second picture illustrates SPA approach: full page content is fetched only once, afterwords only dynamic content is downloaded and full page reload does not happen.</p>\n<h2>SPA evolution in AUTO1</h2>\n<p>Starting from a monolith application, where HTML was rendered on a server-side, we gradually moved to our current state, where a separate application serves SPA for us. But let's go step by step.</p>\n<h3>From Monolith to php-rendered SPA</h3>\n<p>Adopting of new front-end technologies, such as JavaScript library <a href=\"https://reactjs.org/\">React</a>, allowed us to start building first SPA pages. React code is executed in the web-browser. Front-end (React) developers became responsible for all the visual representation of a web-page.</p>\n<p>For organic integration with new technology we updated existing workflow of generating HTML of the page. Specifically, we introduced several new endpoints, that provided essential information for rendering HTML page layout. Those endpoints were:</p>\n<ul>\n<li>GET /menu - returned JSON-encoded structure for rendering navigational menu</li>\n<li>GET /locales - returned JSON-encoded list of available language and country settings</li>\n<li>GET /user/{id} - returned JSON-encoded data about current user</li>\n</ul>\n<p>Front-end developers wrote <a href=\"https://reactjs.org/\">React</a> code, which could fetch data from aforementioned endpoints and render HTML5-compliant page layout.</p>\n<p>Now we needed a way to pass this React code from web-server to client's browser during initial page load!</p>\n<p>This was achieved by introducing of what we call SPA-controller: set of php application's code (written with <a href=\"https://symfony.com/\">Symfony</a> framework), which returned very basic standard html template that contained latest version of the React code. Being executed in the browser, this code rendered HTML contents of specific SPA page.</p>\n<p><code class=\"language-text\">Under the hood</code> of SPA controller we implemented such functionality:</p>\n<ul>\n<li>based on request URL, define exactly which SPA page layout was requested</li>\n<li>check if we already have this HTML-template cached</li>\n<li>\n<p>(if template was not cached, or TTL of the cache is over) do a request to CDN, that contains actual version of front-end assets</p>\n<ul>\n<li>put new HTML-template into cache</li>\n</ul>\n</li>\n<li>return HTML-template of SPA index page to a requesting browser</li>\n</ul>\n<p>The response contains lightweight HTML similar to this:</p>\n<div class=\"gatsby-highlight\" data-language=\"html\"><pre class=\"language-html\"><code class=\"language-html\"><span class=\"token doctype\"><span class=\"token punctuation\">&lt;!</span><span class=\"token doctype-tag\">doctype</span> <span class=\"token name\">html</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>html</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>head</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>meta</span> <span class=\"token attr-name\">charset</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>utf-8<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>meta</span> <span class=\"token attr-name\">http-equiv</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>x-ua-compatible<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">content</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>ie=edge<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>meta</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>viewport<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">content</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>width=device-width,initial-scale=1,shrink-to-fit=no<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>title</span><span class=\"token punctuation\">></span></span>SPA index<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>title</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>link</span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://cdn.xyz.de/1.2.3/main.css<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">rel</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>stylesheet<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>head</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>body</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>noscript</span><span class=\"token punctuation\">></span></span>You need to enable JavaScript to run this app.<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>noscript</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>app<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>script</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text/javascript<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://cdn.xyz.de/1.2.3/main.js<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token script\"></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>script</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>body</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>html</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>As You can see, SPA index page contains none of the data that should be shown on a page. This data will come as a result of subsequent requests.\nStages of building SPA page are illustrated in the diagram (notice 3 phases):</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/e5d2e836bb8bf89ae91ffab228f6e013/d9801/3phasesOfSPAPage.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 132.49566724436744%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,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'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"3 phases of SPA page load\"\n        title=\"3 phases of rendering SPA page\"\n        src=\"/static/e5d2e836bb8bf89ae91ffab228f6e013/40fad/3phasesOfSPAPage.png\"\n        srcset=\"/static/e5d2e836bb8bf89ae91ffab228f6e013/707e9/3phasesOfSPAPage.png 148w,\n/static/e5d2e836bb8bf89ae91ffab228f6e013/649e0/3phasesOfSPAPage.png 295w,\n/static/e5d2e836bb8bf89ae91ffab228f6e013/40fad/3phasesOfSPAPage.png 590w,\n/static/e5d2e836bb8bf89ae91ffab228f6e013/b3fef/3phasesOfSPAPage.png 885w,\n/static/e5d2e836bb8bf89ae91ffab228f6e013/d9801/3phasesOfSPAPage.png 1154w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>Page lifecycle consists of 3 phases:</p>\n<ul>\n<li>fetch SPA index and render empty HTML container</li>\n<li>fetch data about static page components and render a layout of the page</li>\n<li>fetch main content of the page and build it into existing layout</li>\n</ul>\n<p>This approach, regardless of its increased amount of requests, actually resulted in a faster page load times, mostly because caching of SPA index was introduced and because web-server did not render HTML code anymore (this responsibility was moved to the React application code, that was executed on the client-side in web-browser).</p>\n<p>Overtime we noticed drawbacks with this approach: actual SPA version was hardcoded in a config file in php code and full php application required re-deploy each time, when a new version of React code was introduced. Deployment of a big monolith application to production environment was slow, also release notes were meaningless and annoying. </p>\n<h3>Moving SPA index outside of monolith</h3>\n<p>Having understood that keeping a reference to a latest SPA version in the code of main application was causing real problems, we started thinking about another possibilities.</p>\n<p>The most promising was idea to create a standalone application, whose single responsibility would be to serve latest version of SPA index page to authorized users. Expected benefits were:</p>\n<ul>\n<li>decreased time of delivering information about new SPA version to a production environment</li>\n<li>decreasing time of serving SPA index page</li>\n<li>utilize web-browser's capabilities, such as <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching\">HTTP caching</a></li>\n</ul>\n<p>As a result, we implemented a <a href=\"https://golang.org/\">Golang</a> application, that did exactly the same, as it's predecessor-monolith, but outside of php environment. The current page lifecycle started to look like this:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/a4e563c6b73722bb48a1f8147604cacc/efe7c/SPAFromStandaloneApp.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 41.98606271777003%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAACGElEQVQoz3WQT0gUcRTH38zOzra6IB6yMEFiD1md6hKEh7x1CLx08BDYP4SKwKUy1PLQpZYOZYRZUMGiJWWHLNxKKCwiSFwUYmVLV2vTdWdmd2ZnZ5zZmdnfr/ezgoh68Pj+3nu/3+f33gPTNL2SrpOybVdsy6qgOhRtRVIvAFpWUgSmsHm3iIcNfgARasJVx4928PAv00slYlgWVXWdFjSNFopFlwFVrdj7570agGph486wv7E5XOsP1LMcXmuktNKG2op+0DDWWuAtdhXzPDriuuSh55Fhz3PHsZqQlCvKxGzd91m1CcMAROZrfd1zR/juZGd99Ns2Bvz0eaVHx6LnrvdAbbucgnEctR+DW5SSAdSbWB9C/ZiVB1+9i0fuTT7oX0xPNsPpmcN818wz/sz0qeD5xDEGTM1nDi3LdtowzClzzUoqeW0EXiAQIfQOIeQ2IQzsPsb4fSZ3qTN2Ys+Bsbb2l9N390IkuZ87l7jMn01EQ11THQy4sPj1pKbmbUmSMrqua6ZpTfwGkl9AgkBnlAFz0sW/9+2LmnVi75dd7WNOgMVzqYXW7Kr0OicpT/IFNS7L6nWIl8v0BgIGf469PvIj1A9yvo89GtCfixhyPFQF/eDbLgJsxXQA/mdvsM/7rmsPO449hB5zXOMp2+Gq1MPqo+klH1MhuKlJqN6yTwg1tIihhh0sF716jdPUHIdb4tinzH8AY3NsacwXjCQAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"SPA index served by standalone application\"\n        title=\"SPA page is served from standalone application\"\n        src=\"/static/a4e563c6b73722bb48a1f8147604cacc/40fad/SPAFromStandaloneApp.png\"\n        srcset=\"/static/a4e563c6b73722bb48a1f8147604cacc/707e9/SPAFromStandaloneApp.png 148w,\n/static/a4e563c6b73722bb48a1f8147604cacc/649e0/SPAFromStandaloneApp.png 295w,\n/static/a4e563c6b73722bb48a1f8147604cacc/40fad/SPAFromStandaloneApp.png 590w,\n/static/a4e563c6b73722bb48a1f8147604cacc/b3fef/SPAFromStandaloneApp.png 885w,\n/static/a4e563c6b73722bb48a1f8147604cacc/efe7c/SPAFromStandaloneApp.png 1148w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>Now, initial request to fetch SPA index was re-routed to a standalone application, that served the same HTML, as we have seen above.\nAll other phases of SPA page lifecycle did not change.</p>\n<h2>Current state of rendering SPA pages</h2>\n<p>Implementing of a separate application, that serves SPA index pages, brought us several significant improvements in a page load lifecycle:</p>\n<ul>\n<li>SPA index page is returned significantly faster - <em>80-90 milliseconds instead of 1200-1500</em> milliseconds in old world</li>\n<li>time to deploy to production environment decreased almost 10 times (and now it is <em>under 4 minutes</em>)</li>\n<li>moving of configuration parameter outside of monolith allowed our front-end engineers to be able to <em>update version of SPA index directly</em>, without help of back-end guys</li>\n<li>additional configuration improvements allowed us to <em>gradually release feature</em> (allow switching to a new way of fetching SPA index one page by one) without a risk of braking full application</li>\n<li>automated support of <em>multiple development environments</em> was introduced ot of the box</li>\n<li>fine tuning of HTTP caching parameters allowed us <em>to serve less requests</em>, because of browser's caching possibilities</li>\n<li>In the first hour of work with this application on production environment <em>75% of all requests were served by HTTP cache</em>, with most requests not even reaching application</li>\n<li>other teams have <em>successfully adopted technology</em> and were able to release application themselves</li>\n</ul>\n<p>This is pretty good, huh?</p>\n<p>Of course, there are challenges in current approach also. We are investigating the best way to deal with user locales in SPA index, we learn how to do proper caching and authenticating user, but this is a topic of another story!</p>\n<h2>AUTO1 Group</h2>\n<h2>#spa #monolith #mvc #engineering</h2>","fields":{"slug":"/spa-evolution/","tags":["auto1","SPA","monolith","MVC"]}}}]}},"pageContext":{"slug":"/tags/MVC","tag":"MVC","categories":["Architecture","Coding","DevOps","Engineering","ProjectManagement","QA","Social","TechRadar"]}}