Discover the power of Next.js as the modern Internet’s go-to framework for developers. Learn how its built-in features and server-side rendering capabilities have made it a top choice. However, achieving high-level performance, short loading times, and an exceptional user experience requires some effort when tuning a Next.js production application.

In this comprehensive guide, we dive into the essential strategies and best practices to deploy Next.js to production successfully.

Taking Advantage of Next.js’ Built-In Optimization Features

Next.js comes equipped with built-in optimization features that can take your production application’s performance to the next level. By simply integrating them into your project as explained in the documentation, you can benefit from:

  • Image Optimization: Leveraging the <Image> component of next/image ensures automatic image optimization, including serving the best-sized images for different devices, converting them to modern formats like WebP and AVIF, and utilizing lazy loading for better user experience.
  • Font Optimization: Use next/font to optimize local fonts and eliminate external network requests, enhancing privacy and performance. It even supports self-hosting Google Fonts to reduce external requests.
  • Script Optimization: Enhance performance with next/script, which loads JavaScript scripts only once across multiple pages.

Configure Caching and Use Incremental Static Regeneration Whenever Possible

Next.js treats file inside the /public folder as static assets. For example, if you store a logo.png image file in /public, you can then reference it in your code as /logo.png:

<Image src="/logo.png" alt="Company's Logo" width="200" height="200" />

Usually, /public contains static images, JavaScript, CSS, and other media files. Caching these resources is a good way to improve response times and reduce the number of requests required to render a page.

Here is why, by default, Next.js add the following header in production for those assets:

Cache-Control: public, max-age=31536000, immutable

This instructs the browser to cache those files for one year. So, if your Next.js site relies on some static resources, you should download and place them inside /public.

Next.js also provides the minimumCacheTTL configuration to specify the TTL (Time To Live) in seconds for caching images optimized through <Image>. Set it in next.config.js as follows:

// next.config.js

module.exports = {  
  images: {
    // cache optimized images for 60 seconds    
    minimumCacheTTL: 60,  
  },
}

Similarly, Next.js can cache statically generated pages through Incremental Static Regeneration (ISR). Thanks to this feature, new static pages can be created or updated after the application has been built.

To enable ISR, set the revalidate option in getStaticProps():

export async function getStaticProps() {
  // ... 
  return {
    props: {
      // ...
    },
    // Next.js will re-generate this page 
    // when a request comes in, at most once
    // every 10 seconds
    revalidate: 10, 
  }
}

This is how ISR works:

  1. The site initially shows the pre-rendered page generated at build time.
  2. Within 10 seconds, it will continue to display the initial page.
  3. When a request arrives after 10 seconds from the last regeneration, the framework triggers a background regeneration of the page.

Note that a Next.js production app only regenerates static pages that are requested by users to save resources.

Integrate an Analytics or APM Tool

Once a Next.js site is in production, you need a way to track its performance. In particular, you should have a system in place to monitor page views and get information about site traffic.

When deploying to Vercel, you can achieve that with Next.js Speed Insights. This tool allows you to analyze and measure the performance of your application’s pages using various metrics. To enable it:

  1. Turn on the Web Analytics option in the Vercel Dashboard.
  2. Add the @vercel/analytics package to your project’s dependencies with npm i @vercel/analytics
  3. Use the <Analytics /> component to inject the analytics script into your app.
  4. Deploy your app to Vercel, and data should start flowing to the Analytics view.

Similarly, you need a service that tracks site performance, alerts you when something goes wrong or the site goes offline, and collects information about bugs and runtime errors. This is what Application Monitoring (APM) is all about.

Some of the most popular APM libraries for Next.js are SentryNew Relic, and AppSignal.

Set Up a Logging System

To keep track of what is going on in a Next.js production app, you must add some logs to your code. The easiest way to log messages on both the client and server is using the methods exposed by the JavaScript console object. The most popular ones are:

  • console.clear(): Clears the browser console.
  • console.log(): Logs general information.
  • console.debug(): Logs a debug message in the browser console and a regular message on the server.
  • console.error(): Logs an error message.
  • console.warn(): Logs a warning message in the browser console or an error message on the server
  • console.time(): Starts a timer that can be used to compute the duration of an operation.
  • console.timeEnd(): Stops the timer and prints the duration of the operation.

If you instead prefer a more structured solution, Next.js recommends pino. This is a fast and lightweight JavaScript logging library designed for high-performance applications.

Enable Error Handling With Custom 500 Pages

Like any other application, Next.js sites are subject to errors. One of the most important aspects of error handling is presenting meaningful error messages to users to inform them of what happened. When an error occurs in the frontend or backend, Next.js displays this static 500 page:

As you can see, this page is not informative at all and may result in a bad user experience for visitors. This is why Next.js supports custom 500 pages.

If you are a Pages Router user, create a 500.js page under /pages:

// pages/500.js

export default function Custom500Page() {  
  // your custom 500 page component.... 
}

This represents the frontend page components that will be shown to users in case of errors.

If you are an App Router user, create an error.js file under /app:

// app/error.js

'use client' // error components must be client components
  
export default function CustomErrorPage({
  error,
  reset,
}) {
  // your custom error page component.... 
}

Note that this must be a client component.

Reduce the Size of the Build Bundle

Producing a minimized bundle is great, as clients will take less time and network bandwidth to download and render the Next.js production app.

During the next build task, Next.js generates an optimized JavaScript bundle for each page.
The goal is to try to reduce those bundles to the bare while preserving functionality. For this purpose, you can use dynamic imports via next/dynamic to lazy-load JavaScript resources. This mechanism allows you to defer loading specific components or libraries until the user performs a particular operation on the page.

To analyze the bundle produced by Next.js and get guidance on how to reduce its size, you can use the following tools:

  • Webpack Bundle Analyzer: To visually explore the size of webpack output files in an interactive treemap.
  • Package Phobia: To analyze the cost of adding a new dependency to your project.
  • Bundle Phobia: To analyze how much a dependency will increase the bundle size.
  • bundlejs: To quickly bundle and minify your project in your browser.
  • Import Cost: To display the size of any imported package inside Visual Studio Code.

Optimize Page SEO Performance With Lighthouse

The ultimate goal of most Next.js sites is to produce excellent SEO results. Google has changed its approach to SEO performance evaluation a great deal over time, and it now focuses primarily on Core Web Vitals:

  • Largest Contentful Paint (LCP): Measures the time it takes for the main content of a page to become visible to users.
  • First Input Delay (FID): Evaluates the time delay between a user’s first interaction and the browser’s response.
  • Cumulative Layout Shift (CLS): Gauges the visual stability of a page by measuring the number of unexpected layout shifts that may annoy users.

These represent user experience metrics that Google uses to assess and quantify the overall performance of web pages and define their ranking in search results.

The best tool for optimizing these indicators is Google Lighthouse, an open-source tool built into Chrome that can be run on any web page to check its SEO performance.

To optimize Next.js for production, you should build the project, start it, open it in Incognito mode in the browser, and launch Lighthouse on each page. This will provide guidance and best practices for improving site performance, accessibility, and SEO.

Conclusion

With the right strategies and best practices, you can deploy a highly performant and reliable Next.js production site. Taking advantage of built-in optimization features, proper caching, monitoring tools, and SEO optimization ensures your application’s success.

Now that you’re armed with the knowledge to optimize Next.js for production, building a top-notch site has never been easier. Happy coding.