KIếN THứC WEBSITE › PERFORMANCE

Code splitting là gì — chia bundle JS theo route, component

Code splitting là gì — chia bundle JS theo route, component 2026

Tách bundle chia JS thành nhiều phần nhỏ, chỉ tải phần cần cho trang hiện tại — không tải một file lớn cho mọi trang. Kỹ thuật này giảm mạnh JS tải ban đầu, cải thiện LCP và INP rõ rệt.

Tách bundle là gì và lợi ích mang lại

code splitting — Tách bundle là gì và lợi ích mang lại
Tách bundle là gì và lợi ích mang lại

Tách bundle là kỹ thuật bundler chia codebase thành nhiều file riêng (chunk), trình duyệt chỉ tải chunk cần cho trang đang xem. Thay vì 1 file 800KB tải cho mọi trang, trang chủ chỉ cần tải 50KB main + 30KB chunk riêng của nó.

Kỹ thuật này có hiệu quả cao nhất cho ứng dụng SPA hoặc frontend Next.js. WordPress truyền thống có thể áp dụng cách tiếp cận tương đương qua plugin.

3 lợi ích cốt lõi

  • JS ban đầu giảm mạnh: Trang chủ chỉ tải chunk của chính nó, không kéo JS của trang thanh toán hay dashboard. LCP và INP cải thiện rõ.
  • Hiệu quả cache cao hơn: Chunk thư viện (React, axios) ít thay đổi → cache lâu với max-age=31536000. Chunk trang thay đổi thường → không làm mất cache chunk thư viện.
  • Tải song song với HTTP/2: Trình duyệt tải nhiều chunk cùng lúc — tổng thời gian không cộng dồn như tải tuần tự.

3 chiến lược tách bundle

3 chiến lược tách bundle
Sơ đồ minh hoạ — 3 chiến lược tách bundle

Ba cách chia code phổ biến — thường dùng kết hợp trong dự án lớn. Chọn theo kiến trúc trang.

1. Tách theo route

Mỗi route URL có một chunk riêng: trang chủ một chunk, trang sản phẩm một chunk, trang thanh toán một chunk. Người dùng vào trang chủ không tải JS của trang thanh toán.

// Next.js tự động tách chunk theo trang
// pages/product/[slug].js → tự động thành chunk riêng

// React Router — tách thủ công
import { lazy, Suspense } from 'react';
const ProductPage = lazy(() => import('./ProductPage'));
const CheckoutPage = lazy(() => import('./CheckoutPage'));

<Suspense fallback={<Spinner />}>
  <Routes>
    <Route path="/product/:slug" element={<ProductPage />} />
    <Route path="/checkout" element={<CheckoutPage />} />
  </Routes>
</Suspense>

2. Tách theo component

Component nặng (biểu đồ, video player, modal phức tạp) chỉ tải khi người dùng cần. Thư viện biểu đồ 200KB không tải nếu người dùng không cuộn xuống phần biểu đồ.

// Lazy load component khi cần
const Chart = lazy(() => import('./Chart'));

function Dashboard() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChart(true)}>
        Xem biểu đồ
      </button>
      {showChart && (
        <Suspense fallback={<p>Đang tải...</p>}>
          <Chart />
        </Suspense>
      )}
    </div>
  );
}

3. Tách chunk thư viện

Tách các gói npm (React, axios, lodash) thành chunk riêng khỏi code ứng dụng. Thư viện ít thay đổi → cache lâu; code ứng dụng thay đổi thường → không cần tải lại chunk thư viện.

// webpack.config.js
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: {
        test: /[/]node_modules[/]/,
        name: 'vendors',
        priority: 10,
      },
      common: {
        minChunks: 2,
        priority: 5,
      },
    },
  },
};

Dynamic import — cú pháp JS native

Dynamic import — cú pháp JS native
Dynamic import — cú pháp JS native

Dynamic import import() là cú pháp JS cho phép tải module theo yêu cầu. Công cụ build phát hiện dynamic import và tự tách thành chunk riêng.

Cú pháp này hoạt động trên Chrome 63+, Firefox 67+, Safari 11+ — phủ 95%+ trình duyệt đang dùng năm 2026.

2 pattern dynamic import phổ biến

// Tải khi người dùng nhấn nút
button.addEventListener('click', async () => {
  const { showModal } = await import('./modal.js');
  showModal();
});

// Tải khi component cuộn vào tầm nhìn
const observer = new IntersectionObserver(async (entries) => {
  if (entries[0].isIntersecting) {
    const { initChart } = await import('./chart.js');
    initChart();
    observer.disconnect();
  }
});
observer.observe(document.querySelector('#chart-container'));

So sánh công cụ build

