Bỏ qua đến nội dung

TP.HCM (VN)

code splitting la gi

10 tháng 5, 2026

Code splitting là gì (chia nhỏ bundle JS để tải lười)

Code splitting (chia nhỏ mã) là kỹ thuật cắt một bundle JavaScript lớn thành nhiều mảnh nhỏ, để trình duyệt chỉ tải phần mã thật sự cần cho màn hình hiện tại, còn phần còn lại tải lười (lazy) khi người dùng cần. Mục tiêu là giảm JavaScript phải tải, phân tích và biên dịch ngay từ đầu.

Nguyen Hien

Tôi là Nguyen Hien, Founder Web22, freelance team

Code splitting (chia nhỏ mã) là kỹ thuật cắt một bundle JavaScript lớn thành nhiều mảnh nhỏ, để trình duyệt chỉ tải phần mã thật sự cần cho màn hình hiện tại, còn phần còn lại tải lười (lazy) khi người dùng cần. Mục tiêu là giảm JavaScript phải tải, phân tích và biên dịch ngay từ đầu.

Khi một trang web gói toàn bộ JavaScript vào một file duy nhất, trình duyệt buộc phải tải xuống, phân tích (parse) và biên dịch (compile) hết khối mã đó trước khi trang phản hồi mượt. Phần lớn công việc này diễn ra trên main thread (luồng chính — luồng duy nhất xử lý cả giao diện lẫn tương tác), nên trong lúc đó người dùng bấm vào đâu cũng thấy khựng. Đây chính là vấn đề mà việc chia nhỏ mã giải quyết.

Vì sao một bundle lớn lại làm trang chậm

Theo tài liệu của web.dev, một long task (tác vụ dài) là bất kỳ tác vụ JavaScript nào chạy quá 50 mili-giây trên main thread; trong suốt thời gian đó trình duyệt không xử lý được thao tác người dùng. Một file JS khổng lồ thường tạo ra một tác vụ dài duy nhất, làm tăng TBT (Total Blocking Time — tổng thời gian chặn) và kéo theo INP (Interaction to Next Paint — độ trễ tới khung hình kế tiếp sau khi người dùng tương tác) kém.

INP hiện là chỉ số chính thức thay cho FID từ tháng 3/2024, với ngưỡng “Good” là dưới 200ms (đo ở phân vị 75 — p75). Hai chỉ số còn lại trong bộ Core Web Vitals giữ nguyên ngưỡng năm 2026: LCP (Largest Contentful Paint) ≤ 2.5s và CLS (Cumulative Layout Shift) ≤ 0.1. Trong ba chỉ số, INP là cái mà nhiều site trượt nhất, và nó nhạy trực tiếp với khối lượng JavaScript phải xử lý ngay khi tải trang.

web.dev mô tả rõ logic của code splitting: thay vì một tác vụ lớn có thể chặn main thread, bạn có nhiều tác vụ nhỏ hơn — chúng chặn ít hơn, hoặc không chặn. Đó là lý do chia nhỏ mã giúp cải thiện độ phản hồi khi tải trang.

Sơ đồ vì sao một bundle JavaScript lớn chặn main thread làm trang chậm phản hồi
Bundle lớn buộc trình duyệt tải và xử lý hết mới phản hồi, chặn main thread.

Code splitting khác tree-shaking thế nào

Hai khái niệm này hay bị lẫn nhưng giải quyết hai bài toán khác nhau:

Kỹ thuậtLàm gìKết quả
Tree-shaking (rung cây)Loại bỏ mã chết — phần code import vào nhưng không bao giờ dùng tớiBundle nhỏ lại vì bớt code thừa
Code splitting (chia nhỏ mã)Cắt code đang dùng thành nhiều mảnh, tải lười phần chưa cần ngayJS tải ban đầu nhẹ đi, phần còn lại tải sau

Nói gọn: tree-shaking loại code không bao giờ chạy, còn code splitting hoãn lại code có chạy nhưng chưa cần ngay từ giây đầu. Hai kỹ thuật bổ trợ nhau, nên dùng cả hai.

Sơ đồ ba kiểu chia nhỏ mã JavaScript dynamic import chia theo route và lazy component
Thay vì một bundle lớn, chia thành mảnh nhỏ và chỉ tải khi cần đến.

Ba kiểu chia nhỏ mã thường gặp

1. Dynamic import — chia ở mức module

Cú pháp import() trả về một Promise và chỉ tải module đúng lúc gọi. Đây là nền tảng của mọi kiểu code splitting. Ví dụ: thư viện vẽ biểu đồ nặng chỉ nên tải khi người dùng thật sự mở phần biểu đồ.

