DEV Community

Cover image for 10 Next.js Performance Tips I Learned from Real Client Projects
Deepak Kumar
Deepak Kumar

Posted on • Originally published at thecampuscoders.com

10 Next.js Performance Tips I Learned from Real Client Projects

1. Introduction: My Wake-Up Call to Web Performance

Back when I started working as a freelancer, my main focus was getting features out fast—pages looked beautiful, animations were smooth, and clients were happy. But one day, a client came back saying:

"The design is great, but my users are complaining it's slow—especially on mobile."

I ran a quick Lighthouse audit, and boom—Performance score: 43. That was my wake-up call.

Since then, I’ve optimized several Next.js apps—from portfolio sites to dashboards and job boards. In this blog, I’m sharing 10 practical performance tips that helped me speed up real projects and improve user experience dramatically.

And yes—they’re beginner-friendly.


2. Tip #1: Use next/image Instead of Regular <img> Tags

The mistake:

In one portfolio project, I used standard <img> tags with big PNG files. On desktops, it looked okay. But on mobile networks, it took forever to load.

The fix:

I switched to Next.js’s built-in next/image component. It automatically:

  • Compresses and optimizes images
  • Supports lazy loading
  • Serves the right image size based on device

Example:

import Image from 'next/image';

<Image
  src="/profile.jpg"
  width={300}
  height={300}
  alt="My Profile Pic"
/>
Enter fullscreen mode Exit fullscreen mode

Impact:

Page load time improved by 40%, and the Lighthouse "Largest Contentful Paint" (LCP) score jumped from red to green.

Beginner Tip:

Always use next/image for static images. It handles a LOT of performance work for you.


3. Tip #2: Analyze Your Bundle Size (and Trim the Fat)

What happened:

In a dashboard project, I imported chart.js, moment, and other big libraries directly. Everything worked—until I checked the production build.

The issue:

My JavaScript bundle was huge. First load took more than 6 seconds on 3G.

Solution:
I ran this command to visualize my bundle:

npm install @next/bundle-analyzer
Enter fullscreen mode Exit fullscreen mode

In next.config.js:

const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({});
Enter fullscreen mode Exit fullscreen mode

Then ran:

ANALYZE=true npm run build
Enter fullscreen mode Exit fullscreen mode

What I saw:

Huge chunks coming from moment and unused components.

Fixes I made:

  • Replaced moment with date-fns (smaller alternative)
  • Split heavy components using dynamic import
  • Removed unused UI libraries

Result:

Bundle size dropped from 1.2MB to 600KB. First load time was cut in half.


4. Tip #3: Use SSR Only When You Really Need It

What I did wrong:

While building a freelance job board using Next.js, I thought using SSR (Server-Side Rendering) for every page was the best idea.

Why? Because it sounded "professional" and I wanted live data.

The issue:

Each page request hit the server and fetched data in real-time. The result?

  • High server load
  • Slower TTFB (Time to First Byte)
  • Laggy experience for users

What I learned:

SSR is powerful but not always needed.

My fix:

  • Used Static Site Generation (SSG) for pages that don’t change often (like homepage, about, etc.)
  • Kept SSR only for job listings with filters/search
  • In some parts, added Incremental Static Regeneration (ISR) to update static pages every few minutes

Beginner Tip:

Use SSR when content changes on every request (like dashboards or authenticated data).

Otherwise, stick to SSG—it’s faster and cheaper.


5. Tip #4: Cache API Results with SWR (Stale-While-Revalidate)

The problem:

On a personal site, I was fetching my GitHub projects via API on every page load.

Looks dynamic, right? But it slowed down the homepage.

My fix:

I used SWR, a React hook from Vercel that caches data and keeps it fresh in the background.

Code example:

import useSWR from 'swr';

const fetcher = (url) => fetch(url).then((res) => res.json());

export default function GitHubRepos() {
  const { data, error } = useSWR('/api/github', fetcher);

  if (error) return <div>Error loading</div>;
  if (!data) return <div>Loading...</div>;

  return <div>{data.length} repositories found</div>;
}
Enter fullscreen mode Exit fullscreen mode

Benefits:

👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇
👉🔗 👉 Click here to read the full Blog on TheCampusCoders 👈👈👈
👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆

Top comments (2)

Collapse
 
piyushyadav0191 profile image
piyush yadav

Hi thanks for the article. Hey, I am looking for collaboration with freelancers for projects. Please let me know if you have multiple projects and you can't handle on your own.

My Portfolio - ypiyush.live

Collapse
 
raajaryan profile image
Deepak Kumar

Hey Piyush, thanks for checking out the article — glad you found it helpful!

Appreciate you reaching out about collaboration. I’ll definitely keep you in mind for future projects, especially when things get hectic or if there’s something that aligns with your skillset. Your portfolio at ypiyush.live looks clean and focused — nice work!

Let’s stay connected — feel free to drop a message on Instagram or LinkedIn via @thecampuscoders so we can explore potential collab opportunities more easily.

— Deepak | The Campus Coders