Skip to main content
hazuki
moss on the old courtyard wall · sunlight through the leaves, 4 pm · a forgotten greenhouse, still breathing · sage green, faded by summers · wooden window, one potted fern · expired film, overgrown garden · ivy on the greenhouse glass · an old photograph, green at the edges · latest note: rebuilding my site with astro 7 · fresh roll on /tuesday: forgotten places · hanoi, 22°c, drizzle on the windowpane · 05:35 · london, 18°c, clear sky over the courtyard · 23:35 ❧
v0.1.0

Dựng lại trang cá nhân với Astro 7

Vì sao tôi bỏ blog cũ, chọn Astro 7 chạy tĩnh trên Cloudflare Workers, và vài điều tôi học được dọc đường.

Trang cũ của tôi nặng dần theo từng năm: thêm một thư viện giao diện, thêm một bộ theo dõi, rồi lúc nào đó mở lên mất vài giây mới thấy chữ. Lần này tôi muốn làm ngược lại, ít thứ hơn, chậm rãi hơn, và gần như không có JavaScript phía trình duyệt.

Vì sao là Astro

Astro sinh ra HTML tĩnh và chỉ gửi JavaScript khi tôi thật sự cần. Với một trang chủ yếu là chữ và ảnh thì thế là đủ. Nội dung nằm trong thư mục src/content, được kiểm tra bằng Zod ngay lúc build, nên thiếu một dòng alt là build hỏng chứ không lọt ra ngoài.

Danh sách ngắn những gì tôi giữ lại:

  • Markdown và MDX cho bài viết, có chú thích cuối trang và callout.
  • Hai ngôn ngữ, tiếng Việt là mặc định và không có tiền tố trên URL.
  • Phông chữ tự lưu trữ, thử kỹ với các chữ như ằ, ẳ, ỡ, ự.
  • Tìm kiếm bằng Pagefind, chỉ tải khi mở trang /search.

Chọn ngôn ngữ ở rìa mạng

Người đọc vào / sẽ được chuyển hướng theo ngôn ngữ trình duyệt, được quyết định ở Worker chứ không phải trong trang. Phần cốt lõi chỉ là một hàm nhỏ:

type Lang = "vi" | "en";

export function resolveLang(cookie: string | null, acceptLanguage: string | null): Lang {
  const fromCookie = cookie?.match(/(?:^|;\s*)lang=(vi|en)/)?.[1];
  if (fromCookie) return fromCookie as Lang;
  return acceptLanguage?.toLowerCase().startsWith("en") ? "en" : "vi";
}

NOTE

Cookie luôn được ưu tiên hơn Accept-Language, nên ai đã chọn ngôn ngữ một lần sẽ không bị đổi ý hộ.

Phần tệp tĩnh

Cloudflare Workers phục vụ thư mục dist như tài nguyên tĩnh. Chỉ có đường dẫn gốc đi qua Worker, mọi thứ còn lại trả thẳng từ CDN:

{
  "assets": {
    "directory": "./dist",
    "binding": "ASSETS",
    // chỉ "/" chạy Worker trước, còn lại là tệp tĩnh
    "run_worker_first": ["/"],
    "not_found_handling": "404-page"
  }
}

WARNING

Nếu để run_worker_first là true, mọi yêu cầu tệp tĩnh đều tốn một lần gọi Worker. Hãy liệt kê đúng những đường dẫn cần thiết.

Điều tôi suýt làm hỏng

Lúc đầu tôi định thêm một lớp hiệu ứng động cho mọi trang. Sau vài ngày tôi thấy nó không phục vụ ai cả, và nó làm điểm hiệu năng tụt xuống.1

CAUTION

Đừng tải ảnh gốc có EXIF lên trang công khai. Dữ liệu GPS đi kèm có thể tiết lộ chính xác nơi bạn đã chụp.

Kết

Trang này vẫn chưa xong, và có lẽ sẽ không bao giờ xong. Nhưng nó nhanh, nhẹ, và tôi hiểu từng dòng trong đó.

Footnotes

  1. Mọi hiệu ứng động hiện chỉ chạy khi người dùng không bật chế độ giảm chuyển động của hệ điều hành. ↩