👋 Chào mừng đến với Blog - Thành Nam Nguyễn

NextJS 16 chính thức ra mắt - Tính năng mới và cách sử dụng

NextJS 16 chính thức ra mắt - Tính năng mới và cách sử dụng

Framework

🌟 Giới thiệu

Nếu bạn là một dev React, có lẽ bạn đã nghe tin: NextJS 16 đã chính thức ra mắt.

Phiên bản này không chỉ nâng cấp hiệu năng mà còn giới thiệu hàng loạt công nghệ nền tảng mới — từ React Compiler giúp tối ưu hóa code tự động, đến Partial Prerendering (PPR) cho phép kết hợp giữa static và dynamic rendering, mang đến tốc độ vượt trội mà vẫn giữ được sự linh hoạt.

⚙️ React Compiler

React Compiler – tự động tối ưu hiệu suất mà không cần “memo” thủ công

NextJS 16 nay đã hỗ trợ ổn định React Compiler, một bước tiến quan trọng sau khi React Compiler ra mắt phiên bản 1.0. Đây là công cụ giúp React tự động tối ưu re-render — nghĩa là bạn không còn phải dùng React.memo, useMemo, hay useCallback thủ công để tăng hiệu suất nữa.

React Compiler sẽ phân tích mã nguồn component, xác định phần nào có thể được “memo hóa” an toàn và thực hiện tự động.

👉 Kết quả: hiệu suất được cải thiện đáng kể, đặc biệt trong những app có nhiều component con hoặc dữ liệu phức tạp.

Cách bật React Compiler trong NextJS 16

Thêm dòng cấu hình sau vào next.config.ts:

// next.config.ts
const nextConfig = {
  reactCompiler: true,
};

export default nextConfig;

Cài thêm plugin cần thiết:

npm install babel-plugin-react-compiler@latest

Trước khi dùng React Compiler: Bạn thường phải memo hóa thủ công để tránh render lại không cần thiết.

import { useMemo, useCallback, memo } from 'react';

const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
  const processedData = useMemo(() => {
    return expensiveProcessing(data);
  }, [data]);

  const handleClick = useCallback((item) => {
    onClick(item.id);
  }, [onClick]);

  return (
    <div>
      {processedData.map(item => (
        <Item key={item.id} onClick={() => handleClick(item)} />
      ))}
    </div>
  );
});

Ở đây, chúng ta phải dùng memo, useMemo, và useCallback để tránh component render lại mỗi khi data hoặc onClick thay đổi.

Sau khi bật React Compiler: Toàn bộ logic tối ưu này được React tự động xử lý — code trở nên gọn hơn rất nhiều.

function ExpensiveComponent({ data, onClick }) {
  const processedData = expensiveProcessing(data);

  const handleClick = (item) => {
    onClick(item.id);
  };

  return (
    <div>
      {processedData.map(item => (
        <Item key={item.id} onClick={() => handleClick(item)} />
      ))}
    </div>
  );
}

Giờ bạn chỉ cần tập trung viết logic chính. React Compiler sẽ lo việc ghi nhớ (memoize) kết quả tính toán và tránh render thừa — cho hiệu suất cao mà không cần can thiệp thủ công.

⚡ Partial Prerendering (PPR) & Cache Components

Khái niệm

  • Với PPR, bạn có thể kết hợp nội dung tĩnh và động trong cùng một route: phần “vỏ” của trang được prerender (hoặc cache) và gửi ngay, sau đó các phần động được load hoặc streaming vào.

  • Tính năng Cache Components cho phép bạn đánh dấu những phần dữ liệu hoặc UI có thể được cache — từ đó đưa chúng vào prerender pass cùng với các phần tĩnh của trang.

  • Khi bật, NextJS sẽ xem tất cả route dưới dạng dynamic mặc định, nhưng nhờ Cache Components thì bạn vẫn có thể chọn cache các thành phần để đẩy phần tĩnh ra sớm, phần động xử lý sau.

Partially re-rendered Product Page showing static nav and product information, and dynamic cart and recommended products

