DEV Community

Ingila Ejaz
Ingila Ejaz

Posted on

I Just Upgraded My Portfolio from Next.js 14 to Next.js 16 and Felt Like a Child Who Knows Nothing

My honest journey through incremental framework upgrades, breaking changes, and lessons learned

Introduction

When I started this morning, my Next.js portfolio was running smoothly on version 14. By afternoon, after upgrading to version 16, I was debugging 404 errors, dealing with deprecated middleware patterns, and questioning every life choice that led me to this moment.

But here's the thing: upgrading Next.js isn't as scary as it seems. It's just a series of small, manageable steps - kind of like learning to walk again, but digitally.

In this article, I'll walk you through my exact upgrade journey from Next.js 14 → 15 → 16, the unexpected hurdles I hit, the features that impressed me, and the breaking changes that caught me off guard.


Part 1: The Incremental Path - Why Skipping Versions Is Risky

Let me be clear: I didn't jump directly from 14 to 16. That would have been chaos.

The safest approach is to upgrade incrementally:

  1. 14 → 15 (one major version at a time)
  2. 15 → 16 (then proceed to the next)

Why? Because each version introduces breaking changes, and doing them one step at a time makes it easier to identify which upgrade broke what.


The Upgrade Process

Step 1: Next.js 14 → 15

npm install next@15
Enter fullscreen mode Exit fullscreen mode

This initial upgrade went smoothly for me. The main things that happened:

  • React dependencies updated automatically
  • Build process remained largely the same
  • Most of my existing code continued to work

Key learning: Update React and React-DOM at the same time:

npm install react@latest react-dom@latest
Enter fullscreen mode Exit fullscreen mode

Step 2: Next.js 15 → 16

npm install next@latest
Enter fullscreen mode Exit fullscreen mode

This is where things got interesting.


Part 2: The New Features That Made Me Say "Finally!"

1. Turbopack is Now Default

Next.js 16 ships with Turbopack as the default bundler instead of Webpack. This wasn't my choice - it just happened.

The benefit: My initial build time decreased from 22 seconds to 11 seconds. That's a 50% improvement.

▲ Next.js 16.2.3 (Turbopack)
✓ Compiled successfully in 11.0s
Enter fullscreen mode Exit fullscreen mode

Turbopack is written in Rust and is significantly faster for development builds. For production builds, you might still use the old behavior, but the development experience is noticeably snappier.

2. Enhanced React 19 Support

With Next.js 16, React 19 support is more mature. My dependencies now look like:

{
  "react": "^19.2.5",
  "react-dom": "^19.2.5"
}
Enter fullscreen mode Exit fullscreen mode

React 19 brings:

  • Server Components improvements
  • Better error boundaries
  • More predictable state management
  • The new use() hook for cleaner async code

3. Improved Image Optimization

The next/image component in version 16 has better automatic format detection and responsive image handling. Since I have a portfolio with lots of project screenshots, this should help with performance.

4. Better TypeScript Integration

During the build process, Next.js 16 automatically reconfigured my tsconfig.json with better defaults:

{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "react"
  }
}
Enter fullscreen mode Exit fullscreen mode

This means better type checking and IDE autocomplete out of the box.


Part 3: The Breaking Changes That Broke Me (And How I Fixed Them)

🚨 Breaking Change #1: Middleware is Deprecated

The Warning Message:

⚠ The "middleware" file convention is deprecated. 
Please use "proxy" instead.
Enter fullscreen mode Exit fullscreen mode

This was the big one. My project uses next-intl for internationalization (supporting English and German), and the middleware is critical for routing requests to the correct locale.

The Problem:
Next.js ran a codemod to automatically convert middleware to proxy patterns:

npx @next/codemod@canary middleware-to-proxy .
Enter fullscreen mode Exit fullscreen mode

After running this, my routes started returning 404 errors:

  • /en/certifications → 404
  • /de/certifications → 404
  • All locale-based routes → 404

The Solution:
I discovered that next-intl library still relies on middleware. The codemod conversion doesn't work well with i18n setups. So I:

  1. Kept my middleware.ts file intact:
import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  locales: ['en', 'de'],
  defaultLocale: 'en'
});

export const config = {
  matcher: ['/', '/(en|de)/:path*', '/((?!_next|_vercel|.*\\..*).*)']
};
Enter fullscreen mode Exit fullscreen mode
  1. Updated next-intl to the latest version:
npm install next-intl@latest
Enter fullscreen mode Exit fullscreen mode
  1. Cleared the build cache and rebuilt:
rm -r .next
npm run build
Enter fullscreen mode Exit fullscreen mode

The Takeaway: While middleware is supposedly "deprecated," it still works perfectly in Next.js 16 and is necessary for i18n routing with next-intl. The library maintainers haven't migrated to the new proxy pattern yet, and that's okay.

🚨 Breaking Change #2: Missing Dependencies

When I added recharts for data visualization, I encountered:

Module not found: Can't resolve 'react-is'
Enter fullscreen mode Exit fullscreen mode

