Skip to main content

Command Palette

Search for a command to run...

Back to Blog
How to Optimise a Next.js Web App
nextjsreactperformanceoptimization

How to Optimise a Next.js Web App

Optimise your Next.js web app to make it lightning fast!

So in this blog, I'm going to talk about React's and Next.js best practices. This blog will contain landing page and functional both types of optimisations.

Let's get started, but before, let me mention common terms that will be used in this blog.

  • First Contentful Paint (FCP)
  • Largest Contentful Paint (LCP)
  • First Input delay (FID)

Google uses metrics (web vitals) to rank a website on search pages. You can use tools like pagespeed analytics tool to check the current score of the website/web page.

Below is a reference

Bundle Size

A bundle is a JavaScript file that we send to the user's browser to execute the logical or UI part of a frontend application. Ideally, the bundle size should be ~500 KB, but in most cases, or due to UI libraries or different dependencies, the bundle size tends to increase. Make sure it's not exceeding 1500 KB keep it under that.

How you will do that?

First, you need to check what the current bundle size of the page is. If you are on Next.js 16 or above, then there is a CLI command available to analyse it.

npx next experimental-analyze

Refer this Doc

It will look something like this

Bundle Size Analysis using Next.js CLI

You can use external packages for React, Next.js to get the approx bundle size.

To reduce bundle size

  • Find and eliminate unused library import.
  • Remove external libraries as much as possible
  • If you are using a library for something that can be replaced with one or two functions, replace the library with your own implementation. For example you are using react-video library for Resolution setting in the video you could just remove the library with a simple function.
  • The library increasing bundle size ~300 kb that can be reduced + your own implementation will take less of bundle size.

Use specific imports instead of importing everything

There are a lot of packages / libraries that does Barrel export. Mean a single file like index.ts have all the exports.

export * from "./LoaderIcon.tsx"
export * from "./Moon.tsx"
export * from "./Sun.tsx"

So when you import anything from the index.ts file it import the whole file.

import { LoaderIcon } from '@lucide/react' // (size: ~2MB (potentially))

Now someone who read webpack or turbopack implementation knows that barrel imports get optimized. Which is correct but not in case of when you are importing from node_modules web/turbo pack ignore barrel imports if they are from node_modules.

So instead of importing like above pattern use below pattern.

import LoaderIcon from '@lucide/react/disk/icon/LoaderIcon.tsx'

This will insure you are importing only what is needed.

You can also add the following thing in your next.config.ts Official Doc

module.exports = {
  experimental: {
    optimizePackageImports: ['package-name'],
  },
}

By doing this nextjs will automatically going to optimise the package for you.

Optimising FCP & LCP

To optimise First Contentful Paint (FCP) in Next.js, you should focus on delivering the critical content as quickly as possible by prioritising resources above the fold and reducing render-blocking assets.

  • Use the <Image> Component: Replace standard <img> tags with the Next.js Image component. This component automatically optimizes images, uses modern formats like WebP, and implements lazy loading by default. But only if you are hosting on vercel or have setup own CDN server or using any CDN server so make sure that you have CDN in place.
  • Use the priority property for LCP image: Apply the priority prop to the image that appears in the initial viewport (the LCP element). This disables lazy loading for that specific image and adds a preload link in the HTML head, ensuring it's fetched as early as possible.
  • Avoid lazy loading in-viewport images: Do not use loading="lazy" or other JavaScript-based lazy loading solutions for images that are "above the fold". This delays the image's loading until the JavaScript is executed, increasing LCP.
  • Use LQIP to increase the perceived speed: LQIP (Low quality image placeholders) increase the perceived speed.
  • Use next/font: automatically optimizes your fonts (including custom fonts) and removes external network requests for improved privacy and performance.
  • Defer or async 3rd party services: Load other scripts like Tracking services (PostHog, umami, GTAG) after the page delivery. Do not try to load everything all together in content delivery you can use next/dynamic to load them after hydration is done.
  • Use SSR (Server Side Rendering) as much as possible.

Rendering Strategies (SSR, SSG, ISR, CSR)

Next.js supports multiple rendering strategies. Choosing the correct strategy has a direct impact on Web Vitals, especially FCP, LCP, and TTFB (Time To Firsts Byte).

Static Site Generation (SSG)

In SSG, HTML is generated at build time and served via CDN.

When to use:

  • Landing pages
  • Marketing pages
  • Documentation
  • Content that does not change frequently

Why it helps performance:

  • No server computation at request time
  • Serving via CDN so extremely low TTFB
  • Best possible FCP and LCP

