owned this note
owned this note
Published
Linked with GitHub
# Introduction
Web performance describes how fast or slow a web app is. It is crucial to application development. An highly optimised web app is fast, appeals to users' experience while leading to improved conversion rate for a product and high search engine ranking. In this article, you will understand how the **Core Web Vitals (CWV)** metrics are affected by the resources used in your web app, you will implement best practices for improving the performance of your NextJs application, and understand how to measure the performance of your optimised web app.
# Overview of User Experience
Users determine the success rate of a product, their experience while navigating or interacting with a product's web app can indirectly affect the product's revenue. It is important to build web pages that load faster when requested by users. Excluding the **speed** and **user interactivity** of a web app, it is also important that contents displayed on the user interface of a web page maintain **visual stability**, that these contents do not always change their position, shifting from one space to the other.
# Factors Affecting Web Performance
There are certain resources that contribute negatively to web app performance if they are not used effectively. Let us take a look at each of them.
## Multimedia File Size
Image and video files take up much space in a web browser. When these files are in high quality, they become way too large in size, resulting in slower load time and a shift in the position of other contents in the web page. The shift is caused by the browser, after the files have been rendered because the browser could not calculate the appropiate width and height of these files. This shift is known as **Cumulative Layout Shift (CLS)**, a CWV metric which determines if the surrounding contents of an image (or other elements) move to another position after the image (or element) has loaded. In certain cases, multimedia files represent the main contents of a web page. When these files load slowly, they affect the **Largest Contentful Paint (LCP)** of the web page. LCP determines how fast a visually important web content is rendered.
## Network Requests
Remote resources such as libraries, scripts, packages, APIs, that are requested from network servers are considered to be resource-blocking. This affects the INP score of a web app. **INP (Interaction to Next Paint)** is also a CWV metric. It signifies the time it takes for web contents to be completely rendered after user interaction during request-time.
## Memory Usage
Large scale applications require larger resources that affect the performance of a web app. To achieve optimised memory usage during build time, enable lazy loading, minimize the size of resources used, eliminate redundant code, enable caching, and analyze memory issues with tools such as Chrome DevTools.
## URL Redirect
URL redirect simply means visiting a web page from another web page. These web pages have URL patterns which differ from one another. When these patterns do not match, an error occurs in the browser, leading users to view an unexpected web page. For example, it is useful when a web page has updated features, or after form submission. When URL redirect is not implemented effectively, it can lead to slow load time and low search engine ranking of the web page.
# Optimisation Techniques in NextJs
Let us implement the best practices for optimising the resources metioned above.
## Display images with built-in `Image` component
NextJs has built-in `Image` component with props that make it easier to control how we want to render an image. Here is an explanation on the purpose of each prop:
### Required Props:
* **`src`** (required): The source of an image. The image can be stored locally in the repository or remotely on a network server.
* **`alt`** (required): The `alt` property provides textual information as an alternative to an image. It can be read aloud by a screen-reader assistive technology, contributing to an accessible web app. The `alt` can be set to an empty value if the image has no significant information it adds to the user interface.
* **`width`** (required for remote images): This determines how broad an image appears.
* **`height`** (required for remote images): This determines the length of an image.
### Non-required props:
* **`priority`**: When an `Image` has a `priority` prop, the browser will pre-load the image before it is displayed, making the image load faster. This improves the LCP score of a web app.
* **`fill`**: The `fill` property indicates that an image's width and height is determined by the parent element.
* **`sizes`**: This specifies the width and height of an image at different breakpoints of a user's device.
* **`loader`**: A function that returns a url string for an image. It accepts `src`, `width` and `quality` as parameters.
* **`placeholder`**: A `placeholder` fills the blank space of an image before it is rendered completely.
* **`loading`**: Accepts a value of `{lazy}` to specify lazy-loading.
* **`style`**: Enhances an image's visual appeal. Does not include other accepted `Image` props as its property.
* **`onLoad`, `onError`**: Event handlers.
Here, let us control how we want to render different images.
### Rendering local images
```
// flowerPage.jsx
import Image from 'next/image'
import roseFlower from '../public/roseImage.png'
export default function FlowerImage() {
return(
<main>
<div style={{width:"600px", height:"600px"}}>
<Image
src={roseFlower}
alt=""
style={{objectFit: "contain"}}
fill
priority
/>
</div>
</main>
)
}
```
View:
![flowerpage](https://hackmd.io/_uploads/S1F6voSTA.png)
In the `flowerPage.jsx` file above, we did not specify the `height` and `width` inside the `Image` component since `roseFlower` is a local image. NextJs automatically calculates the `width` and `height` of a locally stored image. The `roseFlower` is prioritised during load time with the `priority` prop.
However, the `width` and `height` of the `roseFlower` are both determined by its parent element using the `fill` prop.
### Rendering remote images
It is required to specify the `width` and `height` of an image that is rendered from an external source. This gives us more control over the space that will be occupied by the image before it is rendered.
```
// monalisaPage.jsx
import Image from 'next/image'
export default function MonalisaImage() {
return(
<Image
src="https://s3.amazonaws.com/my-bucket/monalisa.webp"
alt=""
height={600}
width={600}
/>
)
}
```
Images rendered from external sources can affect the security of a web app. To ensure that a web app renders images from specified URLs only, we have to include `remotePatterns` in our `next.config.js` file. `remotePatterns` accepts an object of `protocol`, `hostname`, `port`, and `pathname`
Let us configure our `next.config.js` file to render images from `https://s3.amazonaws.com/my-bucket/**` URL paths only, with different possible number of path segments or subdomains at the end:
```
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 's3.amazonaws.com',
port: '',
pathname: '/my-bucket/**'
},
],
},
}
```
To specify path segments or subdomains at the beginning of the URL, place `**` at the start of the URL paths. For example, `hostname: '**.amazonaws.com'` means that `s3` can be replaced with another subdomain or path segment.
To allow a single path segment or subdomain, use `*` instead of `**`.
### Rendering images using `loader`
`loader` is a function that accepts `src`, `width` and `quality` as parameters while generating dynamic URLs for an image. It works in client components only. You can use `loader` for local images or remote images. Here is an example of how to use `loader`:
```
// artsCollectionPage.jsx
'use client'
import Image from 'next/image'
const artsImageLoader = ({src, width, quality}) => {
return `https://example.com/${src}?w=${width}q=${quality}`
}
export default function ArtsCollectionImage() {
return(
<Image
loader={artsImageLoader}
src='../public/roseImage.png'
alt=""
width={600}
height={600}
quality={80}
/>
)
}
```
### Adding `placeholder` to images
Placeholder solves slow network connectivity issues.
Here in the `artsGalleryPage.jsx` file, the image loads with a blurry effect before it is fully rendered:
```
// artsGalleryPage.jsx
import Image from 'next/image'
import picasso from '../public/picasso.jpg'
export default function ArtsGallery() {
return(
<Image
src={picasso}
alt=""
placeholder='blur'
loading='lazy'
/>
)
}
```
![picasso](https://hackmd.io/_uploads/SySnCgwTC.gif)
`loading='lazy'` enables delayed image rendering. Lazy loading images is useful for images that are not the LCP for a web page.
The `placeholder` can equally be set to `'empty'` or `'data:image/..'`.
#### Using separate images as `placeholder`
When the `placeholder` is set to `data:image/...` , the `src` image loads after the `placeholder` image which is an image data type with a uri converted to `base64`. It is useful when the `placeholder` image has dominant colours that blend with the `src` image. It keeps users to the web page without feeling the need to wait while the `src` image loads.
```
placeholder='data:image/<jpeg|png|...>;base64,<data-uri>'
```
For example:
```
// artsGalleryPage.jsx
import Image from 'next/image'
import picasso from '../public/picassoImage.jpeg'
export default function ArtsGallery() {
return(
<Image
src={picasso}
alt=""
placeholder='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQIAJQAlAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAH0AfQDAREAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAIF/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AobEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/9k='
loading='lazy'
/>
)
}
```
![greenimg](https://hackmd.io/_uploads/S1EpgMwpC.gif)
#### Set the separate image `placeholder` to have blurry effect
To blur a placeholder with an image data type, use `blurDataURL` prop together with the `placeholder` prop.
```
placeholder='blur'
blurDataURL='data:image/jpeg;base64,<data-uri>'
```
For example:
```
// artsGalleryPage.jsx
import Image from 'next/image'
import picasso from '../public/picassoImage.jpeg'
export default function ArtsGallery() {
return(
<Image
src={picasso}
alt=""
placeholder='blur'
blurDataURL='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQIAJQAlAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAH0AfQDAREAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAIF/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AobEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/9k='
loading='lazy'
/>
)
}
```
![blurgreenimg](https://hackmd.io/_uploads/Bk9kbGPpR.gif)
**Alternatively, you can automatically generate placeholders using [plaiceholder](https://www.npmjs.com/package/plaiceholder),**
### Display an image across different viewports using `sizes`
In the example below, the `lilacImage` renders at different sizes based on the user's screen size using media queries.
```
// lilacPage.jsx
export default function lilacImage() {
return(
<Image
src='../public/lilacImage.webp'
alt=""
sizes= "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
)
}
```
## Enhance video players with `<video>` and `<iframe/>`
You can render video players in two different ways by using `<video>` HTML tag for locally stored videos or alternatively, use`<iframe/>` HTML tag for remote videos requested from network servers.
### Rendering local videos with `<video>`
`<video>` accepts the `width` and `height` properties to specify the space occupied by the video player. To indicate the source of the video file, use the `src` attribute inside the `<source />` tag.
The `control` attribute inside the `<video>` tag enables keyboard navigation and screen reader accessibility features.
The `<track />` tag helps to provide alternative information such as `captions`, `subtitles`, `descriptions`, `chapters`, or `metadata` for a video player, these are specified with the `kind` attribute. To specify the source of the track file, use the `src` attribute.
The textual information within the `<video>...</video>` tag is a fallback content. It keeps the users engaged to the web page.
```
// flowerMakingVideo.jsx
export default function FlowerMakingVideo() {
return(
<video width="600" height="500" controls preload="none">
<source src="../flowerMakingVideo.mp4" type="video/mp4" />
<track
src="../flowerMakingCaptions.vtt"
kind="subtitles"
srcLang="en"
label="English"
/>
This video is not supported by your browser
</video>
)
}
```
### Rendering remote videos with `<iframe/>` and React `Suspense`
In NextJs, remote videos are first generated on the server. To render remote video players, use the `<iframe />` HTML tag.
In the `artsMakingVideo.jsx` file below, the video player is rendered with no border. The `title` attribute enables the screen reader to associate the video player with the information it provides.
```
// artsMakingVideo.jsx
export default function ArtsMakingVideo() {
return(
<iframe
width="600"
height="600"
src="https://www.youtube.com/..."
frameborder="0"
loading="lazy"
title="art making video"
allowfullscreen
/>
)
}
```
When a video loads, it is not interactive until the JavaScript for the file is equally loaded or fetched. This process is known as **Hydration**. It leads to slow response time when users need to interact with the video, causing the web app to have a poor INP score. React `Suspense` solves the hydration problem by providing fallback contents for videos, leading to improved user experience.
Let us better understand how to use`Suspense`.
`Suspense` is a React component. It enables you to load an alternative layout that is replaced by the video with the `fallback` prop before the video is rendered completely.
Let us create a fallback component:
```
// artsVideoFallback.jsx
export default function ArtsVideoFallback() {
return(
<div>
<p>Loading Arts Video</p>
<p>Rather, just view me</p>
</div>
)
}
```
Wrap the `<iframe/>` tag inside the React `<Suspense>...</Suspense>` component
```
// artsMakingVideo.jsx
import { Suspense } from 'react'
import ArtsVideoFallback from 'artsVideoFallback.jsx'
export default function ArtsMakingVideo() {
return(
<Suspense fallback={ArtsVideoFallback}>
<iframe
// ...
/>
</Suspense>
)
}
```
## Eliminate external Fonts download
Fonts loaded from network servers take up much time before they are rendered. NextJs self-hosts Google fonts and local fonts without the need to render the fonts from external sources.
Nextjs fonts are functions called with an object of different properties:
* **`src`** (required in local fonts): The path where the local font file is stored.
* **`declarations`** (local fonts only): Describes generated font face.
* **`subsets`** (google fonts only): An array of strings. It is useful for preloaded font's subsets.
* **`axes`** (google fonts only): Specifies the axes of variable fonts.
* **`weight`**: Represents `font-weight`.
* **`style`**: Represents `font-style`. Can be set to `italic`, `oblique` or `normal`.
* **`display`**: Possible string value of `auto`, `block`, `swap`, `fallback` or `optional`.
* **`preload`**: Specifies whether a font will preload or not. Sets to `true` or `false`.
* **`fallback`**: An array of strings. Replaces the imported fonts when loading error occurs. To style a `fallback`, use a CSS class selector for the element it is applied to.
* **`adjustFontFallback`**: Reduces the effect of font fallback on Cumulative Layout Shift (CLS). Sets to `true` or `false`.
* **`variable`**: A string value of declared CSS variable name.
### Using local Fonts
Local fonts are downloaded fonts. You can save local font files in a `./styles/fonts/` folder, placed inside the project's root directory.
To use local fonts, import `localFont` from `next/font/local`:
```
// app/layout.js
import localFont from 'next/font/local'
const myFont = localFont({
src: './fonts/my-font.woff2',
style: 'italic',
display: 'swap',
fallback: ['arial'],
})
export default function RootLayout({ children }) {
return(
<html lang="en" className={myFont.className} >
<body>{children}</body>
</html>
)
}
```
### Using Google Fonts
Google fonts are classified into different types. When using a non-variable font, it is important to specify its `weight`.
To use Google fonts, import the font type from `next/font/google`:
```
// app/layout.js
import { Roboto } from next/font/google
const roboto = Roboto({
weight: '400',
subsets: ['latin'],
style: ['normal', 'italic'],
display: 'swap',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={roboto.className}>
<body>{children}</body>
</html>
)
}
```
### Rendering and Reusing Multiple Fonts
To use multiple fonts in a reusable manner, call the fonts in a single fonts file as an `export const`:
```
// app/fonts.js
import localFont from 'next/font/local'
import { Roboto_Mono } from 'next/font/google'
export const myLocalFont = localFont({
src: "my-local-font.tff",
subset: ['latin'],
})
export const roboto_mono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
})
```
Next, render the font in the file you want to apply it to.
In the example below, the font is only rendered in the `artsCollectionPage.jsx` file:
```
// artsCollectionPage.jsx
import { myLocalFont } from '../fonts.js'
export default function ArtsCollection() {
return(
<div className={myLocalFont.className}>
Available Arts
</div>
)
}
```
### Using CSS variables to render fonts
In the example below, to apply font to a specific text using CSS variable, set the `className` of the text's parent element to the font's `variable`:
```
// aboutArtsPage.jsx
import styles from './aboutArts.module.css'
import { Inter } from 'next/font/google'
const inter = Inter({
variable: '--font-inter',
})
export default function AboutArts() {
return(
<div className=`${inter.className}`>
<h1 className=`${styles.text}`>Arts help to improve the memory</h1>
</div>
)
}
```
Next, style the text in the `aboutArts.module.css` file:
```
// aboutArts.module.css
.text {
font-family: var(--font-inter)
}
```
### Using TailwindCSS to render Fonts
Here, we have the `inter` and `roboto_mono` fonts called with the `variable:` `--font-inter` and `--font-roboto-mono` respectively in the `aboutArtistPage.jsx` file.
```
// aboutArtistPage.jsx
import { Inter, Roboto_mono } from 'next/font/google'
const inter = Inter({
variable: '--font-inter'
})
const roboto_mono = Roboto_mono({
variable: '--font-roboto-mono'
})
export default function AboutArtist() {
return (
<section className=`${inter.variable} ${roboto_mono.variable}`>
<h1>About Artist</h1>
</section>
)
}
```
To apply the css variable fonts using Tailwind css, add the css variable to your `tailwind.config.js` file:
```
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'pages/**/*.{js,ts,
jsx,tsx}',
'components/**/*.{js,ts,jsx,tsx}',
'app/**/*.{js,ts,jsx,tsx}/'
],
theme: {
extend: {
font-family: {
sans: ['var(--font-inter)'],
mono: ['var(--font-roboto-mono)'],
},
},
},
plugins: [],
}
```
### Aplying fonts with `style`
You can equally apply fonts to elements using `style`:
```
<h1 style={inter.className}>Well done</h1>
```
## Amplify Search Engine ranking with `metadata`
Metadata provides additional information about the data in a web app. These data include documents files, images, audios, videos, and web pages. When a web app has enriched metadata information, it takes high precedence and relevance over other web apps on search engines.
In NextJs, metadata is classified as either static or dynamic. Dynamic metadata provide information that are bound to change such as current route parameter, external data or `metadata` in parent segments.
You can add metadata either through configuration or special files:
### Adding metadata through configuration
For static metadata,You can export it using NextJs built-in `metadata` object, while you can export dynamically generated metadata with changing values using the built-in `generateMetadata()` function. Both `metdata` object and `generateMetadata()` function are exported in either `layout.js` or `page.js` files and can only be used for server components.
#### Adding static metadata with `metadata` object
```
// page.jsx
export const metadata = {
title: "....",
description: "....",
}
export default function Page(){}
```
#### Adding dynamic metadata with `generateMetadata()` function
`generateMetadata()` accepts `props` object and `parent` as parameters.
`props` object includes `params` and `searchParams`:
**`params`**: Contains the dynamic route parameters from the root segment to the segment where `generateMetadata()` is called.
**`searchParams`**: Contains the search params of the current URL.
The **`parent`** parameter is the resolved metadata's promise from the parent route segments.
```
// artist/[id]/page.jsx
export async function generateMetadata({params, searchParams}, parent) {
const id = params.id
const artist = await fetch(`https://../${id}`).then((res)=>res.json())
const price = await parent
return {
title: artist.title,
description: artist.description,
},
}
export default function ArtsPage({params, searchParams}){}
```
## Loading scripts with `Script` component
NextJs has built-in `Script` component that enables us to control how to render scripts intended for either a specific folder layout or the app root layout. For optimisation purpose, render the scripts in the specific folders' layouts where they are needed.
Scripts can equally be loaded in `Page` files.
Specifying the `id` prop in `Script` is useful for optimisation purpose.
### Loading inline Scripts
Inline scripts are written directly in the `Script` component. The `id` prop is required in inline scripts. Inline scripts can be written in two ways:
**With curly braces:**
```
// app/artsCollection/layout.jsx
import Script from 'next/script'
export default function Layout({children}) {
return (
<div>
<h1>Available Arts</h1>
<section>{children}</section>
<Script id="show-arts-collection">
{
document.getElementbyId=('arts-collection').classList.remove('hidden')
}
</Script>
</div>
)
}
```
**With `dangerouslyStyleInnerHTML` prop:**
```
// app/artsCollection/layout.jsx
import Script from 'next/script'
// ...
<Script
id="show-arts-collection"
dangerouslySetInnerHTML={{
__html: "document.getElementById('arts-collection').classList.remove('hidden')",
}}
/>
// ...
```
### Loading External Scripts
External scripts are loaded with a required `src` prop to specify the URL.
```
// ...
<Script src="https://example.com"
/>
// ...
```
### Specifying how scripts load using `strategy` prop
Although the `Script` is loaded only once in the web app, but you can control how it loads with the following loading strategies:
* `beforeInteractive`: The script will load before the NextJs code loads and before page hydration happens.
* `afterInteractive`: The script will load immediately after page hydration happens.
* `lazyOnLoad`: The script wll load in a delayed manner after every other code have been loaded in the browser.
*`worker`: The script will load in a web worker.
Here, render the script before page hdration occurs:
```
<Script
src="https://example.com/script.js"
strategy="beforeInteractive"
/>
```
### Adding Event Handlers to scripts
To control how a web page responds to certain events, you can use the following event handlers which can only be used in client components:
* **`onLoad`**: Responds inmediately the script has finished loading.
* **`onReady`**: Responds after the script has finished loading and the component is fully displayed.
* **`onError`**: Responds when an error occurs with script loading.
#### Using Event Handlers
```
'use client'
import Script from 'next/script'
// ...
<Script
src="..."
onReady=()=> {console.log('users can interact with the component now')}
/>
// ...
```
## Control routing with URL Redirect
Implement URL redirect with NextJs built-in functions and hooks based on different use cases.
### Redirecting users after mutation or events
Mutation involves updating data to a network server. Use `redirect` or `permanentRedirect` to enable URL redirect in server components, server actions or route handlers.
#### using `redirect` function
`redirect` is called outside the `try/catch` block.
`redirect` accepts two parameters:
* **`path`**: The URL path users are redirected to. The path can be relative or absolute.
* **`type`**: The type of URL redirect we want to enable, either to replace a URL pattern with `replace`, or to push to an already existing URL pattern with `push`. NextJs automatically uses the `push` redirect type in server actions only. Redirects that called in other files are `replace` by default.
```
// bidArtsAction.js
'user server'
import { redirect } from 'next/navigation'
import { revalidatePath} from 'next/cache'
export default async function bidArts(id) {
try{
// ...
} catch(error){
// ...
}
revalidatePath('/bidArts')
redirect(`/bidArts/${id}`)
}
```
As shown in the example above, `revalidatePath` will update cached `bidArts` page, then the URL pattern will change from `../bidArts` to `../bidArts/[id]` once the user `bidArts` server action is called.
If you want to replace the URL path, use:
```
redirect(`/bidArts/${id}`, replace)
```
#### using `permanentRedirect` function
To redirect users to a permanently changed URL, use `permanentRedirect`:
```
// updateArtistPaintingAction.js
'use server'
import { permanentRedirect} from 'next/navigate'
import revalidateTag from 'next/cache'
export async function updateArtistPainting(artsWork, formData){
try{
// ...
} catch(error) {
// ...
}
revalidateTag('artsWork')
permanentRedirect(`artist/${artsWork}`)
}
```
`revalidateTag` will update all paths related to `artsWork`.
### Redirecting users within client components with `useRouter` hooks
`useRouter` hook works in client components only.
```
// modernArts.jsx
'use client'
import useRouter from 'next/navigation'
export default function Page(){
const router = useRouter()
return(
<button onClick=(()=>{router.push(/fernado)})>
Fernado
</button>
)
}
```
Here, once a user clicks on the button, the user is redirected to a new URL with the path `/fernado`
### Redirecting users with `redirects` in `next.config.js`
To enable URL redirect to different URL paths based on different incoming URL requests, include `redirects` to the `next.config.js` file. It allows you manage different number of URL paths at once.
```
// next.config.js
module.exports = {
async redirects() {
return [
{
source: '/about',
destination: '/',
permanent: true,
},
{
source: '/artists/:slug',
destination: '/artsCollection/:slug',
permanent: true,
},
],
},
}
```
### Redirecting users based on certain conditions using `NextResponse.redirect` in middleware files
Enabling URL redirects in Middleware allows the web browser to begin URL redirect before a user's request is completed. For example, before a browser completes an authentication process.
Here, we want to redirect a user to the login page if the user is not authenticated. The redirect is processed before the login page is rendered. This will avoid errors when the page loads since we are redirecting the user after a condition has been met.
```
// middleware.js
import { NextResponse} from 'next/server'
import 'authenticate' from 'next/auth-provider'
export function middleware(request) {
const isAuthenticated = authenticate(request)
if(!isAuthenticated) {
NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/artscollection/:path*'
}
```
## Bundling and Analyzing packages with `@next/bundle-analyzer`
To minimize memory usage, you can bundle the packages used in your web app with a bundler. A bundler automatically merges all the code written in the web app as a single file. This helps to solve dependency and latency issues. Certain resources depend on other resources such as remote libraries, components, and frameworks and are complex to manage. Latency is a measure of distance in time between a user's device and the network server that is being requested.
NextJs has a built-in plugin `@next/bundle-analyzer` which identifies and reports dependencies issues.
### Installing `@next/bundle-analyzer` plugin
To install the the plugin, use:
```
npm i @next/bundle-analyzer
# or
yarn add @next/bundle-analyzer
# or
pnpm add @next/bundle-analyzer
```
### `@next/bundle-analyzer` usage
To use `@next/bundle-analyzer`:
```
// next.config.js
/** @type {import('next')}.NextConfig */
import('next').NextConfig
const nextConfig = {
experimental: {
optimizePackageImports: ['icon-library'],
},
serverExternalPackages: ['package-name'],
}
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
module.exports = withBundleAnalyzer(nextConfig)
```
* **`optimizePackageImports`** enables manual optimisation of the packages imported. This enables the packages to be imported once while being used in different components as many times as possible.
* **`withBundleAnalyzer`** includes the bundle analyzer's settings to the `next.config.js` file after it has been installed.
* **`serverExternalPackages`** excludes `package-name` from being bundled in the application.
`optimizePackageImports`, `withBundleAnalyzer` and `serverExternalPackages` are used optionally in the `next.config.js` file.
### Identifying and Reporting Bundles
To analyze and generate reports on your bundles, use:
```
ANALYZE=true npm run build
# or
ANALYZE=true yarn build
# or
ANALYZE=true pnpm build
```
## Implement Lazy Loading Strategies
Lazy loading is a performance strategy that reduces page load time by rendering web pages in lightweight until users actively navigate to certain components in the web page. For example, it is useful for enjoyable scrolling engagements by users. So far in this article, you have implemented lazy loading for images and videos.
Server components have features that enable automatic render-delay. To enable manual lazy loading in server components, implement **Streaming**.
Client components cannot be delayed automatically. To achieve lazy loading in client components, you have to use `dynamic` imports or `Suspense`.
In this section, you will lazy load client components only:
#### Using `dynamic`
`dynamic` is a callback function that returns the components we want to lazy load as imports. To disable pre-rendering in client components, set the `ssr` option to `false`. To enable custom loading in `dynamic` imports, set the `loading` option to a preferred UI:
```
// artsExhibitionPage.jsx
'use client'
import dynamic from 'next/dynamic'
// client components:
const Leonardo = dynamic(()=> import('../ui/leonardo'))
const Picasso = dynamic(()=>import('../ui/picasso'))
const Michelangelo = dynamic(()=>import('../ui/michelangelo'), {ssr: false})
const Magritte = dynamic(()=>import('../ui/magritte'), {
loading: () => <div>While waiting for Magritte's arts work, just view me instead</div>
})
export default function ArtsExhibition() {
return (
<div>
<Leonardo />
<Picasso />
<Magritte />
<Michelangelo />
</div>
)
}
```
![dynamicui](https://hackmd.io/_uploads/BJQJRo8pC.gif)
#### Loading named exports with `dynamic`
```
// vermeer.js
export function Vermeer() {
return(
<div>...</div>
)
}
```
To lazy load the named export `Vermeer` using `dynamic`, return it as a Promise after it has been imported:
```
// artsExhibitionPage.jsx
'use client'
const Vermeer = dynamic(import('../components/vermeer').then((res)=>res.Vermeer))
```
### Using `Suspense`
With `Suspense`, you can lazy load client components while providing a `fallback` component:
```
// upcomingArtsExhibitionPage.jsx
'use client'
import { Suspense} from 'react'
import Leonardo from '../components/leonardo.jsx'
import Picasso from '../components/picasso.jsx'
export default function UpcomingArtsExhibition() {
return(
<Suspense fallback={<div>Waiting for all components to load at once</div>}>
<Leonardo />
<Picasso />
</Suspense>
)
}
```
### Lazy loading packages or libraries
To load packages or libraries only when needed, use the `await` import:
```
// myPage.jsx
'use client'
import { useState } from 'react'
export default function MyPage() {
const [results, setResults] = useState(false)
return(
<div>
<button onClick={async()=>{
const MyLibrary = (await import('my-library.js')).default
const myLibrary = new MyLibrary()
setResults(myLibrary)
}>
Show Result
</button>
<div>Results: {JSON.stringify(results)}</div>
</div>
)
}
const myLibrary = (await import('my-library.js')).default
```
In the example above, `my-library.js` will load only when the `<button>` is clicked.
### Solving lazy loading problem with caching
When lazy loading complex components, different problems may arise. An example is multiple requests from network servers at the same time. To prevent this problem, cache the components. Read about [Caching in NextJs](https://nextjs.org/docs/app/building-your-application/caching).
# Measuring Web Performance
In NextJs, you can always keep track of performance issues in production using:
* **`reportWebVitals`[ hook](https://nextjs.org/docs/app/api-reference/functions/use-report-web-vitals)**: monitors Core Web Vitals (CWV) metrics and sends observed reports manually.
* **Vercel's built-in observability tool**: integrated with other observability tools such as OpenTelementry, Datadog, through a process known as instrumentation.
# Conclusion
In this article, you have been able to understand how different resources affect the user experience of a web app. You have been able to implement different techniques to optimise images, videos, fonts, metadata, URL redirect, scripts, and packages. You also implemented lazy loading strategies for client components and other resources.
Optimisation enables your NextJs web app to be high performant, lightweight, and enjoyable by users. You have also been able to understand different performance metrics and possible ways you can measure the performance of your web app.