Next.js 16, Thành phần bộ đệm và Trình biên dịch React: điều gì thực sự thay đổi
· 9 min read · Filippo Spinella · Next.js, React, Frontend, Performance
Trong nhiều năm, một trong những câu hỏi khó chịu nhất trong Next.js là: "Trang này tĩnh hay động?". Đây có vẻ là một câu hỏi đơn giản cho đến khi bạn thêm lệnh gọi tới cookies(), fetch với các tùy chọn khác nhau, ứng dụng khách cơ sở dữ liệu, CMS, giỏ hàng hoặc một phần nội dung tùy chỉnh.
Next.js 16 thú vị vì nó cố gắng làm cho cuộc trò chuyện này bớt bí ẩn hơn. Nó không loại bỏ sự phức tạp, nhưng nó thay đổi mô hình tinh thần: các tuyến đường mặc định là động, bộ đệm tự khai báo khi cần thiết và Suspense trở thành cách tự nhiên để tạo ra các shell nhanh với các bộ phận luôn mới.
Tính năng cần hiểu là Cache Components. Turbopack ổn định, Trình biên dịch React, proxy.ts và các API vô hiệu hóa mới rất quan trọng nhưng chúng xoay quanh cùng một vấn đề: xây dựng các ứng dụng nhanh mà không cần phải đoán xem framework đã quyết định điều gì đằng sau hậu trường.
Bởi vì điều này quan trọng
Trong ứng dụng thực, bạn không chỉ có "trang tĩnh" và "trang động". Bạn có những phần khác nhau với những nhu cầu khác nhau.
Bảng sản phẩm có thể thay đổi vài lần trong ngày. Giá có thể thay đổi thường xuyên hơn. Tính khả dụng phải gần như trực tiếp. Tên người dùng là cá nhân. Đánh giá có thể được truyền trực tuyến. Thanh bên có thể ổn định. Xe đẩy thì không.
Nếu bạn coi mọi thứ như một đơn vị, bạn sẽ luôn rơi vào một trong hai thái cực:
- bộ nhớ đệm tích cực và nguy cơ nhìn thấy dữ liệu cũ;
- hiển thị động ở mọi nơi và hiệu suất kém hơn mức cần thiết.
Thành phần bộ đệm phục vụ chính xác để tránh lựa chọn sai lầm này.
Mô hình trong thực tế
Với cacheComponents: true, bạn có thể khai báo những gì có thể lưu vào bộ nhớ đệm bằng "use cache". Sau đó, bạn có thể liên kết thời lượng và thẻ với cacheLife() và cacheTag(). Các bộ phận động vẫn duy trì trạng thái động và có thể được tách biệt bằng Suspense.
Thiết lập nhỏ:
// next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { cacheComponents: true, }; export default nextConfig;
Sự thay đổi lớn không nằm ở cấu hình. Đó là cách bạn bắt đầu viết các thành phần.
// app/products/[slug]/page.tsx import { Suspense } from 'react'; import { cacheLife, cacheTag } from 'next/cache'; async function getProduct(slug: string) { 'use cache'; cacheLife('hours'); cacheTag(`product:${slug}`); return db.product.findUnique({ where: { slug } }); } async function ProductDetails({ slug }: { slug: string }) { const product = await getProduct(slug); return ( <section> <h1>{product.name}</h1> <p>{product.description}</p> </section> ); } async function LiveInventory({ slug }: { slug: string }) { const inventory = await db.inventory.findFirst({ where: { slug } }); return <p>{inventory.quantity} pezzi disponibili</p>; } export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; return ( <> <ProductDetails slug={slug} /> <Suspense fallback={<p>Controllo disponibilità...</p>}> <LiveInventory slug={slug} /> </Suspense> </> ); }
Trang không nhất thiết phải được lưu hoàn toàn vào bộ nhớ đệm hoặc hoàn toàn động. Bảng sản phẩm có thể nhanh chóng và có thể tái sử dụng. Hàng tồn kho có thể vẫn còn mới. Người dùng sẽ nhìn thấy thứ gì đó ngay lập tức mà không cần chờ đợi phần chậm nhất.
use cache là tài liệu thực thi
Điều tôi thích ở "use cache" là nó buộc bạn phải đưa ra ý định rõ ràng. Khi đọc một hàm, bạn hiểu ngay rằng ai đó đã quyết định: "dữ liệu này có thể được sử dụng lại".
Nó đặc biệt hữu ích khi bạn không sử dụng fetch. Nhiều ứng dụng đọc dữ liệu từ Prisma, Drizzle, SDK nội bộ, ứng dụng khách CMS hoặc chức năng dịch vụ. Trong những trường hợp đó, lý luận cũ chỉ dựa trên các tùy chọn fetch là không đủ.
Một nguyên tắc nhỏ:
- nội dung cachea tương đối ổn định;
- sử dụng thẻ chi tiết;
- để lại các quyền, phiên, giỏ hàng, thông báo và trạng thái giao dịch động;
- đặt các phần chậm bên trong
Suspense; - đo lường trước khi nói "chúng tôi đã cải thiện hiệu suất".
Vô hiệu hóa mà không vứt bỏ mọi thứ
Bộ đệm chỉ hữu ích nếu bạn có thể cập nhật nó một cách chính xác. Ở đây cacheTag, revalidateTag và updateTag trở nên quan trọng.
Ví dụ:
'use server'; import { updateTag } from 'next/cache'; export async function updateProductName(productId: string, name: string) { await db.product.update({ where: { id: productId }, data: { name }, }); updateTag(`product:${productId}`); }
Chi tiết quan trọng là thẻ. product:${productId} cho biết ranh giới chính xác. products kể về một cái thùng lớn. Lúc đầu, chiếc xô lớn rất thoải mái; sau một vài tháng, nó trở thành lý do khiến bạn vô hiệu hóa một nửa ứng dụng để thay đổi tiêu đề.
Turbopack ổn định: phần bạn nghe thấy hàng ngày
Next.js 16 đưa Turbopack vào trung tâm phát triển và xây dựng. Đây không phải là tính năng thơ mộng nhất, nhưng là tính năng bạn cảm nhận được khi làm việc: máy chủ khởi động sớm hơn, làm mới nhanh hơn, xây dựng khiến bạn không còn cảm giác như đang phải nghỉ giải lao ép buộc để uống cà phê.
Điều đó có nghĩa là tôi sẽ không di chuyển một cơ sở mã chứa đầy các plugin tùy chỉnh khi nhắm mắt lại. Tôi sẽ kiểm tra:
- xây dựng địa phương;
- nhập khẩu không đạt tiêu chuẩn;
- MDX, SVG và CSS;
- Các plugin webpack còn lại;
- các trang quan trọng;
- sự khác biệt về thời gian xây dựng.
Đối với các dự án mới, tôi sẽ bắt đầu từ mặc định. Đối với những người trưởng thành, tôi sẽ thực hiện di chuyển có đo lường.
Trình biên dịch React: loại bỏ tiếng ồn, không suy nghĩ
React Compiler 1.0 ổn định và Next.js 16 hỗ trợ nó với reactCompiler. Lời hứa là sẽ giảm bớt nhiều thao tác ghi nhớ thủ công: ít memo hơn, ít useMemo hơn, ít useCallback được sử dụng "vì an toàn".
// next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { reactCompiler: true, }; export default nextConfig;
Tôi sẽ không coi nó như bụi ma thuật. Trình biên dịch sẽ giúp ích khi mã tuân thủ tốt các quy tắc React. Nếu các thành phần có tác dụng phụ lạ, đột biến ẩn hoặc hook bị sử dụng không tốt thì cần phải khắc phục trước tiên.
Cách lành mạnh để thử nó:
- cập nhật
eslint-plugin-react-hooks; - khắc phục các vi phạm thực tế;
- kích hoạt nó trên một khu vực được kiểm soát;
- đo lường thời gian và hành vi xây dựng;
- chỉ xóa ghi nhớ thủ công khi không còn cần thiết.
Mục tiêu không phải là xóa mọi useMemo. Mục đích là ngừng ghi nhớ phòng ngừa vì chúng tôi sợ hiển thị.
proxy.ts và ranh giới mạng
middleware.ts cũ trở thành proxy.ts. Đó là một sự thay đổi tên, nhưng nó có ý nghĩa: tệp đó nằm ở ranh giới yêu cầu, nó không phải là phần mềm trung gian chung kiểu phụ trợ truyền thống.
// proxy.ts import { NextRequest, NextResponse } from 'next/server'; export default function proxy(request: NextRequest) { const isLoggedIn = Boolean(request.cookies.get('session')); if (!isLoggedIn && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); }
Quy tắc ở đây rất đơn giản: giữ nó ở mức nhỏ. Chuyển hướng, định tuyến xác thực, tiêu đề, viết lại cần thiết. Nếu nó bắt đầu có cảm giác giống như một chương trình phụ trợ thứ hai thì có lẽ nó đang làm quá nhiều việc.
Làm thế nào tôi thực sự có thể di cư
Tôi sẽ không bật tất cả các tính năng cùng một lúc. Tôi sẽ làm điều này:
- Tôi cập nhật Next, React và React DOM;
- Tôi khởi chạy codemod chính thức;
- Tôi sửa các thay đổi có thể gây lỗi trên
params,searchParams,cookies(),headers()vàdraftMode(); - Tôi di chuyển
middleware.tssangproxy.ts; - Tôi kiểm tra các bản dựng và các trang quan trọng;
- Tôi kích hoạt Thành phần bộ đệm trên một phần mà bộ đệm hiện đang tạo ra xung đột;
- Tôi xác định các quy ước cho thẻ và tính vô hiệu;
- Tôi thử riêng Trình biên dịch React;
- so sánh các chỉ số trước và sau.
Việc di chuyển tốt không phải là việc sử dụng tất cả các tính năng mới. Đó là điều làm cho hoạt động của ứng dụng trở nên dễ đọc hơn.
Điều gì thay đổi trong cách suy nghĩ
Điều hữu ích nhất ở Next.js 16 là nó buộc bạn phải đặt tên cho ý định tốt hơn. Chức năng không chỉ là "lấy sản phẩm từ cơ sở dữ liệu". Đó là "lấy sản phẩm, tôi có thể lưu nó vào bộ nhớ đệm hàng giờ, tôi vô hiệu hóa nó bằng thẻ này". Một thành phần không chỉ là "kết xuất trang". Đó là "đây là phần vỏ nhanh, phần này mang tính cá nhân, phần này phát trực tuyến."
Lúc đầu có vẻ như có nhiều việc hơn. Sau đó, nó trở thành một hình thức bình tĩnh. Các quyết định về hiệu suất không còn bị ẩn giấu trong sự kết hợp của các giá trị mặc định, phương pháp phỏng đoán và trí nhớ bộ lạc. Chúng nằm trong mật mã.