Cách hoạt động & lợi ích

  • Khi người dùng truy cập một route: server trả nhanh phần shell (vỏ) có nội dung được cache/prerender; các phần động – thường được đặt trong Suspense hoặc tương tự – trả fallback trước rồi bị thay thế khi dữ liệu/dynamic component sẵn sàng.

  • Kết quả là: thời gian tới first byte (TTFB) ngắn hơn; người dùng nhìn thấy nhanh; phần động vẫn có thể cá nhân hóa hoặc cập nhật real-time khi cần.

  • Ví dụ điển hình: trang sản phẩm có nav + thông tin sản phẩm (tĩnh) và giỏ hàng + đề xuất sản phẩm (động). Phần tĩnh được prerender, phần động sẽ streaming.

Cách sử dụng cơ bản

  • Trong file route/layout/component bạn có thể sử dụng directive 'use cache' ở đầu file hoặc component để bật caching cho phần đó.

      // example.tsx
      'use cache';
      export default async function CachedSection() {
        const data = await fetch(...); // dữ liệu có thể cache
        return <div>{data.someField}</div>;
      }
    
  • Cấu hình trong next.config.js nếu cần bật các tính năng thử nghiệm như PPR hoặc use-cache:

      // next.config.js
      module.exports = {
        experimental: {
          ppr: true,
          useCache: true,
        },
      };
    
  • Sử dụng các API fetch với options như next: { revalidate: … } để kiểm soát cập nhật cache.

Những lưu ý

  • PPR hiện vẫn được gắn mác experimental (tuỳ phiên bản) nên chưa khuyến khích sử dụng rộng rãi trong production.

  • Khi dùng caching: cần cân nhắc tính tươi mới (fresh data) của dữ liệu, vì cache lâu quá có thể dẫn tới hiển thị dữ liệu lỗi thời. Revalidation (định thời hoặc theo tag) luôn là phần quan trọng.

  • Khi dùng directive 'use cache', các hàm/trả về của chúng phải có tính tham chiếu rõ ràng (các tham số serializable) để key cache được xác định đúng.

Ví dụ: Giả sử bạn có trang sản phẩm kiểu e-commerce

// app/product/[id]/page.tsx
import { Suspense } from 'react';

export default async function ProductPage({ params }) {
  const { id } = params;

  // Thành phần tĩnh / có thể cache
  const product = await fetch(`/api/product/${id}`, { next: { revalidate: 3600 } });

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>

      {/* Thành phần động: giỏ hàng, öneri sản phẩm */}
      <Suspense fallback={<div>Loading cart & recommendations…</div>}>
        <Cart userId={/* từ cookie */} />
        <Recommendations productId={id} />
      </Suspense>
    </div>
  );
}

Ở đây: phần name/description được cache/prerender; phần CartRecommendations được render động sau, nhờ đó người dùng nhanh nhìn thấy nội dung chính, phần phụ tải sau.

🧠 NextJS DevTools MCP

Một điểm mới thú vị trong NextJS 16NextJS DevTools MCP — tích hợp theo chuẩn Model Context Protocol (MCP), giúp AI có thể hiểu rõ ngữ cảnh ứng dụng của bạn khi debug.

Thay vì phải tự lần mò qua log hay stack trace, DevTools MCP cho phép AI agent (như ChatGPT hoặc công cụ tương tự) truy cập trực tiếp vào thông tin nội bộ của ứng dụng đang chạy để phân tích và hỗ trợ gợi ý fix lỗi thông minh hơn.

🔍 AI DevTools hiểu được những gì?

  • Kiến thức về NextJS: nắm rõ cách routing, caching và rendering hoạt động.

  • Log hợp nhất: tự động thu thập cả log từ trình duyệt và server — không cần chuyển tab.

  • Truy cập lỗi tự động: lấy chi tiết stack trace và error context mà bạn không cần copy thủ công.

  • Hiểu ngữ cảnh trang: biết bạn đang ở route nào, giúp phản hồi chính xác và liên quan hơn.

