DEV Community

Cover image for How I Performance Optimised My Next.js App
Rumen Dimov
Rumen Dimov

Posted on

How I Performance Optimised My Next.js App

So I recently went down a bit of a rabbit hole optimising my Next.js app, and honestly? It was crazy. I wanted to share what I learned because there were some surprises along the way, and maybe it will help someone else trying to speed up their site or it will stir up a nice discussion. Either way, it's a win-win situation for me 😊.

Disclaimer - the things I learned are both from research (google, stackoverflow, dev.to, shady techy websites) and, of course, AI. The cover image is AI generated using Gemini.

The Big Plot Twist

Here's the thing that almost made me lose my mind: my local production build was scoring like 75/100 on Lighthouse. I was freaking out trying everything, questioning my very own existence...πŸ₯΄ and then I deployed it to actual production and...it hit 98/100. How about that?!

Turns out local builds can be super misleading. Your local machine, the way Next.js serves files locally, network conditions – it is all very different from real production. I don't really know the ins and outs of it but the local build is not accurate. So if you're testing locally and getting discouraged, just deploy it and see what happens. You might be pleasantly surprised.
Score on local build

Score on prod

My Approach (aka "Does It Feel Fast? πŸ˜‚")

Look I'm not going to pretend I know if everything I did follows some official "best practices" handbook. A lot of what I did was based on perceived performance – does it feel fast when I use it and does it make sense? Do things load smoothly? Is there annoying layout shift? Also, this is an experiment and a hobby project, so some of the things I did were can be rightfully seen as an exeggeration, i.e. feel free to judge me 😊.
That said, I threw a bunch of optimisations at this thing, and collectively they worked. Here's what I did:

Images Were Eating My Performance

First thing I attacked was images. Next.js has great image optimisation built in, but you gotta configure it properly.
Image optimisation

I set up AVIF and WebP formats (way smaller than JPEGs), and added device-specific sizing. Then I went aggressive on caching – 1 year cache headers on images. Why? Because once someone downloads an image, there's no reason to download it again.
The file size difference is real – I was seeing 30-50% smaller images with modern formats.
Again, reminder, I am not teaching, I am just sharing what I did so if someone knows better, please feel free to level some serious criticism, I'm happy to learn.

Fonts Can Block Your Entire Page

This one was really strange to me at first. I used to just throw Google Fonts in there and call it a day. But fonts can actually block your page from rendering while they load. Next.js has a solution:

Fonts being configured

The display: 'swap' is key – it shows a fallback font immediately while your web font loads in the background. No more blank screens waiting for fonts.

Cache Everything That Doesn't Change

I went a bit crazy with caching:

  • Static assets: 1 year
  • Fonts: 1 year
  • Optimised images: 1 year

Why so aggressive? Because these files don't change. When I deploy a new version, Next.js generates new filenames anyway, so there's no risk of serving stale content. Feel free to disagree.

Cashing

Code Splitting Saved My Bundle Size

This was solid. I had some heavy components – charts, modals, stuff like that. They were bloating my initial JavaScript bundle. So I dynamically imported them:

dynamically importing components

The ssr: false means it only loads on the client, and only when needed. This cut my initial bundle size by...I don't know how much but it was definitely by a lot.

Tree-Shaking Icons

Icon libraries are sneaky bundle size killers. I was using lucide-react and @radix-ui/react-icons, which have like hundreds of icons. I was only using maybe 15.

Database Queries

On the backend I made sure to only fetch the columns I actually needed, which makes perfect sense. I also used joins instead of multiple queries when I needed related data. Fewer round trips to the database = faster API responses.

Skeleton Loaders Are Your Friend πŸ˜‚

Layout shift is annoying. You know when you're reading something and suddenly the whole page jumps because an image loaded? Not a great UX is it.
I added skeleton loaders for basically everything that loads asynchronously. I have 7 different loading.tsx files for different sections:

Skeletons in use

This keeps the layout stable while content loads.

Server Components vs Client Components

Next.js 13+ has Server Components thing and I think it's great for performance. The basic rule I follow:

  • Server Components (default): Marketing pages, content that doesn't need interactivity, and of course anything that renders a lot of data
  • Client Components: Forms, interactive widgets, anything that needs useState or onClick and that does not require a whole lotta data.

Here's why I think the Server Components are so good for data-heavy stuff: they render on the server, so all that data processing happens server-side. The client just gets the final HTML. No huge JSON payloads sent over the network, no client-side rendering loops.

For example, I have an analytics dashboard that displays tons of data, charts, metrics – all that gets rendered on the server. The browser just receives ready-to-display HTML. Compare that to a traditional SPA where you'd fetch all that data, parse it client-side, then render it. Way more work for the browser.

Server Components mean less JavaScript shipped to the browser. Less JavaScript = faster page.

What do you think?

The Little Things That Add Up:

  • Middleware efficiency – I excluded static assets from auth checks. No point checking authentication on every single image request...or is there?
  • useMemo for expensive calculations – Had a filtering function that ran on every render so wrapped in useMemo

Some Honest Thoughts

Did I need to do all this? Maybe not. How cyber safe is it? It certainly has flaws. Could some of these optimisations be overkill? Possibly. But the end result is a site that feels fast and I experimented the hell out of this thing...πŸ˜‚ most importantly I had fun.
I'm sure there are more optimisations I have missed and this is where you come in. Also at some point you gotta ship the thing and move on to actually building features and enjoy life.

Also, seriously, don't stress too much about your local Lighthouse scores. They can be insanely misleading. Test in production (or a production-like environment) to get real numbers.

My Stack
For context, I'm using:

Next.js 14 with App Router
TypeScript
Tailwind CSS
Supabase (PostgreSQL)
Deployed on Vercel

Hope someone find some value in this!

Happy coding!!! 😊

Top comments (2)

Collapse
 
usman_awan profile image
MUHAMMAD USMAN AWAN

πŸ™Œ nice bro

Collapse
 
onlinereceiptmaker profile image
Online Receipt Maker

Nice!