- 26th May, 2025
- Rinkal J.
3rd Aug, 2026 | Shubham G.

This document captures the complete upgrade of a Frontend App from Next.js 12 to Next.js 16, including all breaking changes encountered, migration strategies employed, and lessons learned during the process.
Status: Accepted and Completed
We needed to upgrade our Frontend App from Next.js 12 to Next.js 16. This upgrade involved:
Two approaches were considered:
Upgrade Next.js incrementally (12→13→14→15→16), fixing breaking changes at each step.
Jump directly to target version, then fix broken modules and breaking changes one by one
We chose the module-by-module approach - upgrading directly to Next.js 16 and fixing each broken module systematically.
Why we rejected version-by-version:
Why module-by-module worked:
Positive:
Negative:
Mitigations applied:
Added the overrides section in package.json to handle React 19, Next.js 16, and Node 22 compatibility, This prevents errors during npm install.
{
"engines": {
"npm": ">=6.0.0",
"node": ">=20.9.0"
},
"overrides": {
"react-html-parser": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"@recogito/annotorious-openseadragon": {
"openseadragon": "^5.0.1"
}
}
}
tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@src/*": ["src/*"],
"@public/*": ["public/*"],
"@interfaces/*": ["src/interfaces/*"],
"@config": ["src/config"],
"@config/*": ["src/config/*"]
}
}
}
next.config.js:
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
'@src': path.resolve(__dirname, 'src'),
'@public': path.resolve(__dirname, 'public'),
'@interfaces': path.resolve(__dirname, 'interfaces'),
'@config': path.resolve(__dirname, 'config')
};
return config;
}
npx sass-migrator division **/*.scss
Reference: https://sass-lang.com/documentation/cli/migrator/
| Change | Details | | --- | --- | | next/link | Before: 'textAfter: text No need for nested tag | | Imports | Before: import { useRouter } from next/router'After: import { useRouter, usePathname, useSearchParams } from 'next/navigation` |
| Change | Details | | --- | --- | | Node.js minimum | 20.9.0 - Node 18 is no longer supported | | TypeScript minimum | 5.1+ | | Default bundler | Turbopack becomes the default bundler | | Middleware API | The old middleware.ts API is deprecated/replaced by proxy.ts as the primary network boundary file |
Created route groups as per Next.js Route Groups documentation:
Layouts:
Route Groups:
App Router Structure
File: src/app/error.tsx
File: src/app/not-found.tsx
Problem: Appointment booking had the same route but different UI for:
Solution: Separated the appointment booking page into 2 different pages:
Once all breaking changes were resolved, started picking up packages one by one to ensure compatibility with:
Problem: Two different versions of MUI were installed
Task: Remove older version and upgrade newer version to latest MUI
Migration Strategy:
Breaking Changes:
| Change | Before | After | | --- | --- | --- | | @mui/styles | Supported | Removed in v7 - not supported in v6+ and fully removed in v7 | | Styling approach | makeStyles, withStyles | Migrated to styled from @mui/material/styles |
Current MUI Packages:
{
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.5",
"@mui/material": "^7.3.5",
"@mui/x-tree-view": "^8.19.0"
}
Problem: toasted-notes package stopped working with newer Next.js version
Solution:
Before (toasted-notes):
toaster.notify(({ onClose }) => (
<ErrorToaster onClose={onClose} message="Message string" />
), {
duration: 3000,
position: "top-right",
});
After (react-hot-toast with custom wrapper):
ShowErrorToaster({
message: "Message string",
duration: 3000,
position: "top-right",
});
Implementation File: src/components/ui/ErrorToaster.tsx
We used Cursor to create a new file that listed all type issues in the code and Group similar type issues together Where we have mentioned file paths and line number with the issues.
Provided the grouped issues list as context to Cursor to fix in all files with the same issue and included expected solution when sure.
This approach allowed fixing multiple files with the same pattern simultaneously.
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noImplicitAny": false
}
}
The old middleware.ts API is deprecated/replaced by proxy.ts as the primary network boundary file.
Business logic is based on older _app.tsx file
File: src/proxy.ts
Key features:
For best results, provide an example on how to fix the issues:
Example Prompt:
Remove all usages of toaster.notify in the frontend codebase.
Replace them with ShowErrorToaster imported from @src/ui/toaster.
**Replacement Rule:**
Any usage of:
```jsx
toaster.notify(({ onClose }) => (
<ErrorToaster onClose={onClose} message="Message string" />
), {
duration: 3000,
position: "top-right",
});
Must be replaced with:
```jsx
ShowErrorToaster({
message: "Message string",
duration: 3000,
position: "top-right",
});
| Package | Old | New | | --- | --- | --- | | @dnd-kit/core | ^6.1.0 | ^6.3.1 | | @dnd-kit/sortable | ^8.0.0 | ^10.0.0 | | @emotion/react | ^11.9.0 | ^11.14.0 | | @emotion/styled | ^11.8.1 | ^11.14.1 | | @mui/icons-material | ^5.6.1 | ^7.3.5 | | @mui/material | ^5.6.1 | ^7.3.5 | | @monaco-editor/react | ^4.6.0 | ^4.7.0 | | @tanstack/react-query | — | v5.90.11 | | @tinymce/tinymce-react | ^4.2.0 | ^6.3.0 | | ag-grid (community/enterprise/react) | 32.0.2 | ^33.3.2 | | axios | ^0.19.2 | ^1.13.2 | | react-bootstrap | ^1.4.3 | ^2.10.10 | | react-hot-toast | — | v2.6.0 | | react-html-parser | ^2.0.2 | ^5.2.10 | | react-markdown | ^6.0.3 | ^10.1.0 | | react-mentions | ^4.2.0 | ^4.4.10 | | react-modal | ^3.11.2 | ^3.16.3 | | react-pdf | ^5.7.2 | ^10.2.0 | | react-select | ^3.2.0 | ^5.10.2 | | react-select-async-paginate | ^0.5.3 | ^0.7.11 | | react-timezone-select | ^2.1.5 | ^3.2.8 | | react-tooltip | ^4.2.13 | ^5.30.0 | | reactflow | ^11.11.3 | ^11.11.4 | | recharts | ^2.0.8 | ^3.5.1 | | styled-components | ^5.2.3 | ^6.1.19 | | yup | ^0.27.0 | ^1.7.1 |
Get insights on the latest trends in technology and industry, delivered straight to your inbox.