Bạn có thể xem thêm chi tiết trong tài liệu tại NextJS DevTools MCP docs.

🔀 proxy.ts

proxy.ts thay thế cho middleware.ts, rõ ràng và thống nhất hơn

Trong NextJS 16, file proxy.ts chính thức thay thế cho middleware.ts — nhằm làm rõ ranh giới mạng (network boundary) của ứng dụng và đảm bảo tính nhất quán khi xử lý request.

Trước đây, middleware.ts có thể chạy trên nhiều runtime khác nhau (Edge hoặc Node.js), dẫn đến khó dự đoán hành vi và môi trường chạy. Giờ đây, proxy.ts luôn chạy trên Node.js runtime, giúp developer dễ hiểu và debug hơn.

💡 Cần làm gì khi nâng cấp

  • Đổi tên file:

      middleware.ts → proxy.ts
    
  • Đổi tên hàm export:

      export default function proxy(request: NextRequest) {
        return NextResponse.redirect(new URL('/home', request.url));
      }
    
  • Logic xử lý trong file giữ nguyên, chỉ cần đổi tên cho phù hợp.

⚙️ Lý do thay đổi

  • Tên “proxy” rõ nghĩa hơn: phản ánh đúng vai trò chặn, chuyển hướng, hoặc xử lý request trước khi tới route chính.

  • Runtime thống nhất: chỉ chạy trên Node.js, giúp tránh nhầm lẫn giữa môi trường Edge và server truyền thống.

🧭 Lưu ý:

  • middleware.ts vẫn tạm thời được hỗ trợ cho Edge runtime, nhưng đã deprecated và sẽ bị loại bỏ trong phiên bản tương lai.

  • Hãy sớm chuyển sang proxy.ts để đảm bảo tương thích lâu dài.

Xem thêm hướng dẫn chi tiết tại đây

🔁 Fast Refresh mới

Ai code React cũng biết cảm giác này: sửa file → lưu → đợi reload vài trăm mili giây đến vài giây.
Giờ thì không còn nữa.

Với Fast Refresh mới trong NextJS 16, nhờ React CompilerTurbopack, mỗi khi bạn lưu file:

  • Code được biên dịch lại gần như ngay lập tức.

  • Trạng thái component được giữ nguyên.

Ví dụ: bạn đang dev form hoặc counter – thay đổi UI, nhấn “save” → trình duyệt cập nhật ngay tức thì mà không reset dữ liệu.

⚡ Turbopack Updates

NextJS 16 tiếp tục cải thiện Turbopack, trình build thế hệ mới thay thế Webpack.
Hiệu năng build giờ nhanh hơn tới 40%, đặc biệt với project lớn hoặc monorepo.

Bạn có thể cảm nhận ngay khi chạy:

npx create-next-app@latest my-app

Turbopack là build engine mặc định, không cần cài thêm gì cả.
Và nếu bạn từng “chịu khổ” với Webpack build lâu, bạn sẽ thích Turbopack ngay từ lần đầu chạy.

⚛️ React 19 Support

NextJS 16 được xây dựng để hoạt động mượt mà cùng React 19.
Điều này giúp tận dụng đầy đủ các tính năng mới:

  • Actions & Form Handling: gửi form và thực thi logic mà không cần fetch thủ công.

  • Async Context: chia sẻ dữ liệu bất đồng bộ dễ dàng.

  • Cải tiến Hooks: giúp code React dễ quản lý hơn.

💻 DX & Build Improvements

Vercel tiếp tục đầu tư mạnh vào DX (Developer Experience):

  • Thông báo lỗi rõ ràng, dễ hiểu.

  • Log build chi tiết hơn.

  • Gợi ý fix khi cấu hình sai.

  • Tích hợp tốt hơn với VS Code và TypeScript.

▲ Next.js 16 (Turbopack)

 ✓ Compiled successfully in 615ms
 ✓ Finished TypeScript in 1114ms
 ✓ Collecting page data in 208ms
 ✓ Generating static pages in 239ms
 ✓ Finalizing page optimization in 5ms

