The Slow App Reality Check
You build a Next.js app. It feels blazing fast on localhost.
You deploy it. You share the link.
Then your buddy opens it on his three-year-old Android phone on a 3G network and sends you a screenshot. It took 8 seconds to load.
Your heart sinks.
We've all been there. You thought Next.js gave you performance out of the box. It does. But it also gives you enough rope to hang yourself.
I spent three weeks obsessively optimizing a heavy Next.js dashboard. I read all the docs. I watched all the YouTube videos.
Here is what actually worked, stripped of the marketing hype.
Stop Shipping So Much JavaScript
This is the holy grail. It is the only thing that really matters.
You think you need that massive date formatting library? You don't. Use the native Intl API. You think you need that crazy animation library for one fade-in effect? You don't. Use CSS.
Every kilobyte of JS you ship has to be downloaded, parsed, and executed.
I opened the Next.js bundle analyzer and almost cried. My dependencies were massive.
- I swapped Lodash for specific imports.
- I brutally cut out heavy UI components I wasn't fully utilizing.
- I moved everything I could to Server Components.
If it doesn't need interactivity, it doesn't need to be shipped to the client. Period.
The Image Component: Use It Properly
Next.js gives you next/image. It's great. But you are probably using it wrong.
I was just throwing it everywhere without thinking.
Here is the secret sauce:
- Prioritize LCP: Find your Largest Contentful Paint image (usually your hero image). Add the
priorityprop to it. This tells the browser to fetch it immediately. - Size it right: Don't let the server guess. Give it explicit width and height to prevent layout shifts.
- Formats: Ensure WebP and AVIF are enabled in your next config.
Just fixing my hero image dropped my LCP time by 1.2 seconds. That is an eternity on the web.
Caching is Your Best Friend (And Worst Enemy)
Next.js caching is incredibly powerful, and incredibly confusing.
You have the Data Cache, the Full Route Cache, the Router Cache. It's a lot.
Here is my practical advice: Cache everything aggressively by default.
Only opt out of caching when you absolutely, positively need real-time data. And even then, ask yourself: does the user really need this data to be fresh to the millisecond? Or is a 60-second stale cache acceptable?
I started using revalidate aggressively. Incremental Static Regeneration (ISR) is the superpower of Next.js. Use it.
The Third-Party Script Trap
Google Analytics. Intercom. Hotjar.
Marketing wants them all. Your performance score hates them all.
Do not put these in your main bundle. Do not load them immediately.
I moved every single third-party script to the next/script component and set the strategy to lazyOnload.
Let the user see the content first. Let them start reading. Then, and only then, load the trackers in the background. Your users don't care about your analytics. They care about your content.
The Truth About Performance
Optimization is a rabbit hole. You can spend weeks chasing a perfect 100 Lighthouse score.
Don't.
Get it fast enough that the user doesn't notice the loading. Then stop. Go build features.
But keep the bundle analyzer open. Because bloat always creeps back in.