Tối ưu hóa hiệu suất cho tất cả các framework frontend
Bài viết này tập trung vào các kỹ thuật tối ưu hóa hiệu suất có thể áp dụng cho mọi framework frontend hiện đại, bao gồm React, Vue, Angular, Svelte, Solid và các framework khác. Những nguyên tắc này giúp cải thiện trải nghiệm người dùng bất kể công nghệ nào bạn đang sử dụng.
Mục lục
- Tối ưu hóa bundle size
- Code-splitting và lazy-loading
- Tối ưu hóa rendering
- Quản lý state hiệu quả
- Tối ưu hóa hình ảnh và media
- Caching và memoization
- Server-side rendering và Static site generation
- Tối ưu hóa Web Vitals
- Theo dõi và phân tích hiệu suất
- Tổng kết
Tối ưu hóa bundle size
Kích thước bundle là yếu tố quan trọng ảnh hưởng đến thời gian tải trang. Dưới đây là các kỹ thuật giúp giảm kích thước bundle cho mọi framework:
1. Tree shaking
Tree shaking là quá trình loại bỏ code không sử dụng khỏi bundle cuối cùng. Hầu hết các bundler hiện đại (webpack, Rollup, esbuild, Vite) đều hỗ trợ tree shaking.
// Thay vì import toàn bộ thư viện
import _ from "lodash";
// Chỉ import những gì bạn cần
import map from "lodash/map";
import filter from "lodash/filter";
2. Phân tích bundle
Sử dụng các công cụ như webpack-bundle-analyzer
, rollup-plugin-visualizer
hoặc vite-bundle-visualizer
để phân tích kích thước bundle và xác định các dependency lớn.
# Cài đặt webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer
# Hoặc với Vite
npm install --save-dev vite-bundle-visualizer
3. Sử dụng các thư viện nhỏ gọn
Ưu tiên các thư viện nhỏ gọn hoặc các phiên bản nhẹ của thư viện phổ biến.
// Thay vì moment.js (nặng)
import moment from "moment";
// Sử dụng date-fns (nhẹ hơn nhiều)
import { format, addDays } from "date-fns";
// Hoặc sử dụng Day.js (API tương tự moment nhưng nhẹ hơn)
import dayjs from "dayjs";
4. Compression
Đảm bảo server của bạn hỗ trợ nén Gzip hoặc Brotli cho tất cả các tài nguyên tĩnh.
# Cấu hình Nginx cho Gzip
gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
Code-splitting và lazy-loading
Chia nhỏ ứng dụng thành các chunk nhỏ hơn và chỉ tải khi cần thiết giúp cải thiện thời gian tải trang ban đầu.
1. Dynamic import
Tất cả các framework hiện đại đều hỗ trợ dynamic import để lazy-load các component và module.
// React
const LazyComponent = React.lazy(() => import("./LazyComponent"));
// Vue 3
const LazyComponent = () => import("./LazyComponent.vue");
// Angular
const routes = [
{
path: "lazy",
loadChildren: () => import("./lazy/lazy.module").then((m) => m.LazyModule),
},
];
// Svelte với SvelteKit
const LazyComponent = () => import("./LazyComponent.svelte");
2. Route-based code splitting
Chia code theo route là cách hiệu quả để giảm kích thước bundle ban đầu.
// React Router
import { lazy } from "react";
const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));
const Contact = lazy(() => import("./pages/Contact"));
// Vue Router
const routes = [
{
path: "/",
component: () => import("./pages/Home.vue"),
},
{
path: "/about",
component: () => import("./pages/About.vue"),
},
];
3. Component-level code splitting
Lazy-load các component lớn hoặc ít sử dụng.
// Lazy-load một modal dialog chỉ khi cần
const Modal = lazy(() => import("./components/Modal"));
function App() {
const [showModal, setShowModal] = useState(false);
return (
<div>
<button onClick={() => setShowModal(true)}>Open Modal</button>
{showModal && (
<Suspense fallback={<div>Loading...</div>}>
<Modal onClose={() => setShowModal(false)} />
</Suspense>
)}
</div>
);
}
Tối ưu hóa rendering
Tối ưu hóa quá trình rendering giúp giảm thiểu thời gian xử lý và cải thiện độ mượt của UI.
1. Virtualization
Sử dụng virtualization cho danh sách dài hoặc bảng có nhiều dữ liệu.
// React với react-window
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={500}
width="100%"
itemCount={items.length}
itemSize={35}
>
{Row}
</FixedSizeList>
);
}
// Vue với vue-virtual-scroller
<template>
<RecycleScroller
class="scroller"
:items="items"
:item-size="32"
key-field="id"
>
<template v-slot="{ item }">
<div class="user">
{{ item.name }}
</div>
</template>
</RecycleScroller>
</template>
2. Tránh reflow và repaint không cần thiết
Nhóm các thay đổi DOM và sử dụng các thuộc tính CSS hiệu quả.
// Kém hiệu quả - gây nhiều reflow
function animateBadly() {
const element = document.getElementById("my-element");
for (let i = 0; i < 100; i++) {
element.style.left = i + "px"; // Mỗi thay đổi gây reflow
}
}
// Tốt hơn - sử dụng CSS transforms
function animateWell() {
const element = document.getElementById("my-element");
element.style.transition = "transform 1s";
element.style.transform = "translateX(100px)";
}
3. Sử dụng CSS containment
Giúp trình duyệt tối ưu hóa rendering bằng cách cô lập các phần của trang.
.container {
contain: content;
}
.card {
contain: layout style paint;
}
4. Content-visibility và contain-intrinsic-size
Sử dụng content-visibility: auto
để trì hoãn rendering các phần tử ngoài viewport.
.section {
content-visibility: auto;
contain-intrinsic-size: 500px; /* Ước tính kích thước */
}
Quản lý state hiệu quả
Quản lý state kém hiệu quả có thể dẫn đến re-render không cần thiết và làm giảm hiệu suất.
1. Phân chia state
Chia nhỏ state thành các phần độc lập để tránh re-render toàn bộ ứng dụng.
// React với Context API
const UserContext = createContext();
const ThemeContext = createContext();
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState("light");
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ theme, setTheme }}>
<MainContent />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
// Vue với Pinia
// userStore.js
export const useUserStore = defineStore("user", {
state: () => ({ user: null }),
actions: {
setUser(user) {
this.user = user;
},
},
});
// themeStore.js
export const useThemeStore = defineStore("theme", {
state: () => ({ theme: "light" }),
actions: {
setTheme(theme) {
this.theme = theme;
},
},
});
2. Immutability
Sử dụng cấu trúc dữ liệu bất biến để dễ dàng phát hiện thay đổi và tối ưu hóa rendering.
// Kém hiệu quả - thay đổi trực tiếp
function addTodo(todos, newTodo) {
todos.push(newTodo);
return todos;
}
// Tốt hơn - sử dụng immutability
function addTodo(todos, newTodo) {
return [...todos, newTodo];
}
// Hoặc sử dụng thư viện như Immer
import produce from "immer";
function addTodo(todos, newTodo) {
return produce(todos, (draft) => {
draft.push(newTodo);
});
}
3. Memoization của component
Sử dụng các kỹ thuật memoization để tránh re-render không cần thiết.
// React với memo, useMemo và useCallback
const MemoizedComponent = React.memo(function MyComponent(props) {
return <div>{props.name}</div>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
// Chỉ tạo lại hàm khi dependencies thay đổi
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
// Chỉ tính toán lại khi dependencies thay đổi
const expensiveValue = useMemo(() => {
return computeExpensiveValue(count);
}, [count]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<MemoizedComponent name="John" onClick={handleClick} />
</div>
);
}
// Vue với computed và v-memo
<template>
<div>
<button @click="count++">Increment</button>
<div v-memo="[name]">{{ name }}</div>
<div>{{ expensiveValue }}</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const count = ref(0);
const name = ref('John');
const expensiveValue = computed(() => {
return computeExpensiveValue(count.value);
});
</script>
Tối ưu hóa hình ảnh và media
Hình ảnh và media thường chiếm phần lớn kích thước trang web và có thể ảnh hưởng đáng kể đến hiệu suất.
1. Lazy-loading hình ảnh
Sử dụng thuộc tính loading="lazy"
hoặc Intersection Observer API.
<!-- Native lazy-loading -->
<img
src="image.jpg"
loading="lazy"
alt="Description"
width="800"
height="600"
/>
<!-- Với Intersection Observer -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const images = document.querySelectorAll(".lazy-image");
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);
});
});
</script>
2. Responsive images
Sử dụng srcset
và sizes
để cung cấp hình ảnh phù hợp với kích thước màn hình.
<img
src="small.jpg"
srcset="small.jpg 500w, medium.jpg 1000w, large.jpg 1500w"
sizes="(max-width: 600px) 500px, (max-width: 1200px) 1000px, 1500px"
alt="Responsive image"
width="800"
height="600"
/>
3. Modern image formats
Sử dụng các định dạng hiện đại như WebP và AVIF với fallback.
<picture>
<source type="image/avif" srcset="image.avif" />
<source type="image/webp" srcset="image.webp" />
<img src="image.jpg" alt="Description" width="800" height="600" />
</picture>
4. Image CDN và optimization services
Sử dụng các dịch vụ như Cloudinary, Imgix hoặc Cloudflare Images để tự động tối ưu hóa hình ảnh.
<!-- Cloudinary example -->
<img
src="https://res.cloudinary.com/demo/image/upload/w_800,f_auto,q_auto/sample.jpg"
alt="Optimized with Cloudinary"
width="800"
height="600"
/>
Caching và memoization
Caching giúp tránh tính toán lại các giá trị hoặc tải lại dữ liệu không thay đổi.
1. Memoization functions
Tạo các hàm memoized để cache kết quả của các tính toán phức tạp.
// Memoization đơn giản
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// Sử dụng
const expensiveCalculation = memoize((a, b) => {
console.log("Calculating...");
return a * b;
});
console.log(expensiveCalculation(4, 2)); // Logs: Calculating... 8
console.log(expensiveCalculation(4, 2)); // Logs: 8 (từ cache)
2. HTTP Caching
Sử dụng HTTP caching headers để tối ưu hóa việc tải lại tài nguyên.
// Cấu hình Express.js
app.use(
express.static("public", {
etag: true,
lastModified: true,
maxAge: "1d", // Cache trong 1 ngày
setHeaders: (res, path) => {
if (path.endsWith(".html")) {
// Không cache file HTML
res.setHeader("Cache-Control", "no-cache");
} else if (path.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/)) {
// Cache tài nguyên tĩnh
res.setHeader("Cache-Control", "public, max-age=31536000"); // 1 năm
}
},
})
);
3. Service Worker caching
Sử dụng Service Workers để cache tài nguyên và cung cấp trải nghiệm offline.
// service-worker.js
const CACHE_NAME = "my-site-cache-v1";
const urlsToCache = [
"/",
"/styles/main.css",
"/scripts/main.js",
"/images/logo.png",
];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
})
);
});
4. Data caching với SWR hoặc React Query
Sử dụng thư viện caching data để tối ưu hóa các request API.
// React với SWR
import useSWR from "swr";
function Profile() {
const { data, error } = useSWR("/api/user", fetcher, {
revalidateOnFocus: false,
dedupingInterval: 60000, // 1 phút
});
if (error) return <div>Failed to load</div>;
if (!data) return <div>Loading...</div>;
return <div>Hello {data.name}!</div>;
}
// Vue với VueQuery
import { useQuery } from "vue-query";
export default {
setup() {
const { data, isLoading, error } = useQuery("users", fetchUsers, {
staleTime: 60000, // 1 phút
cacheTime: 900000, // 15 phút
});
return { data, isLoading, error };
},
};
Server-side rendering và Static site generation
SSR và SSG giúp cải thiện thời gian tải trang đầu tiên và SEO.
1. Framework-agnostic approaches
Các nguyên tắc SSR và SSG có thể áp dụng cho mọi framework.
// Ví dụ đơn giản về SSR với Express và React
import express from "express";
import React from "react";
import { renderToString } from "react-dom/server";
import App from "./App";
const app = express();
app.get("/", (req, res) => {
const html = renderToString(<App />);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>My SSR App</title>
</head>
<body>
<div id="root">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
app.listen(3000);
2. Framework-specific solutions
Mỗi framework có giải pháp riêng cho SSR và SSG.
# Next.js (React)
npx create-next-app my-next-app
# Nuxt.js (Vue)
npx create-nuxt-app my-nuxt-app
# SvelteKit (Svelte)
npm create svelte@latest my-sveltekit-app
# Angular Universal
ng add @nguniversal/express-engine
3. Incremental Static Regeneration (ISR)
ISR kết hợp lợi ích của SSG và SSR, cho phép tạo lại trang tĩnh theo thời gian.
// Next.js với ISR
export async function getStaticProps() {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
return {
props: { data },
revalidate: 60, // Tái tạo trang sau 60 giây
};
}
export async function getStaticPaths() {
return {
paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
fallback: "blocking", // Tạo trang mới theo yêu cầu
};
}
Tối ưu hóa Web Vitals
Tối ưu hóa Core Web Vitals giúp cải thiện SEO và trải nghiệm người dùng.
1. Largest Contentful Paint (LCP)
Tối ưu hóa thời gian hiển thị phần tử lớn nhất trong viewport.
<!-- Preload hero image -->
<link rel="preload" as="image" href="/hero.jpg" fetchpriority="high" />
<!-- Inline critical CSS -->
<style>
/* Critical CSS cho above-the-fold content */
</style>
<!-- Defer non-critical CSS -->
<link
rel="stylesheet"
href="/styles.css"
media="print"
onload="this.media='all'"
/>
2. First Input Delay (FID) và Interaction to Next Paint (INP)
Cải thiện khả năng phản hồi của trang web.
// Chia nhỏ JavaScript với dynamic import
const heavyComponent = () => import("./HeavyComponent");
// Sử dụng Web Worker cho tác vụ nặng
const worker = new Worker("./worker.js");
worker.postMessage({ data: complexData });
worker.onmessage = (e) => {
updateUI(e.data);
};
// Tối ưu hóa event handlers
function optimizedScrollHandler() {
if (scrollTimeout) {
cancelAnimationFrame(scrollTimeout);
}
scrollTimeout = requestAnimationFrame(() => {
// Xử lý scroll event
});
}
window.addEventListener("scroll", optimizedScrollHandler, { passive: true });
3. Cumulative Layout Shift (CLS)
Giảm thiểu sự dịch chuyển bố cục không mong muốn.
<!-- Đặt kích thước cho hình ảnh -->
<img src="image.jpg" width="800" height="600" alt="Description" />
<!-- Dự trữ không gian cho nội dung động -->
<div style="min-height: 200px;">
<div id="dynamic-content"></div>
</div>
<!-- Tối ưu hóa font loading -->
<link
rel="preload"
href="/fonts/my-font.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<style>
@font-face {
font-family: "MyFont";
font-display: swap;
src: url("/fonts/my-font.woff2") format("woff2");
}
</style>
Theo dõi và phân tích hiệu suất
Theo dõi hiệu suất liên tục giúp phát hiện và khắc phục vấn đề sớm.
1. Lighthouse và PageSpeed Insights
Sử dụng Lighthouse để phân tích hiệu suất trang web.
# Cài đặt Lighthouse CLI
npm install -g lighthouse
# Chạy Lighthouse
lighthouse https://example.com --view
2. Web Vitals monitoring
Theo dõi Core Web Vitals trong môi trường thực tế.
import { getCLS, getFID, getLCP, getFCP, getTTFB } from "web-vitals";
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
id: "user-id",
page: window.location.pathname,
});
// Sử dụng Beacon API nếu có thể
if (navigator.sendBeacon) {
navigator.sendBeacon("/analytics", body);
} else {
fetch("/analytics", {
body,
method: "POST",
keepalive: true,
});
}
}
// Theo dõi tất cả các metrics
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
getFCP(sendToAnalytics);
getTTFB(sendToAnalytics);
3. Performance monitoring trong CI/CD
Tích hợp kiểm tra hiệu suất vào quy trình CI/CD.
# GitHub Actions workflow
name: Performance Testing
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "16"
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Start server
run: npm run start & npx wait-on http://localhost:3000
- name: Run Lighthouse
run: |
npm install -g @lhci/cli
lhci autorun
4. User-centric performance metrics
Theo dõi các metrics quan trọng đối với trải nghiệm người dùng.
// Theo dõi thời gian tương tác
const interactions = {};
document.addEventListener("click", (e) => {
const target = e.target.closest("button, a");
if (target) {
const id = target.id || target.href || "unknown";
interactions[id] = {
startTime: performance.now(),
};
}
});
// Theo dõi khi UI cập nhật
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Tìm tương tác gần nhất
const now = performance.now();
for (const [id, data] of Object.entries(interactions)) {
if (now - data.startTime < 1000) {
// Trong vòng 1 giây
sendToAnalytics({
name: "interaction-to-paint",
value: now - data.startTime,
id: id,
});
delete interactions[id];
}
}
}
});
observer.observe({ entryTypes: ["paint"] });
Tổng kết
Tối ưu hóa hiệu suất là một quá trình liên tục đòi hỏi sự kết hợp của nhiều kỹ thuật. Bằng cách áp dụng các nguyên tắc trong bài viết này, bạn có thể cải thiện đáng kể hiệu suất ứng dụng frontend, bất kể framework nào bạn đang sử dụng.
Hãy nhớ rằng:
- Tối ưu hóa bundle size bằng tree shaking và phân tích bundle
- Sử dụng code-splitting và lazy-loading để giảm thời gian tải ban đầu
- Tối ưu hóa rendering với virtualization và tránh reflow không cần thiết
- Quản lý state hiệu quả để tránh re-render không cần thiết
- Tối ưu hóa hình ảnh và media với lazy-loading và định dạng hiện đại
- Sử dụng caching và memoization để tránh tính toán lại
- Cân nhắc SSR và SSG để cải thiện thời gian tải trang đầu tiên
- Tối ưu hóa Core Web Vitals (LCP, FID/INP, CLS)
- Theo dõi và phân tích hiệu suất liên tục
Bằng cách kết hợp các kỹ thuật này, bạn có thể tạo ra ứng dụng frontend nhanh, mượt mà và thân thiện với người dùng, bất kể công nghệ nào bạn đang sử dụng.
Ở các phần tiếp theo mình sẽ đề cập dến các kỹ thuật tối ưu cụ thể cho Reatjs và Vue nhé.
Và đừng quên bạn có góp gì thì để lại bình cho mình biết với nhé.