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
andAVIF
(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.