7 Next.JS Tips to get a 99% score on Google PageSpeed
Next.js is a framework created by Vercel. It is open-source based on Node.js and Babel and integrates with React for developing single-page apps. Here’s a list of things you might be able to do to improve the performance of your NextJS application.
1. SSR (server-side rendering)
Modern JavaScript frameworks have made our lives as developers much easier. We can create powerful, rich web applications using many different rendering techniques.
You have probably heard of single-page applications before. A single-page application is an application that is rendered at the client-side, even if the data might be fetched from the server.
Server-side rendering (SSR) is the exact opposite of this. SSR describes the process of pre-rendering the page on the server, which is then generated upon each user request.
When the pre-rendered page reaches the browser, JavaScript code is run to make the page interactive. This whole process makes the load speed faster. It also makes it easier and preferable to use server-side rendering for applications that depend on SEO.
Next.js does this work out of the box. By default, it will try to detect which pre-rendering technique your application is using and fetch your data.
Years ago, before JavaScript became so mature and powerful, developers usually returned HTML files based on HTTP calls. It was a very common technique to process the response on the server-side using a server-side language (usually PHP) and returning a static HTML file.
2. SSG (static site generation)
Static-generated websites are nothing new for developers. We have been building them since the beginning of the web. Building rich web experiences can be hard, but with Next.js we can do so easily.
Next.js has introduced us to a better way of building static-generated websites with more dynamic performance.
SSG describes the process of building websites that render at build time. The output is an HTML file, assets such as JavaScript and CSS, and a few other static files.
When using SSG with Next.js, the page is pre-rendered at compile time. That means that the user won’t have to wait for the page to load at the browser; the page will simply be rendered.
For data fetching, Next.js provides three different functions:
getStaticProps
: The page will be pre-rendered at build timegetServerSideProps
: The page will be pre-rendered at runtimegetStaticPaths
: This function generates a list of pages that will be pre-rendered at build time
The biggest disadvantage of using SSG is that the build time can get very long. You won’t have a problem when you have only a few statically-generated pages, but as your application grows, the build time will increase.
The worst-case scenario is when you have hundreds of statically-generated pages. The build time will take a long time, and if you have dynamic content on those pages, you can end up with too many builds.
3. The <Image/>
component
The <Image/>
component was contrived by the creators of Next.js to solve the optimization issues the former lacks.
It is a better and enhanced version of the <img>
tag, but unlike the <img>
tag, the <Image/>
component is not a native HTML element – but a built-in API in Next.js.
The component essentially wraps the <img>
element with other div elements to prevent cumulative layout shift.
Benefits of using the component
The <Image/>
component’s API doesn’t just render images to the browser like the <img>
tag, it also optimizes the image for every viewport by implementing each of the functionalities below out of the box:
-Lazy loading: Every image linked to a webpage with the <Image/>
component is fetched and rendered on-demand as soon as its holding space is scrolled into view by default. So you never have to worry about slow load time in your web pages and writing extra scripts for such functionality.
-Responsiveness: Images are automatically responsive when rendered with the <image/>
component, saving you from the stress of CSS media queries.
-Resizing: The appropriate size and aspect ratio of an image for a viewport is fetched and rendered on-demand, instead of fetching the intrinsic size and aspect ratio before reducing it for the target viewport.
-Optimized file size: The <Image/>
component fetches the appropriate file size for each viewport, taking away the need to store images with different file sizes for every viewport on the server, and fetching them one after the other when needed.
- Optimized image format: when an image is fetched from the server with the
<Image/>
component, the file format is changed to a more optimized and SEO-friendly one like WebP, if the browser supports it.
4. Font Optimization
Automatic Webfont Optimization is a feature that shipped with Next.js 10.2.
How to use it?
Add a link
tag that loads the Google fonts CSS to Next.js’ built-in Head
component.
import Head from 'next/head';export default function MyPage() {
return (
<div>
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Lobster"
rel="stylesheet"
/>
</Head>
<p>Hello world!</p>
</div>
)
}
Head
is a special component that enables us to append the HTML <head>
element from our page components or from the custom Document
component.
More info: next/head | next/document
How does it work?
Our code above is compiled into this on build:
<head>
<!-- ... other head/meta tags -->
<link rel="stylesheet" data-href="https://fonts.googleapis.com/css2?family=Lobster">
<!-- ... -->
<style data-href="https://fonts.googleapis.com/css2?family=Lobster">
@font-face{font-family:'Lobster';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/lobster/v23/neILzCirqoswsqX9zoKmM4MwWJU.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
</style>
</head>
Without optimization:
- the
link
tag loads the Google fonts CSS,https://fonts.googleapis.com/css2?family=Lobster
- the CSS
@font-face
loads the actual font source,https://fonts.gstatic.com/s/lobster/v23/neILzCirqoswsqX9zoKmM4MwWJU.woff2
With optimization, we skip step (1). Notice that the href
attribute is changed into data-href
. Instead, the style declarations are inlined and injected to the <head>
. The browser requests the font file(s) in step (2) directly.
5. Dynamic Import
Unlike regular import modules, dynamic imports are flexible about when and how they are loaded. Instead of being forced to load the module file at reading time, dynamic imports can be requested at the time of use. By code splitting the module into a separate bundle file it can be fetched separately which reduces the initial page load.
Building an Example
Setting up dynamic imports in Next.js can be done in a few lines of code that are demonstrated in the example below. For more advanced ways to use the dynamic function, make sure to check out the Next.js Dynamic Imports.
- First, import the
dynamic
function usingimport dynamic from "next/dynamic";
. - Next, create a new constant. Assign the
dynamic
function to reference your component. Ex: const GoodbyeDynamic = dynamic(() => import("../components/Goodbye"));
Now the const can be used just like the original Goodbye
component.
The following example demonstrates Goodbye
component being dynamically imported. When the app is first rendered, the console statement from <Hello />
is immediately invoked. Meanwhile, the console statement from <Goodbye />
does not invoke until after the button is clicked which triggers the component to <GoodbyeDynamic />
component to render.
import { useState } from “react”;
import Hello from “../components/Hello”;
import dynamic from “next/dynamic”;const GoodbyeDynamic = dynamic(() => import(“../components/Goodbye”));/**
* Before toggling Hello check the console.
* 1. Hello component file is immediately read.
* 2. Goodbye component file is reader once it’s rendered.
*/
export default function IndexPage() {
const [toggleGoodbye, setToggleGoodbye] = useState(false);
return (
<div>
<button onClick={() => setToggleGoodbye(!toggleGoodbye)}>
Toggle Hello Component
</button>
{toggleGoodbye ? <GoodbyeDynamic /> : <Hello />}
</div>
);
}
6. Script Optimization
next/script was introduced with Next.js 11 release and it enables developers to set the loading priority of third-party scripts.
you can define the strategy property and Next.js will automatically prioritize them to improve loading performance:
<Script
src="https://polyfill.io/v3/polyfill.min.js?features=Array.prototype.map"
strategy="beforeInteractive" // lazyOnload, afterInteractive
/>
7. Next.js Analytics
You can integrate your Next.js app with Vercel analytics to get more detailed information about your website’s speed.
First, you will need to create a custom App component and define a reportWebVitals
function:
// pages/_app.js
export function reportWebVitals(metric) {
console.log(metric)
}function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}export default MyApp
This function is fired when the final values for any of the metrics have finished calculating on the page. You can use to log any of the results to the console or send them to a particular endpoint.
check the documentation on how to measure Next.js app performance here.
Conclusion
Getting a score that is higher than 90 in Google’s PageSpeed is a broad topic with much ground to cover. In this article, I’ve only covered the tip of the iceberg about some of the rare practices you can adopt to get better performance results with Next.js.