// Tải tĩnh: gói vào bundle ban đầu (nặng ngay từ đầu)
// import { renderChart } from './chart';

// Tải lười bằng dynamic import: chỉ tải khi cần
button.addEventListener('click', async () => {
  const { renderChart } = await import('./chart.js');
  renderChart(data);
});

Trình đóng gói (bundler) như Vite, webpack hay Rollup tự cắt ./chart.js ra một chunk riêng và chỉ tải khi dòng import() chạy.

2. Chia theo route (route-based)

Mỗi trang/đường dẫn là một chunk riêng. Người dùng vào trang chủ thì chỉ tải JS của trang chủ, chưa tải JS của trang giỏ hàng hay trang quản trị. Với React thường dùng React.lazy kết hợp Suspense:

import { lazy, Suspense } from 'react';

const CheckoutPage = lazy(() => import('./pages/Checkout.jsx'));

<Suspense fallback={<Spinner />}>
  <CheckoutPage />
</Suspense>

Một số framework làm sẵn việc này: Next.js App Router tự chia mã theo route, mỗi trang là một bundle riêng, nên bạn chỉ cần lazy thủ công cho những component nặng không phải lúc nào cũng hiển thị.

3. Lazy component — chia theo thành phần giao diện

Những khối chỉ xuất hiện khi người dùng tương tác (modal, tab ẩn, trình soạn thảo, bản đồ) nên tách thành chunk riêng và chỉ tải khi mở. Cách này tránh nhồi mã của tính năng “thi thoảng mới dùng” vào lần tải đầu tiên.

Cẩn thận khi chia nhỏ mã

  • Đừng chia quá vụn. Mỗi chunk là một request mạng; cắt thành hàng trăm mảnh tí hon có thể làm chậm vì chi phí request và làm waterfall (chuỗi tải nối đuôi) dài ra.
  • Phân tách rõ “tải lười” với “thứ tự thực thi”. Code splitting quyết định khi nào tải một mảnh; còn việc script không chặn render thì là chuyện của cách nạp script defer hay async. Hai khâu này độc lập, làm cả hai mới gọn.
  • Preload phần chắc chắn cần. Với chunk người dùng gần như sẽ chạm tới (route kế tiếp), có thể báo trình duyệt tải sớm để tránh khựng khi chuyển trang.
  • Có fallback khi tải lỗi. Chunk tải lười có thể trượt do mạng; cần xử lý trạng thái lỗi để giao diện không treo.

Một góc thực chiến

Web22 vận hành web22.dev trên LiteSpeed + Cloudflare và đo Core Web Vitals bằng Lighthouse/PageSpeed thật. Kinh nghiệm rút ra khi tối ưu: con số “điểm” trong phòng lab có thể nhảy, nhưng nguyên tắc thì ổn định — phần JavaScript nào chưa cần ngay trong màn hình đầu thì đừng bắt trình duyệt tải và biên dịch ngay. Chia nhỏ mã đúng chỗ thường giảm rõ khối lượng main thread phải nuốt lúc khởi động, nhờ đó TBT trong đo lab dịu lại và trải nghiệm bấm/cuộn mượt hơn.

Câu hỏi thường gặp

Code splitting có làm trang phức tạp hơn không?

Có thêm một chút logic tải lười và trạng thái chờ. Nhưng với framework hiện đại, phần chia theo route gần như tự động, bạn chỉ cần để tâm tới vài component nặng.

Chia nhỏ mã có cải thiện SEO không?

Gián tiếp. Nó giúp giảm JS chặn main thread, kéo theo INP và TBT tốt hơn — mà Core Web Vitals là yếu tố trải nghiệm Google quan tâm.

Nên chia theo route hay theo component trước?

Bắt đầu từ route: đây là lằn ranh tự nhiên và cho hiệu quả lớn nhất. Sau đó mới tách thêm những component nặng ít khi hiển thị.

Nếu trang của bạn nặng JS và điểm phản hồi kém, dịch vụ tối ưu hiệu năng web của Web22 có thể rà bundle và chỉ ra chỗ nên chia nhỏ.

Dịch vụ liên quan

Bảo trì và vận hành website giữ trang chạy bền Dịch vụ bảo trì và vận hành website là việc trông coi một trang web sau khi bàn giao, gom chăm sóc…

Cùng chủ đề Performance

Đang tính làm website hoặc muốn web đang chạy nhanh hơn? Nhắn cho Web22 một câu là được.