Next.js 16 âm thầm phá app mình ở 4 chỗ không có lỗi nào cả
Upgrade lên Next.js 16 tưởng ngon, ai ngờ app chạy sai mà console sạch bóng. Đây là 4 chỗ mình bị dính và cách fix.
Nguyễn Nhật Long
@nguyennhatlong1303
Mình vừa trải qua một buổi chiều debugging mà không có gì để debug. Không có error, không có warning, không có stack trace app chỉ đơn giản là... chạy sai. Sau khi upgrade lên Next.js 16, mọi thứ trông ổn ở surface nhưng bên dưới thì đã bị thay đổi hoàn toàn.
Nếu bạn đang cân nhắc upgrade hoặc vừa upgrade xong và thấy behavior lạ, bài này dành cho bạn.
Caching mặc định đã bị đảo ngược hoàn toàn
Đây là cái đau nhất. Ở Next.js 13-15, fetch trong Server Components mặc định được cache tức là { cache: 'force-cache' }. Bạn phải chủ động opt-out bằng { cache: 'no-store' } nếu muốn data fresh.
Next.js 16 lật ngược lại hoàn toàn: mặc định là không cache.
1// Next.js 15 trở về trước: cái này được cache2const res = await fetch('https://api.example.com/products')34// Next.js 16: cái này KHÔNG được cache nữa5// Phải viết tường minh nếu muốn cache6const res = await fetch('https://api.example.com/products', {7 cache: 'force-cache'8})
Hậu quả thực tế: mình có một trang product listing, data load từ CMS. Sau khi upgrade, mỗi request đều hit API thật latency tăng, cost tăng, và tệ hơn là không có error nào để biết chuyện gì đang xảy ra. App vẫn chạy, data vẫn đúng, chỉ là chậm hơn và tốn tiền hơn.
Theo kinh nghiệm của mình, đây là breaking change nguy hiểm nhất vì nó không crash app nó chỉ thay đổi behavior theo cách mà bạn chỉ phát hiện khi nhìn vào bill hoặc performance metrics.
generateStaticParams bây giờ strict hơn nhiều
Nếu bạn dùng dynamic routes với generateStaticParams, cách Next.js 16 handle các params không được generate sẵn đã thay đổi.
Trước đây, nếu một route không có trong danh sách static params, Next.js sẽ fallback sang render on-demand. Bây giờ behavior này phụ thuộc vào config dynamicParams và default của nó cũng đã thay đổi trong một số trường hợp.
1// app/products/[id]/page.tsx2export async function generateStaticParams() {3 return [{ id: '1' }, { id: '2' }]4}56// Nếu user truy cập /products/3 behavior bây giờ khác rồi7// Không có error, chỉ là response có thể là 404 hoặc render tùy config8export const dynamicParams = true // phải explicit nếu muốn fallback
Mình bị dính cái này ở một trang blog có pagination. Các bài cũ không được pre-generate, trước đây chúng vẫn render được on-demand. Sau upgrade, một số route trả về 404 im lặng không log, không warning.
Turbopack đã thành default trong dev mode
Next.js 16 bật Turbopack làm bundler mặc định cho next dev. Nếu bạn có webpack config custom trong next.config.js, một số thứ sẽ không được pick up nữa.
Mình có setup @svgr/webpack để import SVG thành React component. Sau khi Turbopack lên ngôi, toàn bộ SVG import trong dev mode bị break nhưng production build (vẫn dùng webpack) thì ổn. Kết quả là dev và prod có behavior khác nhau, cực kỳ confusing.
| Tính năng | Webpack (cũ) | Turbopack (mới) |
|---|---|---|
| Custom loaders | ✅ Hoạt động | ⚠️ Một số chưa support |
| `webpack()` callback trong config | ✅ | ❌ Bị ignore |
| Module aliases | ✅ | ✅ (qua `resolveAlias`) |
| SVG as component | ✅ với loader | ⚠️ Cần config khác |
| Bundle analyzer | ✅ | ❌ Chưa có plugin tương đương |
Fix tạm thời là thêm flag --turbo=false vào script hoặc config tường minh:
1// package.json2"scripts": {3 "dev": "next dev --turbo=false"4}
Nhưng về lâu dài bạn nên migrate sang Turbopack config thật sự. Anh em lưu ý cái này đặc biệt nếu dự án có nhiều webpack customization.
Async cookies() và headers() breaking change không có error
Đây là cái tinh vi nhất. Trong Next.js 16, cookies() và headers() từ next/headers đã trở thành async functions. Nếu bạn gọi chúng synchronously như trước, code vẫn chạy nhưng bạn nhận được một Promise object thay vì giá trị thật.
1// Cách cũ vẫn không throw error trong Next.js 162import { cookies } from 'next/headers'34export default function Page() {5 const cookieStore = cookies() // Đây bây giờ là Promise!6 const token = cookieStore.get('token') // undefined không crash, chỉ sai7}89// Cách đúng10export default async function Page() {11 const cookieStore = await cookies()12 const token = cookieStore.get('token') // Đúng rồi13}
Mình mất gần 2 tiếng để tìm ra bug này trong một middleware auth flow. User không bị redirect, không có unauthorized error chỉ là token luôn là undefined nên logic auth cứ tưởng user chưa đăng nhập, rồi redirect về login page. Nhìn từ ngoài vào thì trông như là session bị expire.
Cái nguy hiểm là TypeScript vẫn không báo lỗi nếu bạn chưa update type definitions, và runtime cũng không throw JavaScript chỉ lặng lẽ gọi .get() trên một Promise object và trả về undefined.
Checklist trước khi upgrade
Sau trải nghiệm này, mình đã lập một checklist nhỏ cho bất kỳ ai đang cân nhắc lên Next.js 16:
Audit fetch calls: Tìm tất cả fetch trong Server Components và quyết định xem cái nào cần cache tường minh.
Kiểm tra generateStaticParams: Nếu bạn dựa vào fallback rendering cho dynamic routes, thêm export const dynamicParams = true tường minh.
Review webpack config: Liệt kê tất cả custom webpack config và kiểm tra Turbopack compatibility. Next.js có một trang docs riêng về Turbopack configuration.
Grep toàn bộ cookies() và headers(): Thêm await vào tất cả, biến component thành async nếu cần.
Mình thấy Next.js 16 có nhiều improvement thật sự tốt Turbopack nhanh hơn hẳn, một số API được clean up gọn hơn. Nhưng kiểu breaking change không có error này là thứ mình ghét nhất trong software engineering. Nó vi phạm nguyên tắc cơ bản: nếu code sai, hãy báo cho dev biết.
Nếu bạn đang chạy production với Next.js 14 hoặc 15 và app đang ổn định, mình sẽ không rush upgrade ngay. Đợi thêm vài patch release để ecosystem settle down, đọc kỹ migration guide, và test kỹ trên staging trước. Đặc biệt là test các auth flow và data fetching đó là hai khu vực bị ảnh hưởng nhiều nhất.
Nguyễn Nhật Long
@nguyennhatlong1303Nguyễn Nhật Long is a Senior Frontend Engineer and Frontend Team Leader with 7 years of experience building real-time fintech platforms. Specializing in React, Next.js, TypeScript, and React Native, shipping 10+ products across Web, Mobile, Telegram Mini-Apps, and Web3.
Thấy hay? Chia sẻ cho bạn bè!