Tăng điểm page speed cho blog này từ 91 lên 100

mở đầu

Sau nhiều năm lười biếng, giờ là lúc mình có động lực tạo một blog cá nhân. Động cơ xuất phát từ việc, càng già thì mình càng giỏi và giàu hơn có nhiều trăn trở hơn. Do đó mình tạo trang này để lưu lại (cũng như nhìn lại) suy nghĩ của bản thân qua năm tháng.

Mục đích đã có, giờ là lúc bắt tay vào việc nào. Vào wordpress.com đăng kí tài khoản, viết và xuất bản thôi 👌. Nếu vậy thì sẽ không có bài post này 🤣. Nhìn từ góc độ ROI (return of investment) thì Wordpress là phương án phù hợp nhất. Tuy nhiên, đây là trang blog cá nhân nên mình sẽ tiếp cận theo hướng thiên technical một chút để còn múa máy 🕺 cũng như cá nhân hóa vài thành phần trong trang.

Để tiết kiệm thời gian thì mình đã fork lại repo này astro-theme-cactus. Sau khi xóa bớt vài thành phần không cần thiết cũng như thêm bài blog đầu tiên, mình thử đo điểm page speed thì được 91 điểm. Điểm này thì khá ổn rồi, nhưng mình mang tiếng làm FE mà không được 100 điểm thì kém quá khá rảnh nên mình tìm cách để tối ưu nó lên 100 điểm 😌.

Sau đây là một vài giải pháp mình đã áp dụng để cải thiện điểm.

ảnh

  • trong trang này, mình dùng đúng một ảnh (là cái avatar ở trên kìa) và nó cũng khá nhẹ nên không tối ưu cũng không sao. tuy nhiên, tiện tay thì mình làm luôn.
  • mình dùng squoosh để tối ưu dung lượng ảnh cũng như convert ảnh từ png sang webp. ngoài ra, mình cũng giảm kích cỡ ảnh từ 320x320 (ảnh gốc) xuống 160x160.
  • kết quả của bước này là ảnh giảm từ 2.5KB xuống 0.85KB.

font chữ

  • thay vì sử dụng nhiều font-weight khác nhau, mình chỉ dùng 2 variants là regularsemibold.
  • mình dùng font Space Grotesk. thay vì import font thông qua Google Font CDN như thông thường thì mình tải font về (và lưu vào public) cũng như khai báo style trong thẻ head luôn. tại sao lại làm như này thì xem tiếp nhé.
<!-- bỏ phần này  -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
	href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600&display=swap"
	rel="stylesheet"
/>

<!-- thêm phần này (copy từ content của link ngay trên) -->
<style>
	/* vietnamese */
	@font-face {
		font-family: "Space Grotesk";
		font-style: normal;
		font-weight: 400;
		font-display: swap;
		src: url(/SG_Regular_Vn.woff2) format("woff2");
		unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0,
			U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
	}
	/* latin */
	@font-face {
		font-family: "Space Grotesk";
		font-style: normal;
		font-weight: 400;
		font-display: swap;
		src: url(/SG_Regular_Latin.woff2) format("woff2");
		unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
			U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
			U+FFFD;
	}
	/* vietnamese */
	@font-face {
		font-family: "Space Grotesk";
		font-style: normal;
		font-weight: 600;
		font-display: swap;
		src: url(/SG_Semibold_Vn.woff2) format("woff2");
		unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0,
			U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
	}
	/* latin */
	@font-face {
		font-family: "Space Grotesk";
		font-style: normal;
		font-weight: 600;
		font-display: swap;
		src: url(/SG_Semibold_Latin.woff2) format("woff2");
		unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
			U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
			U+FFFD;
	}
</style>
  • khai báo trực tiếp style trong thẻ head giúp giảm 1 round trip. khi đó, trình duyệt sẽ không phải tải nội dung từ link https://fonts.googleapis.com/css2?… 1 rồi mới đi tải font. tiếp theo, việc này giúp mình kiểm soát được mình sẽ tải font-variant nào. như trong hình, bạn có thể so sánh nội dung link 1 và nội dung thẻ style thì mình đã xóa được font-variant latin-ext là font-variant mình không dùng tới.

  • mình tự host font trong public thay vì dùng thông qua Google Font CDN vì có 2 lý do. thứ nhất, mình không biết Google CDN ở đâu, có nhanh như Cloudflare (là nơi host blog này) không. thứ hai, tự host font cho phép mình kiểm soát việc cache của font thay vì phụ thuộc vào Google CDN.

  • mình thêm fallback font Space Grotesk Fallback vào thẻ head. font này sẽ được hiển thị trong khi font thật Space Grotesk đang được tải. điểm hay của nó là nó đã được tinh chỉnh các thông số để nó hiển thị gần giống font thật. do đó, khi font thật đã được tải xong và hiển thị thay thế fallback font, sự layout shift sẽ được giảm tối đa. để tính các thông số này thì bạn có thể dùng qua tool này của bác Malte Ubl hoặc tự chỉnh tay thôi.

<style>
	@font-face {
		font-family: "Space Grotesk Fallback";
		src: local("Arial");
		ascent-override: 95.64%;
		descent-override: 24.02%;
		line-gap-override: 0%;
		size-adjust: 105.45%;
	}

	/* khai bảo font Space Grotesk (đã show ở trên) */
