Tại sao Next.js đổi tên middleware thành proxy?
Nếu bạn vừa nâng cấp lên Next.js 16 và thấy dòng warning "The middleware file convention is deprecated and has been renamed to proxy" xuất hiện trong console — đừng hoảng. Mình cũng từng giật mình lần đầu nhìn thấy nó. Đây là một trong những thay đổi breaking lớn nhất của Next.js 16, nhưng thật ra việc migration lại đơn giản hơn bạn nghĩ nhiều.
Từ phiên bản Next.js 12, middleware.ts luôn là nơi bạn xử lý request trước khi route được render — redirect, rewrite, kiểm tra auth, thêm header. Nhưng cái tên "middleware" đã gây hiểu nhầm cho rất nhiều developer, đặc biệt là những ai từng làm việc với Express.js.
Và điều đó hoàn toàn dễ hiểu.
Trong Express, middleware là một chuỗi các hàm xử lý tuần tự — bạn có thể có hàng chục middleware chạy lần lượt, mỗi cái thực hiện một nhiệm vụ riêng (parse body, validate, log, auth...). Middleware trong Next.js thì hoàn toàn khác: nó chỉ là một file duy nhất, chạy trên ranh giới mạng (network boundary) trước khi request đến ứng dụng — nói thẳng ra là giống một proxy hơn là middleware truyền thống.
Đội ngũ Vercel cuối cùng cũng nhận ra rằng cái tên "proxy" mô tả chính xác hơn những gì tính năng này thực sự làm: đứng trước ứng dụng, chặn và chuyển tiếp request. Và thế là proxy.ts ra đời trong Next.js 16.
Những gì thay đổi trong Next.js 16
Đổi tên file và hàm export
Thay đổi cốt lõi cực kỳ đơn giản: file middleware.ts được đổi tên thành proxy.ts, và hàm export middleware được đổi thành proxy. Toàn bộ logic bên trong vẫn giữ nguyên, không cần sửa gì thêm.
// CŨ: middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
export const config = {
matcher: '/about/:path*',
}
// MỚI: proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
export const config = {
matcher: '/about/:path*',
}
Nhìn hai đoạn code trên, bạn sẽ thấy sự khác biệt duy nhất chỉ là tên file và tên hàm. Đơn giản vậy thôi.
Runtime mặc định chuyển sang Node.js
Đây mới là thay đổi thực sự đáng chú ý: proxy.ts chạy trên Node.js runtime mặc định, không phải Edge Runtime như middleware.ts trước đây. Và bạn không thể cấu hình runtime cho proxy — nếu cố đặt export const runtime = 'edge' trong proxy.ts, Next.js sẽ throw error ngay.
Chuyện này có hai mặt:
- Ưu điểm: Bạn có quyền truy cập đầy đủ Node.js API —
fs,crypto,path, và tất cả npm package mà trước đây không tương thích với Edge Runtime. Cái này thật sự rất tiện. - Nhược điểm: Proxy không còn chạy trên CDN edge gần người dùng nữa. Nếu bạn cần Edge Runtime, hãy tiếp tục dùng
middleware.ts(vẫn hoạt động nhưng đã deprecated).
middleware.ts vẫn hoạt động (tạm thời)
Next.js 16 không xóa middleware.ts ngay lập tức. File cũ vẫn hoạt động bình thường, nhưng bạn sẽ thấy deprecation warning mỗi lần chạy dev server. Trong một phiên bản tương lai, middleware.ts sẽ bị loại bỏ hoàn toàn — nên đừng trì hoãn quá lâu nhé.
Hướng dẫn migration từng bước
Bước 1: Sử dụng codemod tự động
Next.js cung cấp codemod giúp tự động đổi tên file và hàm. Đây là cách nhanh nhất và mình khuyên bạn nên thử cách này trước:
npx @next/codemod@canary middleware-to-proxy .
Codemod sẽ thực hiện hai việc: đổi tên file middleware.ts thành proxy.ts, và đổi tên hàm middleware thành proxy trong code. Nhanh gọn, ít rủi ro.
Bước 2: Migration thủ công (nếu cần)
Nếu codemod không hoạt động đúng với project của bạn (đôi khi nó hơi kén cấu trúc thư mục), bạn hoàn toàn có thể làm thủ công. Chỉ cần hai bước:
# Đổi tên file
mv middleware.ts proxy.ts
# hoặc nếu dùng JavaScript
mv middleware.js proxy.js
Sau đó mở file và đổi tên hàm export:
// Đổi từ
export function middleware(request: NextRequest) { ... }
// Thành
export function proxy(request: NextRequest) { ... }
// Hoặc nếu dùng default export
export default function proxy(request: NextRequest) { ... }
Bước 3: Kiểm tra và refactor (nếu cần)
Nếu middleware.ts cũ của bạn chỉ làm những việc đơn giản — redirect, rewrite, kiểm tra cookie — thì migration xong, bạn không cần thay đổi logic gì cả. Nhưng nếu bạn đã nhồi nhét quá nhiều thứ vào middleware (database queries, xác thực phức tạp, caching), thì thành thật mà nói, đây là lúc tốt nhất để refactor.
Triết lý của Next.js 16 rất rõ ràng: proxy chỉ nên xử lý routing-level logic. Những thứ nặng nề hơn nên chuyển sang Server Components, Server Actions, hoặc Route Handlers.
Cấu trúc file proxy.ts
Vị trí file
File proxy.ts phải đặt ở thư mục gốc của project, cùng cấp với app/ hoặc pages/. Nếu bạn dùng thư mục src/, đặt nó trong src/proxy.ts.
# Cấu trúc thư mục
my-nextjs-app/
├── proxy.ts # File proxy chính
├── app/
│ ├── layout.tsx
│ └── page.tsx
├── lib/
│ └── proxy/ # Module phụ cho proxy logic
│ ├── auth.ts
│ ├── rate-limit.ts
│ └── i18n.ts
└── next.config.ts
Một điều cần nhớ: chỉ hỗ trợ một file proxy.ts duy nhất cho mỗi project. Nhưng đừng lo, bạn hoàn toàn có thể tách logic thành các module riêng rồi import vào file chính — mình sẽ chỉ cách ở phần sau.
Matcher — kiểm soát proxy chạy ở đâu
Mặc định, proxy chạy trên mọi route trong project. Đây là điều bạn hầu như không bao giờ muốn. Dùng matcher để giới hạn proxy chỉ chạy trên những path cần thiết, giúp tối ưu hiệu suất đáng kể:
// proxy.ts
export const config = {
matcher: [
// Chỉ chạy proxy trên các route cần bảo vệ
'/dashboard/:path*',
'/api/protected/:path*',
'/admin/:path*',
],
}
Bạn cũng có thể dùng negative lookahead để loại trừ các path không cần proxy:
export const config = {
matcher: [
// Chạy trên tất cả, TRỪ static files, API routes, và metadata files
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
Matcher nâng cao với has và missing
Matcher không chỉ dựa trên path — bạn còn có thể kiểm tra sự có mặt hoặc vắng mặt của headers, cookies, query params. Khá mạnh mẽ:
export const config = {
matcher: [
{
source: '/api/:path*',
has: [
{ type: 'header', key: 'Authorization', value: 'Bearer Token' },
],
missing: [
{ type: 'cookie', key: 'session', value: 'active' },
],
},
],
}
Các pattern thực tế với proxy.ts
Okay, phần lý thuyết thế là đủ rồi. Giờ đến phần mình thích nhất — các pattern thực tế mà bạn có thể copy về dùng ngay.
Pattern 1: Xác thực và bảo vệ route
Đây là use case phổ biến nhất khi dùng proxy. Ý tưởng rất đơn giản: kiểm tra session cookie và redirect người dùng chưa đăng nhập về trang login.
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const protectedRoutes = ['/dashboard', '/profile', '/settings']
const publicRoutes = ['/login', '/register', '/forgot-password']
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl
const sessionToken = request.cookies.get('session-token')
// Kiểm tra nếu đang truy cập route được bảo vệ
const isProtected = protectedRoutes.some(
(route) => pathname.startsWith(route)
)
// Redirect về login nếu chưa đăng nhập
if (isProtected && !sessionToken) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('callbackUrl', pathname)
return NextResponse.redirect(loginUrl)
}
// Redirect về dashboard nếu đã đăng nhập mà truy cập trang login
const isPublic = publicRoutes.some(
(route) => pathname.startsWith(route)
)
if (isPublic && sessionToken) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: [
'/dashboard/:path*',
'/profile/:path*',
'/settings/:path*',
'/login',
'/register',
'/forgot-password',
],
}
Lưu ý quan trọng: Proxy chỉ nên thực hiện kiểm tra optimistic — xác nhận cookie tồn tại, chứ không nên verify JWT signature hay query database ở đây. Xác thực chi tiết nên được thực hiện lại ở Data Access Layer (Server Components hoặc Server Actions) để đảm bảo defense-in-depth. Mình từng mắc sai lầm này khi mới bắt đầu, đừng lặp lại nhé.
Pattern 2: Cấu hình CORS
Nếu API của bạn cần cho phép cross-origin requests, proxy là nơi lý tưởng để xử lý CORS headers:
// lib/proxy/cors.ts
import { NextRequest, NextResponse } from 'next/server'
const allowedOrigins = [
'https://app.example.com',
'https://admin.example.com',
]
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function handleCORS(request: NextRequest) {
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Xử lý preflight request (OPTIONS)
if (request.method === 'OPTIONS') {
const preflightHeaders = {
...(isAllowedOrigin && {
'Access-Control-Allow-Origin': origin,
}),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Xử lý request thông thường
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
Pattern 3: Rate limiting với Upstash Redis
Giới hạn số lượng request từ cùng một IP để bảo vệ API khỏi bị lạm dụng — đây là thứ mà hầu như mọi production app đều cần. Pattern này kết hợp @upstash/ratelimit với Redis:
// lib/proxy/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
import { NextRequest, NextResponse } from 'next/server'
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 request / 10 giây
analytics: true,
})
export async function handleRateLimit(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1'
const { success, limit, reset, remaining } = await ratelimit.limit(ip)
if (!success) {
return NextResponse.json(
{ error: 'Quá nhiều request. Vui lòng thử lại sau.' },
{
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': reset.toString(),
},
}
)
}
return null // Cho phép request tiếp tục
}
Pattern 4: A/B Testing với cookie
Phân chia người dùng vào các nhóm thử nghiệm mà không cần JavaScript phía client. Pattern này khá hay vì user hoàn toàn không biết họ đang trong experiment:
// lib/proxy/ab-test.ts
import { NextRequest, NextResponse } from 'next/server'
export function handleABTest(request: NextRequest) {
const { pathname } = request.nextUrl
// Chỉ áp dụng cho trang chủ
if (pathname !== '/') return null
// Kiểm tra xem user đã được assign variant chưa
let variant = request.cookies.get('ab-homepage')?.value
if (!variant) {
// Random assign variant mới
variant = Math.random() < 0.5 ? 'control' : 'experiment'
}
// Rewrite sang variant tương ứng
const url = request.nextUrl.clone()
url.pathname = variant === 'experiment'
? '/experiments/homepage-v2'
: '/'
const response = NextResponse.rewrite(url)
// Lưu variant vào cookie để giữ nhất quán
if (!request.cookies.get('ab-homepage')) {
response.cookies.set('ab-homepage', variant, {
maxAge: 60 * 60 * 24 * 30, // 30 ngày
httpOnly: true,
})
}
return response
}
Pattern 5: Redirect đa ngôn ngữ (i18n)
Tự động chuyển hướng người dùng sang phiên bản ngôn ngữ phù hợp dựa trên header Accept-Language. Nếu app của bạn hỗ trợ nhiều ngôn ngữ, đây là pattern gần như bắt buộc:
// lib/proxy/i18n.ts
import { NextRequest, NextResponse } from 'next/server'
const supportedLocales = ['vi', 'en', 'ja', 'ko']
const defaultLocale = 'vi'
function getPreferredLocale(request: NextRequest): string {
const acceptLanguage = request.headers.get('accept-language')
if (!acceptLanguage) return defaultLocale
// Parse Accept-Language header
const languages = acceptLanguage
.split(',')
.map((lang) => {
const [code, quality] = lang.trim().split(';q=')
return {
code: code.split('-')[0], // 'en-US' -> 'en'
quality: quality ? parseFloat(quality) : 1,
}
})
.sort((a, b) => b.quality - a.quality)
// Tìm locale được hỗ trợ đầu tiên
const matched = languages.find((lang) =>
supportedLocales.includes(lang.code)
)
return matched?.code ?? defaultLocale
}
export function handleI18n(request: NextRequest) {
const { pathname } = request.nextUrl
// Kiểm tra xem path đã có locale prefix chưa
const hasLocale = supportedLocales.some(
(locale) =>
pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)
if (hasLocale) return null
// Kiểm tra cookie preference trước
const cookieLocale = request.cookies.get('preferred-locale')?.value
const locale = cookieLocale ?? getPreferredLocale(request)
// Redirect sang path có locale prefix
return NextResponse.redirect(
new URL(`/${locale}${pathname}`, request.url)
)
}
Kết hợp nhiều pattern trong một file proxy.ts
Trong thực tế, bạn sẽ cần kết hợp nhiều pattern cùng lúc. Đây là cách mình thường tổ chức — tách logic vào các module riêng rồi gọi tuần tự trong file proxy chính:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { handleCORS } from './lib/proxy/cors'
import { handleRateLimit } from './lib/proxy/rate-limit'
import { handleI18n } from './lib/proxy/i18n'
import { handleABTest } from './lib/proxy/ab-test'
export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl
// 1. CORS — xử lý cho API routes
if (pathname.startsWith('/api/')) {
const corsResponse = handleCORS(request)
if (corsResponse) return corsResponse
// Rate limiting cho API
const rateLimitResponse = await handleRateLimit(request)
if (rateLimitResponse) return rateLimitResponse
}
// 2. i18n — redirect đa ngôn ngữ
const i18nResponse = handleI18n(request)
if (i18nResponse) return i18nResponse
// 3. Auth — kiểm tra session cho protected routes
if (pathname.startsWith('/dashboard') || pathname.startsWith('/admin')) {
const session = request.cookies.get('session-token')
if (!session) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
// 4. A/B Testing
const abResponse = handleABTest(request)
if (abResponse) return abResponse
return NextResponse.next()
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
Cách tổ chức này giúp file proxy chính dễ đọc và mỗi concern được tách biệt rõ ràng. Khi cần thêm logic mới, bạn chỉ việc tạo module mới trong lib/proxy/ và import vào.
Làm việc với Cookies và Headers
Đọc và ghi cookies
API cookies trong NextRequest và NextResponse khá trực quan. Request cookies hỗ trợ các phương thức get, getAll, set, delete, has, và clear:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
// Đọc cookie từ request
const theme = request.cookies.get('theme')
console.log(theme) // { name: 'theme', value: 'dark', Path: '/' }
// Kiểm tra cookie tồn tại
const hasSession = request.cookies.has('session-token')
// Tạo response và set cookie
const response = NextResponse.next()
response.cookies.set('visited', 'true', {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 365, // 1 năm
})
return response
}
Truyền dữ liệu qua request headers
Một pattern cực kỳ hữu ích mà không phải ai cũng biết: truyền thông tin từ proxy sang Server Components qua custom request headers. Ví dụ, bạn có thể detect device type hoặc geo-location ngay trong proxy rồi truyền xuống:
// proxy.ts
export function proxy(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
// Thêm thông tin geo-location
const country = request.geo?.country ?? 'VN'
requestHeaders.set('x-user-country', country)
// Thêm thông tin device type
const userAgent = request.headers.get('user-agent') ?? ''
const isMobile = /Mobile|Android/i.test(userAgent)
requestHeaders.set('x-device-type', isMobile ? 'mobile' : 'desktop')
return NextResponse.next({
request: {
headers: requestHeaders,
},
})
}
// app/page.tsx — Đọc custom headers trong Server Component
import { headers } from 'next/headers'
export default async function HomePage() {
const headersList = await headers()
const country = headersList.get('x-user-country')
const deviceType = headersList.get('x-device-type')
return (
<div>
<p>Quốc gia: {country}</p>
<p>Thiết bị: {deviceType}</p>
</div>
)
}
Unit testing proxy (experimental)
Từ Next.js 15.1, bạn có thể test proxy function bằng các tiện ích từ next/experimental/testing/server. Tuy vẫn đang ở giai đoạn experimental nhưng hoạt động khá ổn định. Đây là cách kiểm tra proxy chạy đúng trên các path mong muốn:
// __tests__/proxy.test.ts
import { unstable_doesProxyMatch } from 'next/experimental/testing/server'
import { config, proxy } from '../proxy'
import { NextRequest } from 'next/server'
import {
isRewrite,
getRewrittenUrl,
getRedirectUrl,
} from 'next/experimental/testing/server'
describe('Proxy matcher', () => {
it('chạy trên /dashboard', () => {
expect(
unstable_doesProxyMatch({
config,
url: '/dashboard',
})
).toBe(true)
})
it('không chạy trên /_next/static', () => {
expect(
unstable_doesProxyMatch({
config,
url: '/_next/static/chunk.js',
})
).toBe(false)
})
})
describe('Proxy function', () => {
it('redirect về /login khi chưa đăng nhập', async () => {
const request = new NextRequest(
'https://example.com/dashboard'
)
const response = await proxy(request)
expect(response?.status).toBe(307)
expect(getRedirectUrl(response)).toContain('/login')
})
})
Viết test cho proxy không phải lúc nào cũng cần thiết cho dự án nhỏ, nhưng nếu proxy logic bắt đầu phức tạp (nhiều điều kiện redirect, rate limiting, i18n), thì test sẽ giúp bạn tự tin hơn rất nhiều khi refactor.
Lưu ý quan trọng khi dùng proxy.ts
Những việc proxy NÊN làm
- Redirect và rewrite URL
- Kiểm tra optimistic auth (cookie tồn tại hay không)
- Thêm/sửa headers cho request và response
- CORS configuration
- A/B testing bằng rewrite
- Rate limiting nhẹ
- Redirect đa ngôn ngữ
Những việc proxy KHÔNG NÊN làm
- Query database trực tiếp
- Verify JWT signature phức tạp
- Fetch dữ liệu chậm từ API bên ngoài
- Xử lý business logic nặng
- Quản lý session đầy đủ
Nếu bạn đang làm những việc trên trong middleware cũ, đây là cơ hội tốt để refactor chúng sang Server Components, Server Actions, hoặc Route Handlers. Proxy nên nhẹ và nhanh — đó là nguyên tắc vàng.
Tránh vòng lặp redirect vô hạn
Đây là lỗi mà mình thấy rất nhiều người mắc phải, kể cả dev có kinh nghiệm. Quên kiểm tra xem request đã ở trang đích chưa trước khi redirect:
// SAI — vòng lặp vô hạn
export function proxy(request: NextRequest) {
if (!request.cookies.get('session')) {
return NextResponse.redirect(new URL('/login', request.url))
// /login cũng trigger proxy -> redirect /login -> lặp!
}
}
// ĐÚNG — kiểm tra path hiện tại
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl
if (pathname === '/login') return NextResponse.next()
if (!request.cookies.get('session')) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
Mẹo nhỏ: luôn kiểm tra pathname trước khi redirect. Hoặc tốt hơn, dùng matcher để loại trừ các route không cần proxy ngay từ đầu.
Lưu ý với OpenNext / Self-hosting
Nếu bạn đang self-host Next.js với OpenNext, hãy kiểm tra tính tương thích trước khi migrate. Tại thời điểm viết bài (tháng 3/2026), OpenNext chưa hỗ trợ proxy.ts — bạn nên tiếp tục dùng middleware.ts cho đến khi có bản cập nhật.
Thứ tự thực thi trong Next.js 16
Hiểu rõ thứ tự thực thi sẽ giúp bạn debug nhanh hơn. Khi một request đến ứng dụng Next.js, nó đi qua các bước sau:
headerstừnext.config.jsredirectstừnext.config.js- Proxy (rewrites, redirects, v.v.)
beforeFilesrewrites từnext.config.js- Filesystem routes (
public/,_next/static/,pages/,app/) afterFilesrewrites từnext.config.js- Dynamic Routes (
/blog/[slug]) fallbackrewrites từnext.config.js
Điểm mấu chốt: proxy chạy sau các redirect/header tĩnh trong config nhưng trước bất kỳ route nào được render. Nghĩa là redirect trong next.config.js sẽ được xử lý trước proxy, nên bạn không cần xử lý lại chúng trong proxy.
Câu hỏi thường gặp
Tôi có bắt buộc phải chuyển từ middleware.ts sang proxy.ts ngay không?
Chưa bắt buộc ngay đâu. File middleware.ts vẫn hoạt động trong Next.js 16, nhưng đã deprecated và bạn sẽ thấy warning trong console. Nên chuyển đổi sớm vì middleware.ts sẽ bị loại bỏ hoàn toàn trong phiên bản tương lai.
proxy.ts có hỗ trợ Edge Runtime không?
Không. Trong Next.js 16, proxy.ts chỉ chạy trên Node.js runtime và không thể cấu hình runtime khác. Nếu bạn nhất định cần Edge Runtime, hãy tiếp tục sử dụng middleware.ts (deprecated) cho đến khi có giải pháp thay thế từ Vercel.
Sự khác biệt giữa redirect trong next.config.js và trong proxy.ts là gì?
Redirect trong next.config.js là tĩnh — được xác định lúc build và chạy trước proxy. Redirect trong proxy.ts là động, bạn có thể dựa vào cookies, headers, query params, hoặc bất kỳ thông tin nào từ request để quyết định. Nguyên tắc đơn giản: dùng next.config.js cho redirect cố định, dùng proxy cho redirect có điều kiện.
Có thể dùng fetch trong proxy.ts không?
Có, nhưng hãy cẩn thận. Các tùy chọn caching của fetch như options.cache, options.next.revalidate, hoặc options.next.tags không có tác dụng trong proxy. Và tuyệt đối tránh gọi API chậm trong proxy — nó sẽ làm chậm toàn bộ request, ảnh hưởng trực tiếp đến trải nghiệm người dùng.
Làm sao để debug proxy.ts hiệu quả?
Cách đơn giản nhất: dùng console.log — output sẽ hiện trong terminal server (không phải browser console nhé). Trong Next.js 16, development logs được cải thiện đáng kể với thời gian chia thành Compile và Render. Bạn cũng có thể dùng waitUntil để gửi log đến dịch vụ analytics mà không block response, hoặc viết unit test với tiện ích từ next/experimental/testing/server như mình đã chỉ ở phần trên.