Giới thiệu
Trong các phần trước, chúng ta đã tìm hiểu về quá trình render trong trình duyệt và các nguyên nhân phổ biến gây chậm render. Phần này sẽ tập trung vào các kỹ thuật tối ưu hóa cơ bản mà bạn có thể áp dụng cho hầu hết các dự án web, bất kể framework hay công nghệ nào đang được sử dụng.
Các kỹ thuật này được phân loại thành các nhóm chính:
- Tối ưu tài nguyên tĩnh
- Tối ưu CSS
- Tối ưu JavaScript
- Tối ưu hình ảnh và media
- Tối ưu font
- Sử dụng resource hints
flowchart LR
A[Tối ưu Frontend Cơ bản] --> B[Tối ưu tài nguyên tĩnh]
A --> C[Tối ưu CSS]
A --> D[Tối ưu JavaScript]
A --> E[Tối ưu hình ảnh và media]
A --> F[Tối ưu font]
A --> G[Resource hints]
B --> B1[Minify và nén]
B --> B2[Caching hiệu quả]
B --> B3[Sử dụng CDN]
C --> C1[Critical CSS]
C --> C2[CSS Modules/CSS-in-JS]
C --> C3[Loại bỏ CSS không sử dụng]
D --> D1[Code Splitting]
D --> D2[Tree Shaking]
D --> D3[Lazy Loading]
E --> E1[Responsive Images]
E --> E2[Modern Image Formats]
E --> E3[Lazy Loading Images]
E --> E4[Preloading Key Resources]
F --> F1[Font Display Strategies]
F --> F2[Subset Fonts]
F --> F3[Variable Fonts]
G --> G1[Preload]
G --> G2[Prefetch]
G --> G3[Preconnect]
G --> G4[DNS-Prefetch]
style A fill:#f96,stroke:#333,stroke-width:2px
style B fill:#bbf,stroke:#333,stroke-width:1px
style C fill:#bbf,stroke:#333,stroke-width:1px
style D fill:#bbf,stroke:#333,stroke-width:1px
style E fill:#bbf,stroke:#333,stroke-width:1px
style F fill:#bbf,stroke:#333,stroke-width:1px
style G fill:#bbf,stroke:#333,stroke-width:1px
Hãy đi sâu vào từng kỹ thuật và xem cách chúng có thể cải thiện hiệu suất trang web của bạn.
1. Tối ưu tài nguyên tĩnh
Tài nguyên tĩnh bao gồm các file CSS, JavaScript, hình ảnh, font và các tài nguyên khác không thay đổi thường xuyên. Việc tối ưu hóa các tài nguyên này có thể mang lại cải thiện hiệu suất đáng kể.
flowchart TD
A[Tài nguyên gốc] --> B[Minify]
B --> C[Nén]
C --> D[Cache]
D --> E[CDN]
E --> F[Tài nguyên tối ưu]
style A fill:#f96,stroke:#333,stroke-width:1px
style F fill:#6f6,stroke:#333,stroke-width:1px
Minify và nén
Minification là quá trình loại bỏ các ký tự không cần thiết như khoảng trắng, comment, và định dạng code mà không ảnh hưởng đến chức năng. Nén là quá trình giảm kích thước file bằng các thuật toán nén.
Minify:
// Trước khi minify
function calculateTotal(items) {
// Tính tổng giá trị
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].price;
}
return total;
}
// Sau khi minify
function calculateTotal(e) {
let t = 0;
for (let n = 0; n < e.length; n++) t += e[n].price;
return t;
}
Nén:
Hai thuật toán nén phổ biến nhất là:
- Gzip: Thuật toán nén phổ biến, được hỗ trợ bởi hầu hết các trình duyệt và server.
- Brotli: Thuật toán nén hiện đại hơn, cung cấp tỷ lệ nén tốt hơn Gzip khoảng 15-25%.
Để bật nén trên server, bạn có thể cấu hình như sau:
Apache (.htaccess):
<IfModule mod_deflate.c>
# Bật Gzip
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/x-javascript application/json
</IfModule>
Nginx:
gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
Brotli trên Nginx:
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
Kết quả của việc minify và nén có thể giảm kích thước file xuống đáng kể:
- HTML: giảm 25-50%
- CSS: giảm 60-80%
- JavaScript: giảm 60-80%
Caching hiệu quả
Caching cho phép trình duyệt lưu trữ các tài nguyên tĩnh và tái sử dụng chúng cho các lần truy cập tiếp theo, giảm số lượng request và tải server.
HTTP Cache-Control:
# Cache tài nguyên tĩnh trong 1 năm
Cache-Control: public, max-age=31536000, immutable
# Cache HTML trong thời gian ngắn
Cache-Control: public, max-age=3600
Versioning và Cache Busting:
Để đảm bảo người dùng nhận được phiên bản mới nhất của tài nguyên khi bạn cập nhật, sử dụng versioning hoặc content hashing:
<!-- Sử dụng version number -->
<link rel="stylesheet" href="styles.css?v=1.2.3" />
<!-- Sử dụng content hash (tốt hơn) -->
<link rel="stylesheet" href="styles.a8f5e91.css" />
Trong các build tools hiện đại như Webpack, Vite, hoặc Rollup, content hashing được tích hợp sẵn:
// webpack.config.js
module.exports = {
output: {
filename: "[name].[contenthash].js",
},
};
Sử dụng CDN (Content Delivery Network)
CDN là mạng lưới các server phân bố trên toàn cầu, giúp phân phối nội dung gần với người dùng hơn, giảm độ trễ và tăng tốc độ tải.
Lợi ích của CDN:
- Giảm độ trễ: Người dùng tải nội dung từ server gần nhất
- Cải thiện khả năng chịu tải: Phân phối tải trên nhiều server
- Bảo vệ khỏi DDoS: Nhiều CDN cung cấp tính năng bảo mật
- Tối ưu hóa tự động: Nén, minify, và chuyển đổi hình ảnh
Các CDN phổ biến:
- Cloudflare
- Akamai
- Amazon CloudFront
- Google Cloud CDN
- Fastly
Ví dụ cấu hình CDN cho tài nguyên tĩnh:
<!-- Sử dụng CDN cho thư viện -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.min.js"></script>
<!-- Sử dụng CDN cho tài nguyên của riêng bạn -->
<link
rel="stylesheet"
href="https://static.yourdomain.com/styles.a8f5e91.css"
/>
2. Tối ưu CSS
CSS là tài nguyên chặn render, vì vậy việc tối ưu hóa CSS có thể cải thiện đáng kể thời gian render ban đầu.
Critical CSS
Critical CSS là kỹ thuật trích xuất và inline CSS cần thiết cho nội dung hiển thị trên màn hình đầu tiên (above-the-fold), cho phép trình duyệt render nhanh chóng mà không cần đợi tải toàn bộ CSS.
Ví dụ về Critical CSS:
<head>
<!-- Critical CSS inline -->
<style>
/* CSS cần thiết cho nội dung above-the-fold */
header,
.hero,
.main-nav {
/* Các thuộc tính CSS */
}
</style>
<!-- CSS không quan trọng được tải không đồng bộ -->
<link
rel="preload"
href="styles.css"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript><link rel="stylesheet" href="styles.css" /></noscript>
</head>
Công cụ tạo Critical CSS:
CSS-in-JS vs CSS Modules
Các phương pháp hiện đại để tổ chức CSS giúp tránh CSS không cần thiết và cải thiện khả năng bảo trì.
CSS Modules:
/* Button.module.css */
.button {
padding: 8px 16px;
border-radius: 4px;
background-color: #0070f3;
color: white;
}
import styles from "./Button.module.css";
function Button() {
return <button className={styles.button}>Click me</button>;
}
CSS-in-JS (styled-components):
import styled from "styled-components";
const Button = styled.button`
padding: 8px 16px;
border-radius: 4px;
background-color: #0070f3;
color: white;
`;
function MyComponent() {
return <Button>Click me</Button>;
}
Ưu và nhược điểm:
- CSS Modules: Tách biệt CSS và JS, tốt cho hiệu suất ban đầu, nhưng có thể dẫn đến CSS không sử dụng.
- CSS-in-JS: Chỉ tải CSS cần thiết, tốt cho code splitting, nhưng có thể tăng kích thước bundle JS và runtime overhead.
Loại bỏ CSS không sử dụng
CSS không sử dụng làm tăng kích thước file, thời gian phân tích, và thời gian tải.
Công cụ loại bỏ CSS không sử dụng:
- PurgeCSS: Phân tích mã HTML và JS để xác định CSS được sử dụng
- UnCSS: Loại bỏ CSS không sử dụng bằng cách render trang
Ví dụ cấu hình PurgeCSS với Webpack:
// postcss.config.js
module.exports = {
plugins: [
require("autoprefixer"),
require("@fullhuman/postcss-purgecss")({
content: ["./src/**/*.html", "./src/**/*.vue", "./src/**/*.jsx"],
defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
}),
],
};
3. Tối ưu JavaScript
JavaScript là một trong những nguyên nhân chính gây chậm trang web, đặc biệt trên thiết bị di động.
Code Splitting
Code splitting là kỹ thuật chia nhỏ bundle JavaScript thành các chunk nhỏ hơn, chỉ tải khi cần thiết.
Ví dụ với Webpack:
// Trước khi code splitting
import { Chart } from "chart.js";
// Sau khi code splitting (dynamic import)
button.addEventListener("click", async () => {
const { Chart } = await import("chart.js");
// Sử dụng Chart
});
Ví dụ với React:
import React, { lazy, Suspense } from "react";
// Lazy load component
const HeavyComponent = lazy(() => import("./HeavyComponent"));
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
</div>
);
}
Tree Shaking
Tree shaking là quá trình loại bỏ code không sử dụng (dead code) từ bundle JavaScript.
Ví dụ:
// utils.js
export function formatDate(date) {
/* ... */
}
export function formatCurrency(amount) {
/* ... */
}
export function formatPercentage(value) {
/* ... */
}
// main.js - Chỉ formatDate sẽ được đưa vào bundle
import { formatDate } from "./utils";
console.log(formatDate(new Date()));
Cấu hình Webpack cho tree shaking:
// webpack.config.js
module.exports = {
mode: "production", // Bật tree shaking
optimization: {
usedExports: true,
},
};
Lazy Loading
Lazy loading là kỹ thuật trì hoãn việc tải tài nguyên cho đến khi cần thiết.
Lazy loading components:
// Vue.js
const AdminDashboard = () => import("./AdminDashboard.vue");
const routes = [{ path: "/admin", component: AdminDashboard }];
Lazy loading hình ảnh và iframe:
<!-- Lazy loading hình ảnh -->
<img
src="placeholder.jpg"
data-src="actual-image.jpg"
class="lazy"
alt="Description"
/>
<!-- Lazy loading iframe -->
<iframe
data-src="https://www.youtube.com/embed/video"
class="lazy"
title="Video"
></iframe>
// Triển khai lazy loading với Intersection Observer
document.addEventListener("DOMContentLoaded", function () {
const lazyElements = document.querySelectorAll(".lazy");
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const element = entry.target;
if (element.tagName === "IMG" || element.tagName === "IFRAME") {
element.src = element.dataset.src;
element.classList.remove("lazy");
observer.unobserve(element);
}
}
});
});
lazyElements.forEach((element) => {
observer.observe(element);
});
});
4. Tối ưu hình ảnh và media
Hình ảnh thường chiếm phần lớn kích thước trang web, vì vậy việc tối ưu hóa chúng có thể mang lại cải thiện hiệu suất đáng kể.
Responsive Images
Responsive images giúp cung cấp hình ảnh phù hợp với kích thước và độ phân giải của thiết bị.
Sử dụng srcset và sizes:
<img
srcset="small.jpg 500w, medium.jpg 1000w, large.jpg 1500w"
sizes="(max-width: 600px) 500px, (max-width: 1200px) 1000px, 1500px"
src="fallback.jpg"
alt="Description"
width="800"
height="600"
/>
Sử dụng picture cho các định dạng khác nhau:
<picture>
<source srcset="image.avif" type="image/avif" />
<source srcset="image.webp" type="image/webp" />
<img src="image.jpg" alt="Description" width="800" height="600" />
</picture>
Modern Image Formats
Các định dạng hình ảnh hiện đại như WebP và AVIF cung cấp tỷ lệ nén tốt hơn mà vẫn duy trì chất lượng hình ảnh.
So sánh kích thước file:
Format | Kích thước tương đối | Hỗ trợ trình duyệt |
---|---|---|
JPEG | 100% (cơ sở) | Tất cả |
WebP | 25-35% nhỏ hơn JPEG | 95%+ (trừ IE) |
AVIF | 50-60% nhỏ hơn JPEG | Chrome, Firefox, Opera |
Chuyển đổi hình ảnh sang WebP:
# Sử dụng cwebp (từ Google)
cwebp -q 80 image.jpg -o image.webp
# Sử dụng ImageMagick
convert image.jpg -quality 80 image.webp
Lazy Loading Images
Lazy loading giúp trì hoãn việc tải hình ảnh cho đến khi chúng gần đến viewport.
Sử dụng thuộc tính loading:
<img
src="image.jpg"
loading="lazy"
alt="Description"
width="800"
height="600"
/>
Sử dụng Intersection Observer:
const images = document.querySelectorAll("img[data-src]");
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
images.forEach((img) => observer.observe(img));
Preloading Key Resources
Preloading giúp tải trước các tài nguyên quan trọng, đặc biệt là hình ảnh hero.
<link rel="preload" href="hero-image.jpg" as="image" />
5. Tối ưu font
Font web có thể ảnh hưởng đáng kể đến hiệu suất và trải nghiệm người dùng.
Font Display Strategies
Thuộc tính font-display
kiểm soát cách font được hiển thị trong quá trình tải.
@font-face {
font-family: "MyFont";
src: url("myfont.woff2") format("woff2");
font-display: swap; /* hoặc block, fallback, optional */
}
Các giá trị font-display:
- auto: Chiến lược mặc định của trình duyệt
- block: Thời gian chặn ngắn, sau đó hiển thị font khi tải xong
- swap: Không có thời gian chặn, hiển thị font dự phòng ngay lập tức
- fallback: Thời gian chặn rất ngắn, thời gian hoán đổi ngắn
- optional: Thời gian chặn rất ngắn, không có thời gian hoán đổi
Subset Fonts
Font subsetting là quá trình loại bỏ các ký tự không cần thiết khỏi font, giảm kích thước file.
<!-- Chỉ tải subset Latin -->
<link
href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap&subset=latin"
rel="stylesheet"
/>
Công cụ tạo subset font:
Variable Fonts
Variable fonts cho phép nhiều biến thể (weight, width, slant) trong một file font duy nhất.
@font-face {
font-family: "MyVariableFont";
src: url("myvariablefont.woff2") format("woff2-variations");
font-weight: 100 900;
font-stretch: 75% 125%;
font-style: oblique 0deg 12deg;
}
.light {
font-weight: 300;
}
.bold {
font-weight: 700;
}
Lợi ích của variable fonts:
- Giảm số lượng file font cần tải
- Thường nhỏ hơn tổng kích thước của các file font riêng lẻ
- Cho phép animation mượt mà giữa các biến thể
6. Resource Hints
Resource hints giúp thông báo cho trình duyệt về các tài nguyên mà trang sẽ cần, tối ưu hóa quá trình tải.
Preload
Preload yêu cầu trình duyệt tải tài nguyên càng sớm càng tốt.
<!-- Preload font -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
<!-- Preload CSS -->
<link rel="preload" href="critical.css" as="style" />
<!-- Preload JavaScript -->
<link rel="preload" href="main.js" as="script" />
Prefetch
Prefetch gợi ý trình duyệt tải tài nguyên với độ ưu tiên thấp cho việc sử dụng trong tương lai.
<!-- Prefetch trang tiếp theo -->
<link rel="prefetch" href="/next-page.html" />
<!-- Prefetch JavaScript cho trang tiếp theo -->
<link rel="prefetch" href="/next-page-bundle.js" />
Preconnect
Preconnect thiết lập kết nối sớm đến domain mà trang sẽ tải tài nguyên từ đó.
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
DNS-Prefetch
DNS-prefetch giải quyết DNS sớm, giảm độ trễ khi kết nối đến domain.
<link rel="dns-prefetch" href="//analytics.example.com" />
Kết luận
Các kỹ thuật tối ưu hóa cơ bản này có thể áp dụng cho hầu hết các dự án web và mang lại cải thiện hiệu suất đáng kể. Bằng cách kết hợp các kỹ thuật này, bạn có thể giảm đáng kể thời gian tải trang và cải thiện trải nghiệm người dùng.
graph TD
A[Tối ưu Frontend] --> B[Giảm kích thước tài nguyên]
A --> C[Giảm số lượng request]
A --> D[Tối ưu thứ tự tải]
A --> E[Caching hiệu quả]
B --> F[Thời gian tải nhanh hơn]
C --> F
D --> F
E --> F
F --> G[Cải thiện Core Web Vitals]
G --> H[LCP tốt hơn]
G --> I[FID/INP tốt hơn]
G --> J[CLS tốt hơn]
H --> K[Trải nghiệm người dùng tốt hơn]
I --> K
J --> K
style K fill:#6f6,stroke:#333,stroke-width:2px
style A fill:#f96,stroke:#333,stroke-width:2px
Trong phần tiếp theo, chúng ta sẽ đi sâu vào các kỹ thuật tối ưu hóa cụ thể cho React, một trong những framework phổ biến nhất hiện nay.