Recharts has a dependency on react-is that wasn't automatically installed as a peer dependency.

The Fix:

npm install react-is
Enter fullscreen mode Exit fullscreen mode

The Lesson: When adding third-party libraries with Next.js 16, double-check their peer dependencies. The ecosystem is still catching up.


Part 4: What I Actually Built (Real-World Example)

To test all these changes, I added a skills chart component using Recharts:

import {
  BarChart,
  Legend,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Bar,
} from "recharts";
import { techSkills } from "../api/skills";

// Transform skill data into chart format
const data = techSkills.frontend.map((skill) => ({
  name: skill.name,
  years: parseFloat(skill.years),
}));

const BarChartExample = ({ isAnimationActive = true }) => (
  <BarChart
    layout="vertical"
    style={{
      width: "100%",
      maxWidth: "700px",
      maxHeight: "70vh",
      aspectRatio: 1.618,
    }}
    data={data}
  >
    <CartesianGrid strokeDasharray="3 3" />
    <XAxis type="number" />
    <YAxis dataKey="name" type="category" width={100} />
    <Tooltip />
    <Legend />
    <Bar dataKey="years" fill="#8884d8" isAnimationActive={isAnimationActive} />
  </BarChart>
);

export default BarChartExample;
Enter fullscreen mode Exit fullscreen mode

The source data structure (which remained unchanged):

export const techSkills = {
  "frontend": [
    { name: "Angular", years: "7" },
    { name: "React", years: "6" },
    { name: "Next.js", years: "3" },
    // ... more skills
  ],
  "backend": [
    { name: "Node.js", years: "7.5" },
    // ... more skills
  ],
};
Enter fullscreen mode Exit fullscreen mode

This example shows how Next.js 16 handles client-side components, external libraries, and data transformations seamlessly.


Part 5: A Checklist for Your Next.js Upgrade

Based on my experience, here's what you should do when upgrading:

Pre-Upgrade

  • [ ] Commit your current work to git
  • [ ] Document your current version: npm list next
  • [ ] Check all test files pass
  • [ ] Review the official Next.js upgrade guide

During Upgrade

  • [ ] Upgrade incrementally (14→15, then 15→16)
  • [ ] Run npm install next@15 first
  • [ ] Then run npm install next@latest for 16
  • [ ] Update React dependencies: npm install react@latest react-dom@latest
  • [ ] Clear build cache: rm -r .next
  • [ ] Run npm run build to catch compilation errors

Post-Upgrade

  • [ ] Test all major routes in development
  • [ ] Check console for deprecation warnings
  • [ ] Verify third-party libraries are compatible
  • [ ] Test with actual users if possible
  • [ ] Review performance metrics (build time, bundle size)
  • [ ] Update documentation if needed

Part 6: The Performance Impact

Here's what changed for my project:

Metric Before (v15) After (v16) Change
Dev Build Time 22.2s 11.0s 50% faster
Build File Size ~2.1MB ~2.0MB Slightly smaller
TypeScript Check 8.2s 6.0s 27% faster
Dev Server Start 4.3s 2.1s 51% faster

That's a significant improvement in developer experience. Faster builds mean faster feedback loops, which means more productive development sessions.


Part 7: Should You Upgrade? (The Honest Answer)

TL;DR: Yes, but do it incrementally.

Reasons to upgrade:

✅ Turbopack performance improvements
✅ Better React 19 support
✅ Improved image optimization
✅ Better TypeScript defaults
✅ Security updates and bug fixes
✅ Future-proofs your project

Reasons to wait:

⚠️ If you're on a tight deadline with a stable project
⚠️ If you use many third-party libraries that aren't updated yet
⚠️ If you have heavy customization (custom webpack config, etc.)

For most projects, the benefits outweigh the risks.


Conclusion: Feeling Like a Child Who Knows Nothing

When I started this upgrade journey, I felt lost. Deprecated features, 404 errors, missing dependencies - it all seemed overwhelming.

But here's what I learned:

  1. Modern framework upgrades aren't as scary as they seem. They're just a series of small, manageable steps.

  2. The ecosystem matters. Libraries like next-intl still rely on middleware, and that's fine. Not everything needs to change just because the framework says it's "deprecated."

  3. Performance wins are real. A 50% faster build time isn't just a nice-to-have - it genuinely improves your daily workflow.

  4. The documentation is your friend. When things broke, the official Next.js error messages and docs pointed me in the right direction.

  5. Incremental upgrades prevent disasters. Jumping two major versions at once would have been a nightmare. Taking it one step at a time made debugging straightforward.

Next.js 16 isn't revolutionary - it's evolutionary. It's the framework maturing, getting faster, and becoming more reliable. And that's exactly what we need as developers.

So yes, I felt like a child who knows nothing when I started. But after working through the problems, I realized I wasn't lost - I was just learning.

And that's been the most valuable part of this upgrade.


Resources


This article is based on real upgrade experiences and best practices for Next.js development. Your mileage may vary depending on your specific setup and dependencies.

Top comments (0)