Frontend Performance

October 4, 2021 (3y ago)

Archive

Guide for the impatient

Before I start, I just want to say that you have to always ensure that there's something happening, even if you anticipate heavy client-side load. Display something interesting upon loading instead of leaving the page blank, otherwise, users might assume it's broken and leave, especially with the short attention span these days, if people don't see something interesting they're going to bounce.

Frontend Performance Metrics

Below are some metrics you should definitely know about, and optimize.

Largest Contentful Paint

LCP measures a webpage's loading performance by marking the point in the page load timeline when the largest content element (i.e. image or text block) becomes visible to the user.

First Contentful Paint

FCP measures the time from when the page starts loading to when any part of the page's content is rendered on the screen. It gives you an idea of how quickly users see any visual response from your webpage.

First Input Delay

FID measures the time from when a user first interacts with your site (like clicking a link or tapping a button) to when the browser can respond to that interaction. It basically indicates the responsiveness of your webpage to user input.

Time to First Byte

TTFB measures the time it takes for a browser to receive the first byte of data from a web server after requesting a webpage. It reflects the server's responsiveness.

Interaction to Next Paint

INP is a metric that measures the time between a user interaction (such as clicking a button) and the next visual change on the screen. It helps assess the responsiveness of a webpage to user actions.

Cumulative Layout Shift

CLSmeasures the visual stability of a webpage by quantifying how much the elements on the page move around during loading. It's concerned with the unexpected layout shifts that can disrupt the user experience.

To measure these metrics, you can use PageSpeed Insights.

Optimization

Here are a couple of optimization techniques you can leverage to enhance these metrics and more.

Purge JS and CSS Bundles

You're probably using a bundler (Vite, WebPack, Rollup, Parcel...) already for JS, so that's in check.

Remove unused CSS. PurgeCSS is good.

Third-party scripts can introduce performance bottlenecks, so evaluate the necessity of each script.

If you're using plain HTML, make sure that critical CSS and JS are loaded first to render meaningful content quickly. Inline critical CSS and defer non-critical scripts to avoid render-blocking resources

Images

Large images and videos can significantly impact page load times. So

  • - Use responsive images and formats like WebP and AVIF (for supported browsers).
  • - Optimize and compress, JPEG/PNG/SVG.
  • - Replace GIFs with looping HTML video or WebP.

Use these attributes for images that need it

<img decoding="async"  />

And

<img loading="lazy"  />

Lazy Loading

Where content is fetched only when it comes into view. Prioritize loading essential elements first, similar to how Netflix or YouTube optimizes their content loading. If you're using React, use React.lazy or <Suspense/> component with a fallback lightweight component.

Also use dynamic imports. If it's not going to be used right away, why import it. Here's an example of a dynamic import with React

1import { useState } from 'react'
2 
3const names = ['Tim', 'Joe', 'Bel', 'Lee']
4 
5export default function Page() {
6  const [results, setResults] = useState()
7 
8  return (
9    <div>
10      <input
11        type="text"
12        placeholder="Search"
13        onChange={async (e) => {
14          const { value } = e.currentTarget
15          // Dynamically load fuse.js
16          const Fuse = (await import('fuse.js')).default
17          const fuse = new Fuse(names)
18 
19          setResults(fuse.search(value))
20        }}
21      />
22      <pre>Results: {JSON.stringify(results, null, 2)}</pre>
23    </div>
24  )
25}

Caching and Memorization

Use a CDN for JS, CSS, Images and such, make sure they're as close to the users as possible.

Memoize computed results and avoid unnecessary re-renders. If you're using React, use React.memo or useMemo hook. API calls too, Next allows you to cache fetch resources with a time validation window. Users won't have to wait each time to request the same resource, that probably doesn't change often.

1  const res = await fetch('https://api.example.com', {
2    next: { revalidate: 3600 }, // Revalidate every hour

Debounce

Useful in cases like user input, where you want to wait for a brief pause in typing before taking action, such as filtering or validating data.

Pagination (The right way)

The more data you load at a time, the more your users have to wait. Only load the necessary data, as in, if heavy, load only what can be in the current viewport.

Use SSR (Server-Side Rendering) by Default

Minimize client load to render the page, as much as possible. You can guarantee the server that serves the content, but can't guarantee what device will the client connect from. Aside from the improved perceived performance for users, it also it improves SEO. Next uses this as default.

Don't Overengineer

Overengineering can lead to unnecessary complexity and performance overhead. So, consider the simplicity of your project's requirements. If a lightweight SPA suffices, go for plain HTML and JavaScript instead of heavier frameworks.

Optimize DNS TTL

When configuring DNS records: A short Time To Live (30s) is beneficial for frequently updated content, as it minimizes the time users wait to see changes. However, this may lead to slightly slower load times, due to more frequent DNS lookups. On the other hand, a long TTL (86400 seconds, or 24 hours) means records are cached for longer periods, which allows for faster site loading as DNS lookups are minimized.