🧾 Improved Caching APIs

Trong NextJS 16, hệ thống cache được tinh chỉnh mạnh mẽ hơn với các API mới và cải tiến, giúp dev kiểm soát rõ ràng cách dữ liệu được lưu, làm mới và cập nhật trong ứng dụng.

Ba API nổi bật trong lần cập nhật này gồm: revalidateTag() (được nâng cấp), updateTag() (mới), và refresh() (mới).

⚡️ revalidateTag() – hỗ trợ stale-while-revalidate (SWR)

API này được cập nhật để bạn có thể chỉ định profile cacheLife — giúp dữ liệu cũ vẫn được trả về ngay lập tức trong khi NextJS ngầm revalidate ở background.

import { revalidateTag } from 'next/cache';

// ✅ Dùng profile có sẵn (gợi ý dùng 'max' cho hầu hết trường hợp)
revalidateTag('blog-posts', 'max');

// Hoặc chọn profile khác
revalidateTag('news-feed', 'hours');
revalidateTag('analytics', 'days');

// Tùy chỉnh thời gian revalidate thủ công
revalidateTag('products', { revalidate: 3600 });

// ⚠️ Deprecated: dạng cũ chỉ truyền 1 tham số
revalidateTag('blog-posts');

Các profile ('max', 'hours', 'days') có thể được định nghĩa sẵn trong next.config.
Khi người dùng request dữ liệu có tag tương ứng, NextJS trả cache ngaylàm mới ở background, giữ trải nghiệm nhanh mà vẫn đảm bảo dữ liệu không quá cũ.

👉 Dùng revalidateTag() cho các nội dung tĩnh (ví dụ: blog, news feed, sản phẩm) — nơi bạn chấp nhận độ trễ nhỏ khi cập nhật để đổi lấy tốc độ tải nhanh.

Hướng dẫn nâng cấp:
Nếu bạn đang dùng revalidateTag('tagName'), hãy thêm tham số thứ hai (ví dụ 'max') để bật SWR behavior.
Nếu bạn cần hành vi “đọc ngay sau khi ghi” (read-your-writes), hãy dùng updateTag() thay thế.

updateTag() – cập nhật cache ngay lập tức

Đây là API mới, dùng trong Server Actions, giúp invalidate cache và đọc lại dữ liệu mới ngay trong cùng request.

Rất hữu ích cho các hành động tương tác (form, cài đặt, cập nhật hồ sơ, v.v.), nơi người dùng cần thấy kết quả thay đổi ngay lập tức.

'use server';

import { updateTag } from 'next/cache';

export async function updateUserProfile(userId: string, profile: Profile) {
  await db.users.update(userId, profile);

  // Hết hạn cache và đọc lại dữ liệu mới ngay
  updateTag(`user-${userId}`);
}

💡 Dùng updateTag() khi cần dữ liệu được cập nhật trực quan và tức thời, đảm bảo người dùng thấy kết quả mới ngay sau thao tác.

🔄 refresh() – làm mới dữ liệu không cache

Cũng dành cho Server Actions, refresh() giúp cập nhật lại các phần dữ liệu động không được cache — mà không ảnh hưởng đến phần tĩnh của trang.

'use server';

import { refresh } from 'next/cache';

export async function markNotificationAsRead(notificationId: string) {
  await db.notifications.markAsRead(notificationId);

  // Làm mới phần hiển thị số thông báo (không cache)
  refresh();
}

Dùng trong các trường hợp như:

  • Làm mới số lượng thông báo chưa đọc.

  • Cập nhật trạng thái online / live metrics.

  • Refresh dữ liệu động mà không cần re-render toàn bộ trang.

APIDùng choHành vi chính
revalidateTag()Nội dung tĩnh, cache lâuTrả cache ngay, revalidate nền
updateTag()Tác vụ người dùngLàm mới cache & đọc lại tức thì
refresh()Dữ liệu không cacheRefresh phần động mà không động tới cache

Tài liệu tham khảo