Tối ưu hiệu năng ứng dụng Next.js từ 3 giây xuống còn 300ms trong sản xuất

07 tháng 4, 2026·5 phút đọc

Bài viết chia sẻ quá trình chẩn đoán và tối ưu hiệu năng một ứng dụng Next.js bị chậm với FCP lên đến 3 giây, qua các biện pháp như loại bỏ thư viện nặng, chuyển fetch dữ liệu sang Server Components, tối ưu ảnh và caching, giúp giảm thời gian tải xuống còn 300ms.

Tối ưu hiệu năng ứng dụng Next.js từ 3 giây xuống còn 300ms trong sản xuất

Tối ưu hiệu năng ứng dụng Next.js từ 3 giây xuống còn 300ms trong sản xuất

Ngày nay, với các ứng dụng web hiện đại, thời gian tải trang và trải nghiệm người dùng là yếu tố sống còn. Tác giả bài viết đã trải qua một trải nghiệm thực tế khi ứng dụng Next.js của họ đang chạy trên môi trường sản xuất nhưng First Contentful Paint (FCP) lên tới khoảng 3 giây, còn trên di động thì LCP khoảng 4 giây — con số quá chậm gây ảnh hưởng nghiêm trọng tới UX và SEO.

Bài viết này kể lại chi tiết quá trình chẩn đoán nguyên nhân và các bước tối ưu từ việc phân tích bundle, giảm kích thước gói phần mềm, chuyển các truy vấn fetch khỏi client sang server, tối ưu ảnh và config caching đúng cách. Kết quả cuối cùng là cải thiện FCP xuống còn 300ms trong môi trường thực tế.

Chẩn đoán: Hiểu rõ điểm nghẽn hiệu năng

Trước khi chỉnh sửa code, tác giả dùng các công cụ:

  • Google Lighthouse chạy ở chế độ ẩn danh để tránh ảnh hưởng từ extension
  • Chrome DevTools với network throttling mô phỏng mạng 3G nhanh
  • Vercel Analytics để đo dữ liệu thực tế người dùng
  • Next.js bundle analyzer (@next/bundle-analyzer) để xem chi tiết gói bundle

Qua bước phân tích, phát hiện ra:

  • Bundle client chứa nhiều phụ thuộc thừa: ví dụ moment.js nguyên gói (67kb gzipped) để format vài ba ngày tháng
  • Các thư viện nặng (như thư viện vẽ đồ thị) được load luôn dù chỉ dùng ở một trang
  • Các component thực hiện fetch dữ liệu theo chuỗi trên phía client, gây latency chồng chất
  • Ảnh chưa được tối ưu, không khai báo kích thước dễ gây layout shift

Vấn đề 1: Bundle quá cồng kềnh

Để giảm tải bundle:

  • Thay moment.js bằng date-fns import cụ thể chỉ module cần thiết, giảm đến 67kb kích thước bundle
  • Sử dụng dynamic import (next/dynamic) để chỉ tải các component không hiển thị ngay đầu trang như biểu đồ dashboard, giúp giảm thêm ~45kb bundle gốc

Cách làm này giúp giảm gần 60% kích thước bundle hoạt động của client, cải thiện thời gian tải và trải nghiệm người dùng.

Vấn đề 2: Waterfall fetch dữ liệu trên client

Trước đó, các fetch request cho user, posts, stats được thực hiện nối tiếp, mỗi fetch chờ fetch trước hoàn thành, tạo overhead khoảng 800ms.

Để giải quyết:

  • Bước 1: Nếu đã có userId từ trước, các fetch có thể chạy song song bằng Promise.all, giảm độ trễ chờ đợi.
  • Bước 2 (tiêu biểu trong Next.js 13+): Di chuyển fetch về Server Components trong App Router, để dữ liệu được preload trên server và gửi sẵn đầy đủ trong HTML tới client, tránh round trip tốn thời gian.

Điều này giúp loại bỏ hẳn chuỗi fetch gây chậm, giảm thời gian phản hồi đáng kể.

Vấn đề 3: Ảnh chưa tối ưu, gây layout shift

Tác giả phát hiện ảnh dùng tag HTML <img> cũ, không khai báo kích thước width/height và không lazy loading thông minh, gây Cumulative Layout Shift (CLS) rất cao (0.34, trong khi Google khuyến nghị dưới 0.1).

Giải pháp:

  • Chuyển sang dùng component next/image cho phép đặt chiều rộng/ chiều cao cố định, tự động lazy load và chuyển đổi định dạng hiện đại WebP/AVIF phù hợp trình duyệt.
  • Cấu hình các domain cho phép host ảnh trong next.config.js.
  • Với ảnh hiển thị đầu trang (above the fold), bật priority={true} để preload ảnh ưu tiên.

Nhờ vậy, CLS giảm hơn 90%, kích thước ảnh gốc 800kb giảm còn 120kb sau chuyển WebP.

Tối ưu caching nghiêm túc

Phần lớn các route của Next.js App Router mặc định cache tĩnh tốt, tuy nhiên trước đây tác giả vô tình tắt cache bằng cách export dynamic = 'force-dynamic'.

Giờ tác giả chuyển sang:

  • Sử dụng revalidate cho các trang hoặc fetch API có dữ liệu thay đổi theo thời gian, ví dụ revalidate = 60 (tái tạo cache mỗi 60s)
  • Sử dụng cache options như force-cache, no-store tùy trường hợp với fetch trong Server Components để cân bằng giữa dữ liệu tươi mới và tốc độ tải nhanh

Kết quả thực tế

Sau 1 tuần áp dụng các biện pháp trên, số liệu từ Vercel Analytics cho thấy cải thiện rất rõ rệt:

Mặt đoTrướcSauMức cải thiện
FCP (p75)3.1 giây310 msGiảm 90%
LCP (p75)4.2 giây820 msGiảm 80%
CLS0.340.02Giảm 94%
Kích thước bundle487 kb198 kbGiảm 59%
TTFB890 ms180 msGiảm 80%

Điểm số Lighthouse tăng từ 42 lên 91 trên desktop, 31 lên 84 trên mobile.

Trong các tác động, Server Components loại bỏ waterfall request đóng góp 40% cải thiện, tiếp đến bundle splitting 30%, tối ưu ảnh 20% và caching 10%.

Bài học và lời khuyên

Tác giả rút ra bài học quan trọng là đo đạc hiệu năng ngay từ giai đoạn phát triển đừng để đến khi ra sản xuất có người dùng thật mới phát hiện. Họ cũng triển khai Lighthouse chạy tự động trong pipeline CI/CD để thất bại khi điểm số rớt dưới 80.

Tinh thần tối ưu hiệu năng phải là một mindset dài hạn, không phải chạy sprint cuối cùng. Mỗi thư viện thêm vào, mỗi fetch trên client, mỗi ảnh không tối ưu đều có hệ quả lâu dài về tốc độ và SEO.

Cuối cùng, tác giả nhấn mạnh Next.js cung cấp sẵn rất nhiều công cụ hữu ích như Server Components, component Image, bundle analyzer… Việc tận dụng chúng một cách nghiêm túc sẽ giúp sản phẩm của bạn chạy nhanh, người dùng hài lòng mà không cần "phép màu".


Bài viết gốc tại juanchi.dev

Bài viết được tổng hợp và biên soạn bằng AI từ các nguồn tin tức công nghệ. Nội dung mang tính tham khảo. Xem bài gốc ↗