</style>
  • kết quả của bước này là giảm được 1 round trip, vài chục KB dung lượng font cũng như giảm sự layout shift.

các thư viện JavaScript

  • trong trang này, mình dùng 2 thư viện JavaScriptpagefind để tìm kiếm nội dung và cusdis để bình luận. để giảm thiểu dung lượng tải ban đầu của trang thì mình chỉ tải 2 thư viện này khi chúng được tương tác. cụ thể là chỉ tải pagefind khi người dùng bấm vào biểu tượng tìm kiếm và chỉ tải cusdis khi người dùng cuộn chuột tới cuối trang.
// chỉ load pagefind khi click vào 'openBtn'
const initSearch = async () => {
	const { PagefindUI } = await import("@pagefind/default-ui");
	return new PagefindUI({ element: "#cactus__search" });
};

const openModal = (event) => {
	dialog.showModal();
	initSearch();
};
openBtn.addEventListener("click", openModal);
// chỉ load cusdis khi nó visible
<CusdisWidget attrs={{ host: "https://cusdis.com", appId: "1", pageId: post.id }} client:visible />
  • kết quả của bước này là giảm được 20KB khi mới tải trang.

CSS

  • như đã chia sẻ ở trên, trong trang này mình dùng thư viện pagefind. ngoài phần code JavaScript nêu ở phần trên, thư viện này còn có ~ 10KB CSS code đi kèm. chưa kể khoảng 3KB CSS mình thêm vào để custom việc hiển thị của thư viện này. do thư viện này không cần dùng luôn ngay sau khi tải trang, thay vì import trực tiếp, mình sẽ inject CSS khi người dùng nhấn nút tìm kiếm

styles/search.css

@import "@pagefind/default-ui/css/ui.css";

:root {
	--pagefind-ui-font: inherit;
}

#cactus__search .pagefind-ui__search-clear {
	width: calc(60px * var(--pagefind-ui-scale));
	padding: 0;
	background-color: transparent;
	overflow: hidden;
}

Search.astro

// ...
const initSearch = async () => {
	if (document.querySelector(".pagefind-ui__search-input")) return;

	const searchStyles = await import("../styles/search.css?inline");
	const styleTag = document.createElement("style");
	styleTag.id = "search";
	styleTag.innerHTML = searchStyles.default || "";

	document.head.appendChild(styleTag);
};
// ...
  • kết quả ở bước này là giảm được khoảng 10KB khi mới tải trang .

ưu tiên preload

  • trang này không có nhiều assets ngoài 1 cái ảnh, do đó mình preload load font để font được hiển thị sớm nhất có thể. khi đó, font sẽ được tải ngay khi *.html được tải xong.
<title>Some title</title>
<link rel="preload" href="/SG_Regular_Vn.woff2" as="font" type="font/woff2" />
<link rel="preload" href="/SG_Regular_Latin.woff2" as="font" type="font/woff2" />
<link rel="preload" href="/SG_Semibold_Vn.woff2" as="font" type="font/woff2" />
<link rel="preload" href="/SG_Semibold_Latin.woff2" as="font" type="font/woff2" />
...
<style></style>
  • kết quả ở bước này là waterfall có sự cải thiện.

trước Before

sau after

cache

  • bước này thì đơn giản rồi. mình config file _headers để Cloudflare cache các page trong 60s và các assets 6 tháng để người đọc đỡ phải tải lại trang nhiều lần. file hơi dài do mình copy / paste và lười chưa sắp xếp lại 🫠.
/
  Cache-Control: max-age=60, must-revalidate

/minh-la-ai/
  Cache-Control: max-age=60, must-revalidate

/p/*
  Cache-Control: max-age=60, must-revalidate

/*.css
  Cache-Control: max-age=604800, must-revalidate, immutable

/*.js
  Cache-Control: max-age=604800, must-revalidate, immutable

/*.png
  Cache-Control: max-age=604800, must-revalidate, immutable

/*.webp
  Cache-Control: max-age=604800, must-revalidate, immutable

/*.woff2
  Cache-Control: max-age=604800, must-revalidate, immutable
  • cache này cũng là một sự đánh đổi. người đọc sẽ tải trang rất nhanh ở lần truy cập thứ 2 trở đi, tuy nhiên nội dung sẽ không được cập nhật mới nhất trong một khoảng thời gian do đã bị cache trong trình duyệt. đối với mình thì không phải là vấn đề.

tổng kết

  • các kỹ thuật tối ưu điểm page speed thì có nhiều, trên đây chỉ là một vài kỹ thuật cơ bản phù hợp với trang này. nó đúng với trang này nhưng chưa chắc đúng với trang khác 🙈. mọi kỹ thuật hầu hết xoay quanh việc tải ít assets (cache, lazy load, giảm round trip, …) và tránh việc render blocking.
  • việc tối ưu lên 100 điểm trong hầu hết trường hợp là không cần thiết, chỉ nên tối ưu khi bạn quá rảnh như mình.

p/s

  • một điều khá buồn cười là bác Malte Ubl nhắc ở trên là CTO của Vercel nhưng blog của bác này lại host ở Netlify. Blog khá nay, nếu rảnh bạn có thể đọc.
  • một vài site để đánh giá trang web:

mời bình luận 🗣️

bình luận 😨

chờ chút, đang tải bình luận...