Hướng dẫn tạo website miễn phí với Google Sites cho người mới
Google Sites là công cụ tạo website miễn phí của Google, cho phép bất kỳ ai có tài khoản Gmail đều có thể xây dựng một trang web chỉ bằng thao tác kéo thả mà không cần biết một dòng...
Tối ưu JavaScript là một trong những việc có tác động lớn nhất đến tốc độ tải trang và đặc biệt khi Google ngày càng ưu tiên Core Web Vitals trong bộ tiêu chí xếp hạng. Nếu bạn đang thắc mắc tại sao trang web của mình vẫn chậm dù đã tối ưu hình ảnh hay dùng hosting tốt thì rất có thể JavaScript đang là “thủ phạm” chính.
Bài viết này mình sẽ chia sẻ hơn 12 kỹ thuật tối ưu JS từ cơ bản đến nâng cao cùng kèm ví dụ code thực tế và công cụ cụ thể. Đây cũng là những gì mình áp dụng trong các dự án WordPress, Shopify và Next.js tại Phuc Lee để đạt điểm PageSpeed gần tuyệt đối.
Tối ưu JavaScript là quá trình giảm thiểu kích thước, thời gian phân tích (parse) và thực thi (execute) của các file JS nhằm tránh chặn main thread của trình duyệt và cải thiện trải nghiệm người dùng.
Khi trình duyệt tải một trang web nó phải tải xuống, phân tích và thực thi toàn bộ JavaScript trước khi người dùng có thể tương tác. Nếu bundle JS quá lớn hoặc không được tổ chức tốt trang sẽ bị “đóng băng” trong vài giây, dẫn tới ảnh hưởng trực tiếp đến chỉ số TTI (Time to Interactive), LCP (Largest Contentful Paint) và FID (First Input Delay).
Thực tế thì, một trang web có JS được tối ưu tốt có thể cải thiện TTI lên đến 47%, tăng tỷ lệ chuyển đổi gấp đôi và giúp SEO tốt hơn đáng kể. Với Core Web Vitals 2025, Google đặt ngưỡng chuẩn là LCP dưới 2.5 giây, FID dưới 100ms và CLS dưới 0.1. Đây là những con số bạn cần nhắm tới trước khi bắt đầu bất kỳ chiến dịch tối ưu nào.
Trước khi bắt tay vào tối ưu, bạn nên đo lường hiện trạng bằng Lighthouse hoặc Chrome DevTools để biết chính xác đâu là điểm nghẽn. Tối ưu mà không đo trước thì chẳng khác gì chữa bệnh mà không biết bệnh gì.
Minify là kỹ thuật loại bỏ toàn bộ khoảng trắng, comment và ký tự thừa trong file JS mà không làm thay đổi logic code. Kết hợp với việc gộp nhiều file JS nhỏ thành một file duy nhất, bạn có thể giảm kích thước bundle xuống hơn 70% và giảm số lượng HTTP request đáng kể.
Công cụ phổ biến nhất cho việc này là Webpack với plugin Terser. Chỉ cần cấu hình mode: 'production' trong Webpack và quá trình minify sẽ được kích hoạt tự động:
// webpack.config.js
module.exports = {
mode: 'production', // Tự động minify với Terser
};
Với các dự án Shopify, mình thường áp dụng bước này ngay trong pipeline build của theme để đảm bảo file JS xuất ra luôn ở trạng thái tối ưu nhất.
Thay vì tải toàn bộ JavaScript của ứng dụng ngay từ đầu, code splitting cho phép bạn chia bundle thành nhiều chunk nhỏ hơn (lý tưởng dưới 30KB mỗi chunk) và chỉ tải chúng khi thực sự cần thiết.
Trong Next.js và React, bạn có thể dùng dynamic import() để lazy-load component theo route hoặc theo hành động người dùng:
// Next.js dynamic import
import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <p>Đang tải...</p>,
});
Kỹ thuật này có thể giảm TTI từ 30 đến 50%, đặc biệt hiệu quả với các ứng dụng có nhiều tính năng phức tạp. Mình áp dụng code splitting cho hầu hết dự án Next.js và thấy sự khác biệt rõ rệt ngay từ lần đo đầu tiên.
Tree shaking là quá trình phân tích dependency graph của ứng dụng và loại bỏ những đoạn code không bao giờ được gọi đến (dead code). Kỹ thuật này hoạt động tốt nhất với ES Modules (import/export) vì cú pháp tĩnh của nó cho phép bundler phân tích được chính xác những gì được sử dụng.
Với Rollup hoặc Webpack ở chế độ production, tree shaking được bật mặc định. Tuy nhiên, bạn cần đảm bảo các thư viện bạn dùng hỗ trợ ES Modules. Ví như khi import từ lodash, thay vì:
// Tải toàn bộ lodash (70KB+)
import _ from 'lodash';
// Chỉ tải hàm cần dùng (vài KB)
import debounce from 'lodash/debounce';
Cách import có chọn lọc này kết hợp với tree shaking có thể loại bỏ đến 35% code thừa từ dependencies.
Không phải tất cả JavaScript đều cần chạy ngay khi trang vừa tải xong. Với những script không ảnh hưởng đến nội dung hiển thị ban đầu (above-the-fold), bạn hoàn toàn có thể trì hoãn việc tải chúng.
IntersectionObserver là API trình duyệt cho phép bạn theo dõi khi nào một phần tử xuất hiện trong viewport, từ đó kích hoạt tải script đúng lúc:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Load script khi phần tử vào viewport
loadScript('/js/heavy-feature.js');
observer.disconnect();
}
});
});
observer.observe(document.querySelector('#feature-section'));
Bên cạnh đó, requestIdleCallback cho phép bạn lên lịch các tác vụ không khẩn cấp chạy vào thời điểm trình duyệt rảnh rỗi và tránh tranh chấp tài nguyên với các tác vụ quan trọng hơn. Kết hợp hai kỹ thuật này có thể giảm thời gian block main thread đến 40%.
Đây là một trong những cách đơn giản nhất nhưng lại bị bỏ qua nhiều nhất. Khi trình duyệt gặp thẻ <script> thông thường, nó sẽ dừng parse HTML lại để tải và thực thi JS. Thêm thuộc tính async hoặc defer sẽ thay đổi hoàn toàn hành vi này:
<!-- Script tải song song, thực thi ngay khi tải xong -->
<script async src="/js/analytics.js"></script>
<!-- Script tải song song, thực thi sau khi HTML parse xong -->
<script defer src="/js/main.js"></script>
Khi thêm thuộc tính defer trong thẻ <script> HTML thường là lựa chọn tốt hơn cho hầu hết script vì nó đảm bảo thứ tự thực thi và không làm gián đoạn quá trình render trang.
Third-party script như Google Analytics, Facebook Pixel, chat widget hay các tracking tool khác thường là nguyên nhân hàng đầu khiến trang web chậm mà bạn không kiểm soát được. Mỗi script bên thứ ba thêm vào là thêm một DNS lookup, một kết nối TCP và một lượng JS cần thực thi.
Cách tiếp cận mình hay dùng là lazy-load tất cả third-party script sau khi trang đã tải xong và người dùng bắt đầu tương tác:
// Tải Google Analytics sau khi user tương tác lần đầu
window.addEventListener('scroll', () => {
loadGoogleAnalytics();
}, { once: true });
Ngoài ra, hãy thường xuyên audit lại danh sách third-party script và loại bỏ những cái không còn cần thiết. Thực tế thì nhiều website đang chạy 5-10 tracking script mà không ai nhớ tại sao lại cài chúng.
Ngoài ra bạn có thể xem thêm cách Delay GTM và sGTM giúp tối ưu tốc độ website của mình.
Các sự kiện như scroll, resize hay mousemove có thể kích hoạt hàng trăm lần mỗi giây. Nếu bạn gắn logic phức tạp vào những sự kiện này mà không kiểm soát tần suất, CPU sẽ bị quá tải và trang web sẽ giật lag rõ rệt.
Debounce trì hoãn thực thi hàm cho đến khi sự kiện ngừng xảy ra trong một khoảng thời gian nhất định, còn throttle giới hạn số lần hàm được gọi trong một khoảng thời gian:
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
// Chỉ xử lý sau khi user ngừng scroll 100ms
window.addEventListener('scroll', debounce(handleScroll, 100));
// Chỉ xử lý tối đa 1 lần mỗi 200ms khi resize
window.addEventListener('resize', throttle(handleResize, 200));
Áp dụng đúng hai kỹ thuật này có thể giảm mức sử dụng CPU từ 25 đến 35% trong các tình huống tương tác nặng.
Main thread của trình duyệt chịu trách nhiệm cho cả việc render UI lẫn thực thi JavaScript. Nếu bạn có những tác vụ tính toán nặng như xử lý ảnh, mã hóa dữ liệu hay phân tích file lớn, hãy chuyển chúng sang Web Worker để chạy trên thread riêng biệt:
// main.js
const worker = new Worker('/js/heavy-worker.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (e) => {
console.log('Kết quả:', e.data.result);
};
// heavy-worker.js
self.onmessage = (e) => {
const result = processHeavyData(e.data.data);
self.postMessage({ result });
};
Nhờ vậy, UI vẫn mượt mà và phản hồi người dùng trong khi tác vụ nặng đang chạy ngầm.
Với những script quan trọng cho nội dung hiển thị ban đầu, bạn có thể dùng <link rel="preload"> để báo cho trình duyệt tải chúng sớm hơn với độ ưu tiên cao:
<!-- Preload script quan trọng -->
<link rel="preload" href="/js/critical.js" as="script">
<!-- Prefetch script cho trang tiếp theo -->
<link rel="prefetch" href="/js/next-page.js">
preload dành cho tài nguyên cần thiết ngay ở trang hiện tại, còn prefetch dành cho tài nguyên có thể cần ở trang tiếp theo. Kết hợp hai kỹ thuật này giúp trải nghiệm điều hướng giữa các trang mượt mà hơn rõ rệt.
Với các ứng dụng lớn, module federation (tính năng của Webpack 5) cho phép bạn chia ứng dụng thành nhiều module độc lập, mỗi module có thể được build và deploy riêng biệt. Nhờ đó, build time giảm đáng kể và các team có thể làm việc song song mà không ảnh hưởng lẫn nhau.
Kỹ thuật này đặc biệt phù hợp với các dự án enterprise hoặc khi bạn cần tích hợp nhiều ứng dụng frontend khác nhau vào một giao diện thống nhất.
eval(), new Function() và with statement là những pattern mà V8 engine (JavaScript engine của Chrome/Node.js) không thể tối ưu được. Ngoài vấn đề bảo mật, chúng còn ngăn engine áp dụng các kỹ thuật tối ưu như JIT compilation, khiến code chạy chậm hơn đáng kể.
Bên cạnh đó, hãy tránh tạo quá nhiều closure không cần thiết, tránh thay đổi kiểu dữ liệu của biến liên tục (type coercion) và hạn chế truy cập DOM trong vòng lặp. Những thói quen code nhỏ này cộng dồn lại có thể tạo ra sự khác biệt lớn về hiệu năng.
Cấu hình cache đúng cách giúp trình duyệt không phải tải lại JS file trong mỗi lần truy cập. Kết hợp với CDN (Content Delivery Network), file JS sẽ được phục vụ từ server gần nhất với người dùng, giảm latency đáng kể.
Chiến lược cache hiệu quả nhất là dùng content hashing trong tên file (ví dụ main.a3f9b2.js). Khi code thay đổi, hash thay đổi theo, trình duyệt sẽ tải file mới. Khi code không đổi, trình duyệt dùng cache. Kết hợp CDN và cache đúng cách có thể giảm load time đến 50% cho người dùng quay lại.
Để bạn dễ hình dung và ưu tiên áp dụng, mình tổng hợp lại hiệu quả của từng kỹ thuật trong bảng sau:
| Kỹ thuật | Giảm kích thước bundle | Cải thiện TTI | Độ khó | Công cụ khuyến nghị |
|---|---|---|---|---|
| Minify & Merge | ~70% | ~20% | Dễ | Webpack + Terser |
| Code Splitting | ~47% | 30-50% | Trung bình | Next.js, Webpack |
| Tree Shaking | ~35% | ~25% | Dễ | Rollup, Webpack |
| Lazy Loading | ~40% | ~40% | Trung bình | IntersectionObserver |
| Async/Defer | Không đổi | ~15% | Rất dễ | Native HTML |
| Tối ưu 3rd-party | 20-40% | ~30% | Trung bình | DevTools, Partytown |
| Debounce/Throttle | Không đổi | ~25% | Dễ | Lodash |
| Web Workers | Không đổi | ~35% | Khó | Native API |
| Preload/Prefetch | Không đổi | ~10% | Dễ | Native HTML |
| Browser Cache + CDN | Không đổi | ~50% (repeat) | Trung bình | Cloudflare, Nginx |
Tối ưu mà không đo lường thì không biết mình đang đi đúng hướng hay không. Mình thường dùng bộ công cụ kiểm tra tốc độ website sau trong mọi dự án:
Lighthouse là điểm khởi đầu tốt nhất. Chạy audit ngay trong Chrome DevTools (F12 > Lighthouse), bạn sẽ thấy ngay điểm số Core Web Vitals, danh sách các vấn đề JS cụ thể và gợi ý cải thiện. Mình thường chạy Lighthouse trên cả desktop lẫn mobile vì kết quả có thể chênh nhau khá nhiều.
Webpack Bundle Analyzer là công cụ không thể thiếu khi làm việc với các dự án dùng Webpack. Nó tạo ra một bản đồ trực quan của bundle giúp bạn thấy ngay thư viện nào đang chiếm dung lượng lớn nhất và có thể được thay thế hoặc lazy-load.
Chrome DevTools Performance tab cho phép bạn record và phân tích chi tiết từng millisecond của quá trình tải trang, bao gồm thời gian parse JS, thời gian thực thi từng function và các điểm gây block main thread.
Ngoài ra, Web Vitals Chrome Extension giúp bạn theo dõi Core Web Vitals real-time trên bất kỳ trang web nào nó rất tiện để so sánh trước và sau khi tối ưu.
Có một nguyên tắc mình luôn nhắc nhở bản thân và khách hàng: đo trước, tối ưu sau. Đừng tối ưu mọi thứ cùng lúc vì bạn sẽ không biết cái gì thực sự tạo ra sự khác biệt. Thay vào đó, hãy tập trung vào 20% vấn đề đang gây ra 80% sự chậm trễ.
Một số nguyên tắc thực tế khác mình rút ra từ kinh nghiệm triển khai:
Thứ nhất, hãy áp dụng progressive enhancement. Trang web phải hoạt động được ngay cả khi JavaScript bị tắt hoặc chưa tải xong. Điều này không chỉ tốt cho hiệu năng mà còn tốt cho accessibility và SEO.
Thứ hai, đừng over-engineer. Không phải dự án nào cũng cần micro-frontends hay Web Workers. Với một landing page đơn giản, minify và defer script là đã đủ. Với ứng dụng Next.js phức tạp, bạn mới cần đến code splitting và module federation.
Thứ ba, kiểm tra trên thiết bị thực. Lighthouse chạy trên máy tính của bạn sẽ cho kết quả khác với điện thoại Android tầm trung của người dùng thực. Hãy dùng Chrome DevTools với tính năng CPU throttling để mô phỏng thiết bị yếu hơn.
Cuối cùng, tối ưu là quá trình liên tục. Mỗi lần thêm thư viện mới hoặc mỗi lần cập nhật tính năng đều có thể ảnh hưởng đến hiệu năng. Hãy tích hợp Lighthouse vào CI/CD pipeline để phát hiện regression sớm.
Tối ưu JavaScript có khó không? Tôi không phải developer chuyên nghiệp thì có làm được không?
Phụ thuộc vào kỹ thuật bạn muốn áp dụng. Những cách như thêm async/defer vào script tag, lazy-load third-party script hay bật minify trong plugin WordPress (như WP Rocket, LiteSpeed Cache) thì rất đơn giản, không cần biết code. Còn code splitting, Web Workers hay module federation thì cần kiến thức JavaScript nhất định.
Tối ưu JavaScript có ảnh hưởng đến SEO không?
Có, và ảnh hưởng khá lớn. Google sử dụng Core Web Vitals (LCP, FID, CLS) như một tín hiệu xếp hạng. JavaScript chậm ảnh hưởng trực tiếp đến LCP và FID. Ngoài ra, Googlebot cũng cần thực thi JavaScript để render nội dung nên JS quá nặng có thể khiến bot không crawl được đầy đủ nội dung trang.
Minify và compress (gzip/brotli) khác nhau như thế nào?
Minify loại bỏ ký tự thừa trong source code (khoảng trắng, comment), còn compress (gzip/brotli) nén file ở tầng server trước khi gửi đến trình duyệt. Hai kỹ thuật này bổ sung cho nhau và bạn nên áp dụng cả hai. Minify giảm kích thước file gốc, compress giảm thêm kích thước khi truyền qua mạng.
Nên dùng async hay defer cho script?
Thông thường thì defer là lựa chọn an toàn hơn cho hầu hết script vì nó đảm bảo thứ tự thực thi và chỉ chạy sau khi HTML parse xong. Còn đối với async thì phù hợp với script độc lập, không phụ thuộc vào DOM hay script khác ví dụ như Google Analytics.
Làm sao biết thư viện nào đang làm bundle JS phình to?
Dùng Webpack Bundle Analyzer hoặc bundlephobia.com để kiểm tra kích thước của từng thư viện trước khi cài. Với dự án đang chạy, chạy npx webpack-bundle-analyzer sẽ cho bạn thấy ngay “thủ phạm” đang chiếm dung lượng lớn nhất.
Tree shaking có hoạt động với CommonJS (require) không?
Không hiệu quả. Tree shaking hoạt động tốt nhất với ES Modules (import/export) vì cú pháp tĩnh của nó cho phép bundler phân tích được dependency graph tại compile time. CommonJS dùng require() động nên bundler không thể biết chính xác những gì được dùng.
Tôi dùng WordPress thì tối ưu JavaScript bằng cách nào?
Với WordPress, bạn có thể bắt đầu với plugin như WP Rocket, LiteSpeed Cache hoặc Autoptimize để minify, defer và lazy-load JS mà không cần code. Tuy nhiên, nếu theme hoặc plugin đang load quá nhiều JS không cần thiết, bạn cần can thiệp ở tầng code hoặc dùng plugin như Asset CleanUp để disable script theo từng trang.
Tối ưu JavaScript không phải là việc làm một lần rồi thôi mà là một thói quen cần duy trì trong suốt vòng đời của dự án. Bắt đầu từ những kỹ thuật đơn giản như minify, async/defer và lazy-load third-party script, sau đó tiến dần đến code splitting và tree shaking khi bạn đã quen tay hơn.
Điểm mấu chốt là luôn đo lường trước và sau mỗi thay đổi. Lighthouse và Chrome DevTools là hai người bạn đồng hành không thể thiếu trong hành trình này.
Nếu bạn đang cần một website tốc độ cao, chuẩn SEO và không muốn mất thời gian tự mày mò từng kỹ thuật, mình (Phuc Lee) có thể giúp bạn. Mình chuyên tư vấn và triển khai website trên WordPress, Shopify và Next.js với hiệu năng tối ưu ngay từ đầu, không cần phải lo lắng quá nhiều sau này. Ghé qua phuclee.com để tìm hiểu thêm hoặc liên hệ trực tiếp để được tư vấn miễn phí nhé!