Frontend performance pattern

Why should you read this?

  • Common patterns use to optimize frontend performance
  • Boost your web app speed
  • Convince your boss and colleague

When should I care about performance?

First of all, let’s make an agreement that whenever we design a system, structure for an app, it means that we’re making trade-offs. We cut some parts to gain others in order to make the system fit with the problem. What if we want all? It’s impossible, because resources when kick off the project is always limited, however, the problem on the opposite can grow endlessly.
  • Centralize vs Decentralize
  • Monolithic vs Microservice
  • SSR vs CSR
  • OOP vs FP
  • SQL vs No-SQL
  • Language X vs Language Y
  • API vs GraphQL
  • Stream vs Batch
 
Then when should I care about performance? And which is the trade-off?
For me, a frontend app has to maintain 3 aspects
  • Functional - Your app must run with correct logic
  • Maintainable/Readable - If it run right, then it should be easy to maintain and add a new feature
  • Performance - it should be fast, delight the user journey
 
Functional is easy to understand, it is the only aspect that we can not make a trade-off. Then now, our application becomes a slider between clean code vs performance, it’s depended on each project and the problem to trade-off
🐣
You are a guy in the project, so you have the right to know which is more important? Do you want to launch many features at a fast pace or do you want the feature is running lighting fast?
I was really obsessing with performance and had a chance to see that many juniors got the same obsession as me. Once upon a time, I spend a day paralleling a script that validates the CSV file. The optimization looks good, reduce the time from 15 minutes to 4 minutes. Sadly, the script run only 1 time a week and in turn, it can only save about 40 minutes a month on a free server

Performance pattern

Here are some common patterns used to optimize performance. And because it is quite popular so it’s quite easy to apply for your project which our sacrifice much on Maintainenable/Readable
 

Split code/Lazyload

Difficulty: Easy
When to apply: As soon as the project start, we can start with a simple one like split code by pages/routes. After that, if you want to take this further, you can split the code by user interaction.
Spell: Only load what users need
How: It depends on your framework, so search Google with this formula: Framework + code splitting
Example on React
import React, { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); }
 

Prevent installing the duplicated lib

Difficulty: Quite Easy
When to apply: When you start thinking about installing a new library. Then we have 3 options:
  • If you use existing lib, pray it to fit with your problem
  • If you use new lib, change the legacy code, and pray it to fit with the legacy problem, testing for regression bugs
  • Use both libs ⇒ ONLY this is your last hope... like your PM is hanging a knife on your neck
In my current project, we have 3 libraries to handle date-time: momment, date-fnsdayjs . Which moment and date-fns is big bundle size.
Spell: Check packages.json before searching/installing new libraries
 

Choose the library that supported ES6 and tree shaking

Difficulty: Easy, but depends on community
When to apply: All the time. Bundle size and tree shaking support should be important points to consider.
Spell: The newer library the more chance it is better (But it does not guarantee that it is stable and correctly)
How: Check library on https://bundlephobia.com/
redux bundle size is 1.6kB when Gzip and supported tree-shaking
redux bundle size is 1.6kB when Gzip and supported tree-shaking

Debounce user input

Difficulty: Quite easy
When to apply: When we’re hooking user typing, scrolling event which some tasks
Spell: Search input ⇒ Debounce
 
In more advantage cases, we can use debounce for API response. A common case is to debounce the response for trading/order book on weak computers
 

Add loading=lazy for tag img, iframe

Difficulty: Easy
When to apply: Most of the time you see img tag, unless you are sure that the img is above the fold
Spell: Image + loading=lazy ⇒ ✈️
notion image
<img src="https://www.notion.so/image/https:%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Feed374b2-19d1-444a-b21a-ee686940bb30%2FScreen_Shot_2022-01-21_at_12.45.19_PM.png?table=block&id=a6b45670-a7e6-40bc-ba3d-db40424d6a91&cache=v2" loading="lazy" alt="Nimbus" />

Memorized function

Difficulty: Normal
When to apply: When your function drain lots of CPU and RAM
Spell: Cache the expensive task
 
In addition, you can use Web Worker to push those computations into background processes
const cachedResult = useMemo(() => { // CPU intensive task here }, [dependencies]);

Cache frontend assets using Service Worker

Difficulty: Normal, hard. It’s quite hard when starting but the result is worst it
When to apply: When you are working in a really big app, bundle size is huge likes complex Admin/CRM
Spell: Complex, big web app ⇒ Service Worker
Example in React
 
Trust me, after you have done this, users will only ever see the loading indicator for the beginning. After that, you can update the app in the background. I will go into detail about how I do it in another post.
 

Virtual list

Difficulty: Hard
When to apply: When you have a list containing lots of items. Users have to scroll a while to view all items
Spell: You have a table more than 100 items, you are building something like feed on Facebook, Twitter ⇒ Virtual list
I highly recommend this one. Supper power and lightweight. Forget outdated react-window, react-virutalize
When working with Virtual list, developers should know about the concept how it works, and also when the component is rerendered to take full power from it. If not, you’re shooting on your foot.
notion image

Break long-run functions into multiple short-run functions

Difficulty: Hard
When to apply: When you run the function, and your laptop hangs 🙃
Spell: Like Above
How: You break your long-run, CPU-bound function into multiple short-run functions with setTimeOut ,requestAnimationFrame. However, when breaking long-run functions into many small ones is not an easy task, sometimes your need to keep those functions running sequentially to make sure the function is always correct
 

Optimistic update

Difficulty: Easy, normal, hard
Easy when you apply for simple entity
Normal when those entities start conflicting with local and server and you need to solve the conflict
Hard when the logic is quite complex and you have also to deal with solving the conflict on local state and server state
For eg: The like button is easy, the comment is normal, and posting a status is a really hard case
 
When to apply: When the feature is quite simple. The success rate of the API is about 99.99%
Spell: Simple logic, 99.99% success ⇒ Optimistic update
 

Lazy polyfill/Dynamic polyfill

Difficulty: Normal, hard
When to apply: When you’re too tired, don’t have any other option to optimize
Spell: When you see that the polyfill bundle size is quite huge but users are all high-tech
How: Leading right now is Polyfill.io. However, it is quite hard because you need to know how to set up in both frontend and backend
 
Which is the pattern that you use to optimize performance? let me know, I’m excited to explore more
 

Loading Comments...

Follow me @thanhledev

Xin lỗi các bạn vì thời gian qua mình không dành thời gian viết nhiều. Dạo này mình khá bận cho dự án https://getnimbus.io. Check it out 🥳