Công cụ Tách bundle mặc định Phù hợp khi nào
Vite (Rollup) Có — tự động Dự án mới, HMR nhanh
Webpack 5 Có — cấu hình linh hoạt Dự án lớn, nhiều plugin
esbuild Có — build cực nhanh Cần tốc độ build, ít cần plugin
Next.js (Turbopack) Tự động theo trang App Next.js — không cần cấu hình thêm

Tách bundle trong ngữ cảnh WordPress

WordPress truyền thống không có bundler — không tách bundle theo kiểu native. Plugin enqueue file JS đầy đủ trên mọi trang.

Plugin dequeue script — cách tiếp cận tương đương

  • Asset CleanUp / Perfmatters: Tắt script không cần trên từng trang. Ví dụ: script thanh toán chỉ load trên trang checkout, không load trên trang chủ.
  • Thiết lập: cài plugin, đánh dấu rule theo từng trang.
  • Kết quả: giảm đáng kể JS tải trên trang chủ và trang danh mục.

WordPress headless + Next.js

  • WordPress làm CMS qua REST API, Next.js làm giao diện với tách bundle đầy đủ.
  • Mỗi trang Next.js thành chunk riêng, component nặng lazy load.
  • Thiết lập phức tạp hơn nhưng hiệu năng tier 1.
  • Đọc thêm về tree shaking — bổ sung cho tách bundle trong cùng pipeline build.

Tác động lên SEO và trải nghiệm người dùng

SEO — lazy component và Googlebot

Googlebot đợi JS thực thi trước khi index — thường tối đa 5 giây. Component lazy load không block render HTML ban đầu → SEO không bị ảnh hưởng.

Ngoại lệ: component lazy chứa H1 hoặc nội dung chính có thể bị bỏ sót khi index — không lazy load nội dung cốt lõi SEO.

Đọc thêm về Core Web Vitals 2026 và ảnh hưởng lên xếp hạng.

Ngưỡng số chunk hợp lý

HTTP/2 hỗ trợ multiplex tốt cho 5–20 chunk song song. Chia quá nhiều chunk (trên 50) tăng overhead header giao thức.

Ngưỡng thực tế: 10–30 chunk mỗi trang là phù hợp. Không cần tách mọi component thành chunk riêng.

Checklist tách bundle cho dự án Next.js / React

  1. Kiểm tra bundle size hiện tại với next build && next analyze hoặc webpack-bundle-analyzer.
  2. Xác định chunk lớn bất thường — thường là thư viện chưa được tree-shake.
  3. Áp dụng tách theo route: đảm bảo mỗi trang là chunk riêng (Next.js tự động).
  4. Lazy load component nặng (biểu đồ, rich text editor, video player).
  5. Tách chunk thư viện qua cấu hình splitChunks.cacheGroups.
  6. Cấu hình cache header dài cho chunk thư viện, ngắn cho chunk trang.
  7. Kết hợp prefetch chunk route tiếp theo dự đoán được.
  8. Kết hợp defer/async cho script bên thứ ba không cần ngay.
  9. Đo lại INP và LCP sau khi triển khai qua PageSpeed Insights.

Bài liên quan

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

Tách bundle ảnh hưởng SEO không?

Không ảnh hưởng nếu nội dung cốt lõi không bị lazy load. Googlebot đợi JS thực thi tối đa 5 giây.

Không lazy load H1 hoặc nội dung văn bản chính của bài.

Quá nhiều chunk có làm trang chậm hơn không?

Có ngưỡng. HTTP/2 xử lý tốt 5–20 chunk song song.

Trên 50 chunk, overhead header tăng.

Mức phù hợp: 10–30 chunk mỗi trang — không cần tách mọi component.

WordPress shop nhỏ có cần tách bundle không?

WordPress truyền thống render phía server, ít JS tương tác — tách bundle không mang lại nhiều lợi ích. Plugin Asset CleanUp bỏ script không dùng theo trang là đủ.

Khi site có dashboard người dùng hoặc bộ lọc tương tác phức tạp, nên cân nhắc chuyển sang headless để tách bundle đầy đủ.

Next.js có tự tách bundle không, cần cấu hình thêm gì?

Next.js tự động tách chunk theo trang — không cần cấu hình. Mỗi file trong thư mục pages/ (hoặc app/) thành chunk riêng.

Cần cấu hình thêm khi: lazy load component nặng, tách chunk thư viện cụ thể, hoặc tùy chỉnh cacheGroups.

Web22 audit bundle, xác định chunk cần tách và component nên lazy load, kèm đo lường trước/sau và xác nhận INP cải thiện. Xem dịch vụ tối ưu Core Web Vitals để tư vấn cho dự án.