DEV Community

Cover image for Frontend performance pattern
Thanh Minh
Thanh Minh

Posted on

Frontend performance pattern

Original post:

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

Code-Splitting - React

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
Enter fullscreen mode Exit fullscreen mode

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.

You might not need date-fns

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

 raw `redux` endraw  bundle size is 1.6kB when Gzip and supported tree-shaking

redux bundle size is 1.6kB when Gzip and supported tree-shaking

Minimizing bundle size - MUI

Debounce user input

Difficulty: Quite easy

When to apply: When we’re hooking user typing, scrolling event which some tasks

Spell: Search input ⇒ Debounce

Debounce Your Input Handlers | Web Fundamentals | Google Developers

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 ⇒ ✈️

Image description

<img src="" loading="lazy" alt="Nimbus" />
Enter fullscreen mode Exit fullscreen mode

Memorized function

Difficulty: Normal

When to apply: When your function drain lots of CPU and RAM

Spell: Cache the expensive task

How to use Memoize to cache JavaScript function results and speed up your code

In addition, you can use Web Worker to push those computations into background processes

const cachedResult = useMemo(() => {
    // CPU intensive task here
}, [dependencies]);
Enter fullscreen mode Exit fullscreen mode

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

React PWA with Workbox

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

React Virtual

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.

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

Cheat Code for a Lightning Fast Front End: Building an Optimistic UI

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 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

Top comments (0)