ข้ามไปเนื้อหาหลัก

Category: guide

Web Fonts Optimization — โหลด Font เร็วโดยไม่กระทบ UX

วิธี optimize font loading ให้ไม่เกิด FOUT/FOIT, ใช้ font-display อย่างถูกต้อง, preconnect, self-host fonts, variable fonts และ system font stack

· อ่านประมาณ 2 นาที

สารบัญ

ปัญหาของ Web Fonts

FOUT (Flash of Unstyled Text) — text แสดงด้วย fallback font ก่อน แล้วกระพริบเป็น web font เมื่อโหลดเสร็จ

FOIT (Flash of Invisible Text) — text ซ่อนอยู่จนกว่า web font จะโหลดเสร็จ ผู้ใช้เห็นหน้าว่าง

Layout Shift — ขนาด/spacing ของ font ต่างกันทำให้ element เลื่อน (ส่งผลต่อ CLS score)

font-display — ควบคุม Loading Behavior

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap; /* แนะนำสำหรับ body text */
}
ValueBehaviorเหมาะกับ
autoขึ้นกับ browser (มักเหมือน block)ไม่แนะนำ
blockซ่อน text สั้นๆ แล้วใช้ fonticon fonts
swapfallback ก่อน → swap เมื่อพร้อมbody text
fallbackซ่อน 100ms → fallback → swap ใน 3 วิเหมือน swap แต่ window สั้นกว่า
optionalใช้ font ถ้าโหลดเสร็จในเวลาสั้น มิฉะนั้นใช้ fallback ตลอดnice-to-have fonts

Preconnect — เตรียม Connection ล่วงหน้า

<!-- ใส่ใน <head> ก่อน Google Fonts stylesheet -->
<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=Inter:wght@400;700&display=swap" rel="stylesheet" />

preconnect ทำ DNS lookup + TCP handshake + TLS negotiation ล่วงหน้า ประหยัดเวลา ~200-300ms

Self-hosting Fonts (ดีที่สุด)

ไม่ต้องพึ่ง CDN ภายนอก โหลดเร็วกว่า และ privacy ดีกว่า:

# ใช้ fontsource สำหรับ npm-based self-hosting
npm install @fontsource/inter
npm install @fontsource-variable/inter  # variable font version
// src/layouts/Layout.astro หรือ global CSS
import '@fontsource/inter/400.css';
import '@fontsource/inter/700.css';

// หรือ import ใน CSS
@import '@fontsource/inter';

Variable Fonts — 1 ไฟล์แทน N ไฟล์

Variable font บรรจุ weight/style หลายอัน (100–900) ในไฟล์เดียว:

/* ปกติ: 6 font files */
@font-face { font-family: 'Inter'; font-weight: 400; src: url('inter-regular.woff2'); }
@font-face { font-family: 'Inter'; font-weight: 500; src: url('inter-medium.woff2'); }
@font-face { font-family: 'Inter'; font-weight: 700; src: url('inter-bold.woff2'); }

/* Variable font: 1 ไฟล์ */
@font-face {
  font-family: 'Inter var';
  src: url('inter-variable.woff2') format('woff2-variations');
  font-weight: 100 900;  /* range */
  font-display: swap;
}

ขนาด 1 variable font file มักเล็กกว่าหลาย static files รวมกัน

System Font Stack — ไม่ต้องโหลดเลย

สำหรับ project ที่ไม่ต้องการ web font:

:root {
  --font-sans:
    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
    Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';

  --font-mono:
    'SF Mono', 'Cascadia Code', 'Fira Code', Consolas,
    'DejaVu Sans Mono', monospace;
}

body { font-family: var(--font-sans); }
code { font-family: var(--font-mono); }

ข้อดี: โหลดเร็วสุด (ไม่มี network request), text ดูคุ้นเคยสำหรับผู้ใช้

สำหรับภาษาไทย (Noto Sans Thai)

<!-- โหลดเฉพาะ weight ที่ใช้ — Thai subset -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@400;600;700;800&display=swap" rel="stylesheet" />
body {
  font-family: 'Noto Sans Thai', sans-serif;
}

/* fallback สำหรับ Latin text */
h1, p { font-family: 'Inter', 'Noto Sans Thai', sans-serif; }

ตรวจสอบ Performance

# ดูว่า font โหลดช้าไหม
curl -o /dev/null -s -w "DNS: %{time_namelookup}s | Connect: %{time_connect}s | Total: %{time_total}s\n" \
  "https://fonts.gstatic.com/..."

# หรือดูใน DevTools > Network > Fonts tab

Lighthouse จะแจ้ง warning ถ้า:

  • ไม่มี font-display
  • ไม่มี preconnect สำหรับ third-party fonts
  • ใช้ fonts ที่ทำให้เกิด layout shift