Chuyển tới nội dung chính

[Lv1] Tối ưu hóa cấp route: ba tầng Lazy Loading

Với ba tầng Lazy Loading route, tải lần đầu giảm từ 12.5MB xuống 850KB, thời gian màn hình đầu rút ngắn 70%.


Bối cảnh vấn đề (Situation)

Đặc điểm dự án:

  • 27+ template multi-tenant khác nhau (kiến trúc multi-tenant)
  • Mỗi template có 20-30 trang (trang chủ, sảnh chính, khuyến mại, đại lý, tin tức, v.v.)
  • Nếu tải tất cả code cùng lúc: lần đầu cần tải 10MB+ file JS
  • Người dùng đợi hơn 10 giây (đặc biệt trên mạng di động)

Mục tiêu tối ưu (Task)

  1. Giảm kích thước JavaScript lần đầu tải (mục tiêu: < 1MB)
  2. Rút ngắn thời gian màn hình đầu (mục tiêu: < 3 giây)
  3. Tải theo yêu cầu (người dùng chỉ tải những gì cần thiết)
  4. Duy trì trải nghiệm phát triển (không ảnh hưởng đến hiệu suất làm việc)

Giải pháp (Action)

Chúng tôi áp dụng chiến lược Lazy Loading route ba tầng, tối ưu từ "template" -> "trang" -> "quyền".

Tầng 1: Tải template động

// src/router/routes.ts
export default async function (siteKey?: string) {
// Tải động route của template tương ứng theo biến môi trường
const module = await import(`../../template/${siteKey}/router/routes.ts`);
return { routes: module.routes };
}

Giải thích:

  • Dự án có 27 template, nhưng người dùng chỉ sử dụng 1
  • Xác định thương hiệu hiện tại qua environment.json
  • Chỉ tải cấu hình route của thương hiệu đó, 26 template còn lại hoàn toàn không tải

Kết quả:

  • Giảm khoảng 85% code cấu hình route khi tải lần đầu

Tầng 2: Lazy Loading trang

// Cách viết truyền thống (❌ - không nên)
import HomePage from './pages/HomePage.vue';
component: HomePage; // Tất cả trang đều được đóng gói vào main.js

// Cách viết của chúng tôi (✅ - nên dùng)
component: () => import('app/template/okbet_green/pages/HomePage/Home.vue');
  • Mỗi route sử dụng hàm mũi tên + import()
  • JS tương ứng chỉ được tải khi người dùng truy cập trang đó
  • Vite tự động đóng gói mỗi trang thành file độc lập

Tầng 3: Chiến lược tải theo điều kiện

// src/router/index.ts
router.beforeEach((to, from, next) => {
const { needAuth } = to.meta;
if (needAuth && !store.isLogin) {
// Người dùng chưa đăng nhập sẽ không tải các trang như "Trung tâm đại lý"
return next({ name: 'HomePage' });
}
next();
});

Kết quả tối ưu (Result)

Trước tối ưu:

Tải lần đầu: main.js (12.5 MB)
Thời gian màn hình đầu: 8-12 giây
Bao gồm tất cả 27 template + tất cả trang

Sau tối ưu:

Tải lần đầu: main.js (850 KB) ↓ 93%
Thời gian màn hình đầu: 1.5-2.5 giây ↑ 70%
Chỉ bao gồm code cốt lõi + trang chủ hiện tại

Dữ liệu cụ thể:

  • Giảm kích thước JavaScript: 12.5 MB → 850 KB (giảm 93%)
  • Rút ngắn thời gian màn hình đầu: 10 giây → 2 giây (cải thiện 70%)
  • Tải trang tiếp theo: trung bình 300-500 KB mỗi trang
  • Điểm trải nghiệm người dùng: từ 45 lên 92 điểm (Lighthouse)

Giá trị kinh doanh:

  • Tỷ lệ rời trang giảm 35%
  • Thời gian ở lại trang tăng 50%
  • Tỷ lệ chuyển đổi tăng 25%

Điểm chính phỏng vấn

Câu hỏi mở rộng thường gặp:

  1. Q: Tại sao không dùng React.lazy() hoặc component bất đồng bộ của Vue? A: Chúng tôi có dùng component bất đồng bộ của Vue (() => import()), nhưng điểm chính là kiến trúc ba tầng:

    • Tầng 1 (template): quyết định lúc compile (cấu hình Vite)
    • Tầng 2 (trang): Lazy Loading lúc chạy
    • Tầng 3 (quyền): kiểm soát bởi navigation guard

    Lazy loading đơn thuần chỉ làm được tầng 2, chúng tôi đã thêm phân tách ở cấp template.

  2. Q: Làm sao quyết định code nào nên nằm trong main.js? A: Qua cấu hình manualChunks của Vite:

    build: {
    rollupOptions: {
    output: {
    manualChunks: {
    'vendor': ['vue', 'pinia', 'vue-router'],
    'ui': ['element-plus'],
    }
    }
    }
    }

    Nguyên tắc: chỉ những thứ "mỗi trang đều dùng" mới đặt vào vendor chunk.

  3. Q: Lazy Loading có ảnh hưởng trải nghiệm người dùng (thời gian chờ) không? A: Có hai chiến lược đối phó:

    • Prefetch: tải trước các trang có thể truy cập khi nhàn rỗi
    • Trạng thái loading: sử dụng Skeleton Screen thay vì màn hình trắng

    Thử nghiệm thực tế: thời gian tải trung bình của trang tiếp theo < 500ms, người dùng không cảm nhận được.

  4. Q: Làm sao đo lường hiệu quả tối ưu? A: Sử dụng kết hợp nhiều công cụ:

    • Lighthouse: Performance Score (45 → 92)
    • Webpack Bundle Analyzer: phân tích trực quan kích thước chunk
    • Chrome DevTools: Network waterfall, Coverage
    • Real User Monitoring (RUM): dữ liệu người dùng thực
  5. Q: Có những đánh đổi (Trade-off) nào? A:

    • Có thể gặp vấn đề phụ thuộc vòng khi phát triển (cần điều chỉnh cấu trúc module)
    • Thời gian tải ngắn khi chuyển route lần đầu (giải quyết bằng prefetch)
    • Nhưng tổng thể lợi ích vượt xa, đặc biệt cải thiện trải nghiệm trên điện thoại