Incremental Static Regeneration (ISR)

ISR allows you to regenerate static pages at a fixed interval.

export const revalidate = 60

When to use:

  • Blogs
  • Product pages
  • Content-driven applications

Why it helps performance:

  • Keeps pages static for most requests
  • Avoids full rebuilds
  • Maintains good LCP while keeping data fresh

Server-Side Rendering (SSR)

SSR generates HTML on every request.

When to use:

  • SEO-critical pages with highly dynamic data
  • Pages requiring request-time personalization

Trade-offs:

  • Increased server load
  • Higher TTFB compared to SSG / ISR
  • Backend latency directly affects frontend performance

Client-Side Rendering (CSR)

CSR renders content entirely in the browser. (This is what plain react does and we get in default Vite + React app)

When to use:

  • Dashboards
  • Authenticated internal tools
  • Highly interactive applications

Downside:

  • Poor FCP and LCP
  • SEO is affected
  • Heavy hydration cost

React Server Components (RSC)

React Server Components allow components to run only on the server and send rendered output to the client without shipping JavaScript.

Why RSC improves performance:

  • Reduces JavaScript bundle size
  • Eliminates hydration for server components
  • Improves INP and overall responsiveness

By default, all components in the Next.js App Router are Server Components.

Only components marked with "use client" are sent to the browser. Avoid marking entire pages as client components.

Instead make a child component and make it client sided, This ensures only required JavaScript is shipped.

Code Splitting and Dynamic Imports

Next.js performs route-level code splitting automatically, but component-level splitting is still required for large client-side features.

Use dynamic imports when dealing with:

  • Heavy components (charts, editors)
  • Client-only libraries
const Chart = dynamic( () =>
    import("./Chart"),
    { ssr: false }
)

Do not use them when:

  • Small shared components
  • Above-the-fold UI
  • Layout components

Excessive dynamic imports increase network requests and can cause loading waterfalls.


Hydration Optimization

Hydration is the process where React attaches event listeners to the static HTML server sends. Poor hydration strategy can lead to poor INP (Interaction to Next Paint).

Tips for better hydration:

  • Use suppressHydrationWarning sparingly on components that intentionally differ between server and client.
  • Avoid putting heavy state initialization in components high up the tree.
  • Consider using use server directive to keep logic on the server.

Memoization and Performance

React re-renders components when state or props change. Excessive re-renders can hurt performance.

Use these wisely:

  • React.memo - memoizes a component to prevent unnecessary re-renders
  • useMemo - memoizes expensive calculations
  • useCallback - memoizes callback functions
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);

When NOT to use memoization:

  • If the component renders rarely with the same props
  • If the computation inside is cheap
  • Over-memoization can actually hurt performance due to the memoization overhead

Animations and Performance

Animations can make or break your web app's perceived performance.

Best practices:

  • Use CSS transforms and opacity for animations - they run on the GPU
  • Avoid animating layout properties (width, height, top, left) - these trigger reflows
  • Use will-change sparingly to hint the browser about upcoming changes
  • Consider using libraries like Framer Motion that are optimized for performance
/* Good - GPU accelerated */
.element {
  transform: translateX(0);
  opacity: 1;
  transition: transform 0.3s ease, opacity 0.3s ease;
}

/* Avoid - causes layout recalculation */
.element {
  width: 100%;
  transition: width 0.3s ease;
}

Database Queries and Data Fetching

Backend performance directly impacts frontend metrics like TTFB and INP.

Tips:

  • Use database indexing for frequently queried fields
  • Implement query caching at the application level
  • Use connection pooling for database connections
  • Consider using Edge caching for API responses

Monitoring and Measurement

You can't optimize what you can't measure. Use these tools:

  • PageSpeed Insights - Google's tool for analyzing page performance
  • WebPageTest - Detailed waterfall analysis
  • Chrome DevTools - Performance tab for real-time analysis
  • Next.js Analytics - Built-in analytics if using Vercel
  • Sentry - Error tracking + performance monitoring

There are still many more topics we haven't covered here - Web Vitals in depth, PWA optimization, Service Workers, Caching strategies, and more. We'll tackle those in future blogs.

Keep iterating, keep measuring, and remember: premature optimization is the root of all evil. Profile first, then optimize where it matters most.

Related Posts

From 4 Minutes to 2 Seconds: Docker Build Optimization

How we achieved 100x faster Docker rebuilds and 42% smaller images while hardening containers for production

dockerdevopsperformance+2 more
Read More