I Upgraded My 2023 Portfolio to Next.js 16 and Tailwind 4: Everything That Broke
Back in 2023, when AI was just starting to boom, building a developer portfolio from scratch was the thing to do. I was a junior developer, so I built one: a simple single-page site with my basic details. Now that I’m j
Back in 2023, when AI was just starting to boom, building a developer portfolio from scratch was the thing to do. I was a junior developer, so I built one: a simple single-page site with my basic details.
Now that I’m job hunting, I wanted to polish it and add a few things. Then npm install greeted me with 27 vulnerabilities, one of them critical. I figured npm audit fix would sort it out, but it only got me partway. The remaining warnings suggested --force, which I did, and my application produced a string of errors that were so complicated I decided to take help from AI, and its suggestions produced so many errors I had to re-clone my website from git and start from scratch.
Starting fresh taught me one thing in particular: npm audit fix --force was never going to fix this. It just pushes breaking changes through blindly. The real fix was a proper upgrade to Next.js 16 and Tailwind CSS 4, done step by step on a branch. Everything that broke along the way, and how I fixed it, is below, so you can skip the Googling.
Before you start
One thing my 5 years of Software Development career has taught me is: always have a backup. Any feature or upgrade to be done should have a copy in your branch. Any upgrade or new feature should happen on its own branch, so the working version stays untouched. Dependency upgrades across a framework and a CSS engine can break both the build and the design, and I’d already learned that the hard way with --force.
So I committed a snapshot first and did the whole upgrade on a separate branch:
git checkout -b upgrade-dep
git add -A && git commit -m "pre-upgrade snapshot"
Step 1: Understand npm audit before touching anything
My first mistake was treating npm audit as a to-do list to clear as fast as possible. The second time around, I read it first.
npm audit fix took me from 27 vulnerabilities down to 13. The rest wanted npm audit fix --force, which is exactly what broke my project the first time. It accepts breaking major-version changes automatically, and for one chain it would even have downgraded eslint-config-next, the wrong direction for a Next.js 16 upgrade.
So this time I asked a different question: does this vulnerability ship to my users? Most of what remained was build-time tooling (Tailwind v3’s dependencies and eslint-config-next), not code that runs in the browser. You can check that for yourself:
npm audit --omit=dev
The upgrades in the next steps cleared most of the list. The last 5 (a braces chain under eslint-config-next) had no patched release, so I left them alone. They only affect lint tooling on my machine, not the deployed site.
Step 2: Upgrade Next.js with the official codemod
I tried npx next upgrade first, but it didn’t work for me. A quick search, confirmed against the Next.js docs, pointed me to the codemod from the canary channel instead:
npx @next/codemod@canary upgrade latest
It bumps next, react, react-dom, and eslint-config-next, then offers optional codemods (middleware-to-proxy, remove-experimental-ppr, and so on). A quick grep told me my project used none of those features, so they were harmless.
Step 3: Upgrade Tailwind, and this is where it got interesting
npx @tailwindcss/upgrade
Problem 1: "yarn: not found"
The upgrader tried yarn add tailwindcss@latest, failed, and then crashed while migrating templates because it couldn't resolve Tailwind. It picks its package manager from lockfiles, and I had a stale yarn.lock in an npm project.
Fix: delete the stale lockfile and install the packages with npm yourself first:
npm i tailwindcss@latest @tailwindcss/postcss@latest postcss@latest
npx @tailwindcss/upgrade
Problem 2: "Cannot apply unknown utility class border-b-slate-700"
The upgrader reads @apply rules in your CSS and aborts if it can't generate one. My earlier half-migrated state caused it. To get past it, I temporarily replaced the @apply with plain CSS. That was a mistake I made worth sharing: I only copied the border color and forgot the width and padding.
Problem 3: postcss.config.js still pointed at the v3 plugin
In v4 the PostCSS plugin moved to its own package, and autoprefixer is built in:
// before
module.exports = {
plugins: { tailwindcss: {}, autoprefixer: {} },
};
// after
module.exports = {
plugins: { "@tailwindcss/postcss": {} },
};
And your CSS entry point changes from the three @tailwind directives to:
@import "tailwindcss";
Step 4: The build passed, but the design was broken
The colors and fonts survived. The spacing was gone: no top padding, no section dividers.
The cause was a custom .wrapper class that originally did this:
.wrapper {
@apply border-b-[1px] border-b-slate-700 py-20;
}
My workaround reduced it to:
.wrapper {
border-bottom-color: #334155;
}
A color with no border width and no padding draws nothing. Restoring it fixed every section at once:
@layer components {
.wrapper {
@apply border-b border-slate-700 py-20;
}
}
Tailwind 4 gotchas worth checking in your own project
-
Cascade layers: Utilities live in a CSS layer now, so an unlayered reset like
* { margin: 0; padding: 0 }beats everypy-*andmt-*. Put resets in@layer base. -
Default border color changed from gray-200 to
currentColor. -
Renamed utilities:
shadow-smbecomesshadow-xs,shadowbecomesshadow-sm,roundedbecomesrounded-sm,outline-nonebecomesoutline-hidden,ringbecomesring-3. -
Config moved to CSS. Custom colors in
tailwind.config.jsneed@configor a@themeblock. -
Old arbitrary-value syntax:
max-w-[theme(screens.xl)]still worked, butmax-w-screen-xlis the v4-friendly version.
Step 5: Deploy errors that didn't show up locally
Local npm run build passed. Vercel did not.
EOVERRIDE on npm install
npm error Override for @types/react@^19.3.0 conflicts with direct dependency
An overrides entry in package.json didn't match the version in devDependencies. Locally it worked because node_modules was already resolved, but Vercel installs fresh.
Fix: remove the override, or make it a reference:
"overrides": { "@types/react": "$@types/react" }
Lesson: before pushing a big upgrade, reproduce Vercel's environment:
rm -rf node_modules package-lock.json
npm install
npm run build
target=ES5 has been removed
After updating TypeScript, the build failed on my 2023 tsconfig.json:
error TS5108: Option 'target=ES5' has been removed.
Fix: set "target": "ES2017". Next compiles your code with its own toolchain, so this only affects type-checking.
Async server components flagged by TypeScript
'Articles' cannot be used as a JSX component.
Its return type 'Promise<Element | null>' is not a valid JSX element.
Old @types/react versions don't know about async server components. Updating typescript and @types/react fixed it.
Learn from me and do not do these mistakes
- Use a branch and a preview deployment.
-
Don't run
npm audit fix --forceblindly. Read what it wants to change. - Install packages yourself if the upgrader picks the wrong package manager.
- Check custom CSS classes after the Tailwind upgrade. That's where my spacing went.
-
Test a fresh install before pushing, because Vercel doesn't have your cached
node_modules.
The whole thing took my Sunday morning, and most of the errors were one-line fixes once I understood the cause.
Have you upgraded a Tailwind 3 project yet? What broke for you? Let me know in the comments.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.