Best practices to increase the speed for Next.js apps
[Ed. note: While we take some time to rest up over the holidays and prepare for next year, we are re-publishing our top ten posts for the year. Please enjoy our favorite work this year and we’ll see you in 2023.]
In recent years, web application development has undergone a radical transformation due to the rise of Next.js. This framework allows developers to build powerful web apps with JavaScript without worrying about building the back-end infrastructure. It simplifies the process of creating hybrid applications with client-side as well as server-side rendered pages. While the framework is simple, developers still struggle to increase the speed of their applications.
An application’s speed is strongly related to the amount of time it takes to serve the application code, styles, and data to the client in the first round trip. When the server needs to send additional assets (for example images) during the initial round trip, the application performance degrades. Fortunately, developers can follow a number of best practices to improve the speed of their Next.js applications.
Use server-side rendering
Server-side rendering (SSR) is a technique used to render the initial HTML of a webpage on the server before delivering it to the browser. Using server-side rendering will help your app reduce the time required to render the first page on the client side, so the user will see the content of your page much faster. SSR will also improve application performance, especially on mobile devices.
Next.js provides an async function named getServerSideProps
that we can use to render any page on the server and return static HTML to the client. You can do your data-fetching work inside this function.getServerSideProps
function takes a context object as a parameter that contains page data such as params
, res
, req
, query
, etc. This function will be called by the server on every request, returning an object that will be passed to the page component as a prop. In other words, this function allows you to fetch your data from the API and return the fetched data to the page component as a prop.
Example:
// This function will be called by the server
export async function getServerSideProps({context}) {
// Fetch data from external API
const data = await fetch(`YOUR_API`)
// Returning the fetched data
return { props: { data } }
}
function SSRPage({ data }) {
// Displaying the data to the client
return(
<div>{data}</div>
)
}
export default SSRPage
In the above example, whenever the user visits the SSR page, the getServerSideProps()
function will be called by the server and will return the fully rendered static page.
Use dynamic imports
Traditionally, applications load all the components and the CSS required by the application in the initial load. Dynamic import allows you to split your code into small chunks and load them on demand. In the context of web applications, this means that you can import specific components on an as-needed basis. If a user never interacts with a particular component, that component will never be loaded. This can be a huge performance boost, especially on mobile devices. This will also reduce the initial load time and the overall bundle size of the application.
For example, if the user hasn’t logged in, you can lazy load the login component. To use dynamic import, you just need to import the code using an ES2020 dynamic import.
import dynamic from 'next/dynamic'import SimpleButton from '../components/Buttons'
const DynamicComponent = dynamic(() => import('../components/LoginButton'))
function Program() {
return (
<div>
<SimpleButton />
<DynamicComponent />
</div>
)
}
export default Program
In the above code, we are using the dynamic component provided by the framework to load our login button dynamically. You can pass a component name, an array of module names, and a function inside the component that will be invoked when the module is loaded.
Cache frequently used content
Caching improves response times and reduces bandwidth usage by serving content from a cache instead of the original source. Next.js has built-in caching so pages load faster. To implement caching in your Next.js application, you can manually set the headers on any API routes that retrieve content and server-side rendered props to use Cache-Control
. Below is the implementation for built-in caching.
For API routes:
export default function handler(req, res) {
res.setHeader('Cache-Control', 's-maxage=10');
}
For server-side rendering:
export async function getServerSideProps({ req, res }) {
res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
)
return {
props: {},
}
}
For static files and assets, you don’t have to manually add caching; Next.js automatically adds them.
Remove unused dependencies
Many applications depend on third-party packages. While dependencies are definitely good for your app, they increase its size and loading time. If you are using npm packages in your Next.js application, you should watch for unused dependencies. They take up space in your final bundle and might cause unexpected behaviors in your application.
If you have a small project, you can easily find the unused dependencies and remove them from the package.json
file of your Next.js app. But if you have a large project with lots of different dependencies, it may be difficult to find the unused dependencies. In this case, use the depcheck
package to find unused dependencies in your project (this package is included with npm).
I recommend that you remove dependencies one by one and restart your application after each removal to ensure that the dependency was truly not needed and that you didn’t break your application.
Optimize images
Image optimization involves reducing the size of an image file. Because images are one of the biggest assets weighing down your app’s performance, reducing the size of image files can improve performance. This is a two-step process: 1) resize the image to a smaller size and 2) save it in the correct format (jpeg is better for photos; png is better for graphics).
Next.js provides an inbuilt next/image
component that we can use in place of the native <img>
component.
import Image from 'next/image'
function OptimizedImage() {
return (
<>
<h1>Next.js Image</h1>
<Image
src={image_url}
alt="Any Text"
width={500}
height={500}
blurDataURL="URL"
placeholder="blur"
/>
</>
)
}
export default OptimizedImage
Now let’s look at the benefits of the next/image
component.
Lazy loading:
Lazy loading is the process of loading a particular chunk of an app only when it is visible in the client viewport. By default, the next/image
component lazy loads images, which will decrease the loading time. If you don’t want to lazy load an image, set priority={true}
to turn it off.
Placeholder images:
Using the next/image
component, you can add a blurred placeholder for any image using the placeholder
prop.
Preload images:
If you have multiple images in a page, you can prioritize loading using the next/image
component.
Optimize your scripts
In addition to npm dependencies, many applications use third-party scripts like Google Analytics, Google AdSense, and Bootstrap. These scripts can further slow your Next.js app. Instead of using the default <script>
tag, you can use the next/script
component of Next.js. It allows you to set the loading priority for third-party scripts.
For example:
import Script from 'next/script'
export default function OptimizedScript() {
return (
<>
<Script
id="YOUR_ID"
src="URL"
onError={(err) => {
console.error('Error', err)
}}
onLoad={() => {
// Function to perform after loading the script
}}
/>
</>
)
}
By setting the value of the strategy
prop in the next/script
component, you can use three different script loading approaches:
afterInteractive
: The script will be loaded on the client side after the page becomes interactive.beforeInteractive
: The script will be loaded on the server side before self-bundled JavaScript is executed.lazyOnload
: The script will be loaded after all other resources are loaded.
After applying one of these strategies, check the speed and performance of your app using web performance tools like Google pagespeed. A web performance tool can provide valuable information about application performance, such as:
- The time it takes to get the initial page.
- The time it takes to get the initial resources.
- The number of round trips transmitted.
- The amount of data transferred each trip.
Start building faster Next.js applications
Next.js has become popular because it allows developers to build powerful JavaScript apps without having to build the back-end infrastructure, but it’s also full of features that can help you improve application performance while doing much of the heavy lifting on the server. Following these best practices will help you take advantage of those features so you can start building faster Next.js applications.
Tags: next.js, performance, server-side rendering
8 Comments
Hi! I am new in coding, how can I get high speed js?
Yeah just for coding for new programmer.
“Use server-side rendering”
It’s almost like we knew this was the answer 20 years ago.
Hey Imran,
This post is very helpful.
I already implement many of these point for my company and It improve the performance.
I’m adding one point that we can render image quality as requirement. Like if user visiting a website from mobile then loading high quality image would increase loading time.
I laugh every time the topic of server-side js comes up, especially in terms of performance or security. It’s very sad that we’re still trying to jam a square peg in a round hole. Imagine someone taking their graphing calculator and trying to make it into a web server. Sure, it can be done, but it’s an utterly horrible idea. That’s what you’re doing when you try to use javascript for anything except client-side UI enhancement. This goes for many areas in life – “Use the right tool for the job.”
I see your expression raise you another: “You can’t teach an old dog new tricks”
I don’t think SSR is good in every page with dynamic data.
I suggest using SSR in small pages (eg. a info page for a post fetched from a database or a profile page), or for pages that change every 2-3s and need the newest data every request.
ISR is a faster approach that provides the speed of a static page with the dynamic data of a SSR page.
It can revalidate data and its suitable for most pages with dynamic data. (Remember to use it for data that changes after a reasonable amount of time, like 5-10s. Using it for pages with rapidly changing data can cause the page to not have the latest data)
> Use server-side rendering
> Server-side rendering (SSR) is a technique used to render the initial HTML of a webpage on the server before delivering it to the browser. Using server-side rendering will help your app reduce the time required to render the first page on the client side, so the user will see the content of your page much faster. SSR will also improve application performance, especially on mobile devices.
Huh??? SSR will not reduce the time to render the first page on client side. The fastest way to deliver data is with SSG, since the HTML will be compiled at build time, rather than being regenerated/hydrated with user data server-side before being delivered. SSR has specific purposes, as does SSG and client side rendering (e.g. with fetch hooks, SWR, etc). I really dislike the certainty with which you prescribe SSR.