Nowadays Internet connections are pretty fast (usually) so why worry if your app has 1 megabyte of JS? Most native mobile apps go into the hundreds of megabytes!
Given that, let's see how we can improve our website's performance metrics, with a few tips.
I know, shocking. We made apps like this for ages and it kinda worked out. Check out Basecamp, they're doing pretty well. Having worked with multiple SPA libraries and server-rendered full-stack frameworks, I can attest that most times, we duplicate a bunch of concepts on the client-side that could just exist as a full-stack unit: routing, data validations, app state, API calls (when full-stack, these just don't exist).
Working in a digital agency I often saw SPAs applied to very simple projects, where a classic Rails app would excel. Mea culpa, I’ve done that myself. A client-side approach is great when you want to have separate teams for backend and frontend. Having dedicated teams for both will help teams organize themselves. After all, all these SPA frameworks and libraries were made by very large organizations with very large codebases.
But even for smaller projects, SPAs excel on highly reactive applications, and any application that is trying to emulate a native experience, through animations and navigation controls, for example, will also benefit from these modern frontend technologies.
A final friendly reminder on this matter. Pick something that strikes a good balance between maintainability and your team's happiness. Don't switch over to a specific technology or way of doing things just because a random dude on the internet says so! Explore and see what works for you.
Use native libraries
Right now you probably are using something like
superagent on your web app, both sitting at 4.4kb and 6.4kb total size respectively, at the time of writing this blog post. I resort to
axios all the time due to old habits, but I've been replacing it with
fetch and life has been great.
Most problems pointed to
fetch are its lack of defaults and weird error handling (they only throw errors on network failure and not on bad responses), but that can be fixed with a simple custom wrapper. Check out this awesome blog post from Kent C. Dodds where he builds a custom wrapper to remove all problems regarding the lack of sane defaults.
If you want to keep around the axios API, you can always use redaxios. An axios compatible API that uses
fetch under the hood and it's just 800 bytes!
And if you need to support older browsers, use unfetch as a polyfill.
Try and use the browser's native functionality before going after solutions on npm, you will be surprised. Browsers can do awesome stuff nowadays and almost every single functionality can be polyfilled back into older browsers.
Be careful with 3rd party dependencies
Even though the browser is a great platform, it's pretty rare to complete a project without ever using a 3rd party dependency. Even if you maximize the natural power of the browser, there are essential libraries that you will probably need. One thing that happens though, is that people often search for a given library and don't really think about the consequences. Any library you use will increase the total size of your web app. We should be mindful of that.
For example, if you import
lodash the entire package will end up on your final bundle. However, you can use the alternative
lodash-es, which does the same thing and is tree-shakeable, and you only use the functions you import. As long as you do this:
Remember, try to find the right balance between "reinventing the wheel" or adding another dependency. And when you are looking for libraries to solve your problems, pick one that is small and tree-shakeable.
You can also resort to code-splitting and load polyfills conditionally. I'll show you how in a bit.
For example, if you have a React app with
I am not going in-depth on implementation details, but you can check the
react-router docs on the best way of doing this. The important thing to note is that we should only load code that the user needs or will almost surely need in the future.
Popular frameworks on top of their SPA libraries like Next.js (React), Nuxt (Vue.js), and Sapper (Svelte) do this out of the box via code splitting based on-page components. This is a cool way of going about this since you do need to manually implement this yourself.
You can even use this strategy to conditionally load dependencies. In the next example, we are importing some polyfills only if the browser does not support the given functionality natively.
Apply this to anything you need. You can use this to load different bundles for mobile or desktop. For different user roles, for example, regular users won't probably need to have the admin dashboard code loaded into their browsers.
Don't support older browsers
Dramatic statement. Nowadays you are probably using
babel to transpile your JS code for it to be compatible with older browsers. So every single new feature of the language is then ported back to be supported. If you have IE (Internet Explorer) as a target, then
babel will convert every arrow function into a regular function. Transpiled code is longer, heavier, and probably not as optimized as the code you have actually written.
How to solve this? Ditch older browsers. I mean, this might seem ridiculous and counter-intuitive at first but older browsers, mostly IE obviously, are insecure, slower, and just plain worse than the alternative. If a computer runs IE it probably can run either Chrome or Firefox. There are a few cases where this is not possible. Some institutions and companies just don't allow people to update or install applications on their computers, so they are stuck with Windows XP and IE.
polyfill.io is a possibility, it loads polyfills conditionally, based on your browser’s version.
Remember, you can always review your web app user base and check the percentage of users with older browsers. You can track your user's browser versions respectfully by using a privacy-focused tracker (a bit paradoxical) like Goatcounter. They just collect very basic information that cannot uniquely identify users, respecting their privacy. You will probably notice that you don't have IE users at all (this is the case for the products I've been working on at least).
For the global market share, IE has 3%, but it's a good idea to scan the market and see if it makes sense to be IE friendly. Imagine that your app is a specific tool for Linux people. They won't be using IE at all.
It's a matter of user research like all great products should have. An app for the enterprise financial market would probably need IE. Lot's of people on that field are stuck on Windows XP due to organizational restrictions. For a rad startup idea? Probably no IE users will pop up.
At the very least, make sure your landing page works on IE, then just tell people to upgrade :)
Also, make sure you test your web experiences on slower devices. I cannot say this enough. Not everyone has a Galaxy S20 or the latest shiny iPhone in their pockets.
It all boils down to the user experience. Make accessible, performant web apps that do just what they are supposed to do, well.