To choose a right tech stack for building an SPA (Single Page App), which gets the best possible score in lighthouse audit. Just telling in advance, we are looking for something simple and elegant, not for a pure JS solution or some rocket science.
There are multiple benefits:
Lighthouse audits sorts out its findings and proposals into four categories: Performance, Accessibility, Best Practices and SEO. All of them are important for ranking in google search, but some are more important than others. Getting the last three to 100% won’t be that hard. We just need to add all required metadata to a page and set up server correctly. Getting Performance to 100% is different story — we will focus on that later. Based on searchmetrics.com study Lighthouse Ranking Factors 2019, top ranked search results (when searching generic keywords like “shoes”) have something in common. They are best in: Performance, time to First Contentful Paint, following best Practices, size of DOM, they use webP images and run over https or better http/2.
SEO and Accessibility are also important for them, but correlation between its score and ranking in top 20 is not obvious, and rather misleading.
We as developers experience era of fancy frontend frameworks. Interactive functionality, which we could have dreamed about 10 years ago, is not only possible these days, it is often matter of minutes to implement. It has been a giant leap for developers, but rather small one for users. Our networks are 10 times faster; our computers are 10 times more powerful, our frameworks are 10 times more awesome, but it takes similar time to load average web page, as 10 years ago. How is that even possible?
You always wish to have extraordinary web, which is step ahead of your competition. Full of interactivity, animations, images and videos. Your product owner wants it as soon as possible. Your stake holders want all stats about visitors. Your FCO wants more ads to make more money, and so it goes on. It is a common pattern I have been observing over 13 years of my professional carrier as web developer ;) In the end, requirements in its MVP (Minimal Viable Product) are already so demanding and way beyond what users really need and want. Performance is simply not a priority for most of software teams. As Jeremy Wagner says in: Innovation Can’t Keep the Web Fast. A “hello world” app with all above mentioned requirements would already be 1 or 2 MB. Especially if you use something like React, or Angular. We can do better. We should start creating web for users again!
Let’s try to learn from big companies and their successful products. Facebook for instance; its SPA homepage (on desktop) is loaded in about 10s, has 9.8MB and it is all done in 350requests. There are some advanced techniques used, but it is not point of this article. I can just say it performs better than it looks. To be honest, its visible content is loaded in 4s; it is not bad, considered the app complexity. But do we need all that complexity on initial load? I would say no. Just check facebook.com lighthouse ratings; it is a disgrace.
Maybe it is not that great idea to have one of the most loaded pages on the internet as a model. We will try to achieve 4x100% with far less complex app. You always need to think twice, whether an app really needs to be so huge and complex. Especially when you can lazy load most of the stuff.
I am sorry, but very probably your favourite framework won’t do it. To choose one which will, we need to understand all restrictions and requirements.
First of all, we know, we need a JS code because we want to build SPA. We cannot do that with server rendered pages, without JS.
We need either vanilla JS or a lightweight framework. I can tell you straight away, that if we aim for 4x100% on slower mobile devices, we cannot use any of the holy trinity of JS frameworks (React, Angular, Vue).
Just for illustration, a React hello world app itself (one dummy screen with no routing and data management) won’t pass the audit with 100% Performance. Not even Next.js (server prerendered and optimised React app) can reach the desired Lighthouse Performance score, as it still contains a minified build of React and only gets us to 96% for Performance.
Inferno, Preact and some other clones of react would be able to reach the score, but we won’t use them. They wouldn’t give us the luxury of full featured framework and we would end up with writing a lot of pure JS code along those libraries.
Fortunately there is an unspoken demand for what we are attempting here. There are frameworks, popping up right now, trying to please both, users and developers. They offer rich features and minimal trace at the same time. For our purpose we need two things from such a framework:
1) to support easy lazy-loading of almost anything and
2) minimal or no size on its own.
Probably the most endorsed “framework” of this type is Svelte. With Sapper, its pre-rendering counterpart, they fit to our requirements. So let’s focus on them.
It will be good to know, what are the limits of compiler compared to regular framework, The fundamental difference is in a way how its code is run in a browser. With regular FE framework you are able to load such a framework by
<script src=”framework.js”> tag and then just write your code inside another
<script> tag. None of the modern frameworks encourage developers to follow this pattern, and with most of them you would have hard time to get it running this way. But you can, it gives you power to dynamically create components and inject them during runtime to your app. It can be “must have” in some apps, but — let me exaggerate a bit — in 99.9% of them you don’t need it. For those 99.9% you can use a compiler.
Svelte is a compiler, which accepts code written in similar manner as in React, Angular or Vue (component centred architecture), but is compiled to direct DOM manipulation instructions. If you don’t use a feature of Svelte, it won’t be output to a production bundle. If your page is just 10KB of HTML and CSS, then the svelte generated page will have about 10KB. If you use one two-way binding in that page, you will get maybe 0.1KB extra. Definitely there won’t be a 100KB framework handling your one two-way binding ;) With Svelte you can write high level declarative code and you will end up with highly optimised native JS code. With svelte we know, we start with minimal burden.
So we chose the tech, but there are still decisions to do. Should we just bundle an app to big JS file, prerender it on server, or serve it as static assets? Best option seems to be be the last one. It has several advantages: 1) Code is split to smaller chunks. 2) Content loaded for first meaningful paint is served from a static HTML file which can be easily served over CDN.
Once static page is loaded, we can fetch JS and add some dynamic functionality to it. We can even do some api requests and customise the page for a user. This approach is called Jamstack, it is successor of statically generated pages. Jamstack is bringing API and more custom content to static generators. Jamstack stands for JS, Api, Markup.
Fortunately for us Sapper does support static generation of pages. It also provide basic setup of service worker for subsequent loads of the app. It comes with some minor bells and whistles supporting prefetching of pages before you hit them, basic in app routing. It all in cost of 13KB (before G-zipping).
FE is just one side of the puzzle. We will also need reliable CDN server, fast API (cloud - optimally - distributed database). User Authentication and authorization of requests. These BE and middleware related topics are not main focus of this article, but we will touch them, because…
In Part 2 of this serie, we will try to prove our tech stack in a demo app. We are gonna build and deploy hello world SPA… In Part 3 we will turn our dummy app into real weather forecast SPA.
See you next time;)