Self-Hosting Web Font untuk Performa: next/font yang Benar

Foto oleh Marcus dePaula

Foto oleh Marcus dePaula
Font adalah item paling diremehkan dalam anggaran performa. Saya pernah melihat hasil Lighthouse di portfolio ini sendiri di mana largest contentful paint tidak terhambat oleh gambar atau bundle JavaScript — ia menunggu sebuah typeface. Font duduk di critical rendering path, memengaruhi layout shift, dan cara default kebanyakan situs memuatnya — link stylesheet ke CDN pihak ketiga — adalah salah satu opsi paling lambat.
Solusinya: self-host, subset, dan pilih strategi display yang waras — dan di Next.js modul next/font mengerjakan sebagian besar ini untuk Anda, asalkan Anda paham apa yang sebenarnya ia lakukan. Artikel ini menelusuri seluruh rantainya: mengapa CSS font pihak ketiga adalah pajak, bagaimana FOIT dan FOUT sebenarnya bekerja, apa yang dihasilkan next/font di balik layar, dan di mana subsetting serta variable font membuktikan nilainya.
Embed Google Fonts klasik membebani Anda minimal satu koneksi ke host stylesheet dan koneksi kedua ke host file font: DNS lookup, handshake TCP, negosiasi TLS — sebelum satu glyph pun terunduh. Di koneksi desktop yang cepat overhead itu tersembunyi; di Android kelas menengah lewat seluler yang mendominasi trafik Indonesia, ia rutin menambah ratusan milidetik ke render teks pertama. Dan karena URL font berada di dalam CSS eksternal, browser bahkan tidak bisa mulai mengunduh font sampai stylesheet itu tiba dan selesai diparse.
Self-hosting meringkas semua itu ke koneksi origin Anda yang sudah ada. File font menumpang koneksi HTTP/2 atau HTTP/3 yang sama dengan HTML dan CSS Anda, bisa di-preload dengan percaya diri penuh, dan tidak ada pertanyaan ketersediaan atau privasi pihak ketiga. Poin terakhir ini bukan teori: mengirim IP pengunjung ke CDN font pihak ketiga adalah persis jenis aliran data yang regulasi privasi — termasuk UU PDP Indonesia — membuat Anda bertanggung jawab atasnya. Font self-hosted sederhana saja menghapus percakapan itu.
Selagi font terunduh, browser harus merender sesuatu. Descriptor font-display memutuskan apa, memakai timeline dua jendela: block period di mana teks tak terlihat, dan swap period di mana teks fallback tampil tapi akan ditukar begitu font tiba. Setiap nilai hanyalah konfigurasi berbeda dari dua jendela itu:
| font-display | Perilaku | Penilaian saya |
|---|---|---|
| block | Block period pendek, lalu swap tanpa batas. Teks tak terlihat sampai sekitar tiga detik — FOIT klasik. | Hindari. Teks tak terlihat di jaringan lambat adalah hasil terburuk bagi pengguna dan bagi LCP. |
| swap | Block nyaris nol, swap tanpa batas. Teks fallback langsung dirender, ditukar kapan pun font tiba — FOUT. | Default saya untuk tipografi penting bagi brand. Pasangkan dengan fallback yang dituning agar pertukarannya tidak menggeser layout. |
| fallback | Block nyaris nol, swap pendek sekitar tiga detik. Jika font melewatkan jendela itu, tampilan halaman ini tetap memakai fallback. | Jalan tengah yang baik untuk body text di mana pertukaran terlambat terasa mengganggu. |
| optional | Block nyaris nol, tanpa swap. Font hanya dipakai jika sudah tersedia, kalau tidak tampilan ini memakai fallback. | Opsi puris-performa yang direkomendasikan web.dev — nol pergeseran akibat swap, dengan biaya branding di kunjungan pertama. |
Tidak ada makan siang gratis: Anda memilih antara teks tak terlihat, reflow yang kelihatan, atau sesekali menampilkan font fallback. Keputusan engineering yang jujur adalah membuat trade-off itu secara eksplisit per proyek, alih-alih mewarisi apa pun yang diputuskan kode embed hasil copy-paste.
next/font bukan pembungkus embed Google Fonts — ia adalah pipeline font saat build. Ketika Anda mengimpor font dari next/font/google, saat build Next.js:
// app/fonts.ts — one instance, imported everywhere
import { Inter } from "next/font/google"
import localFont from "next/font/local"
export const inter = Inter({
subsets: ["latin"], // only preload the glyphs you use
display: "swap", // text visible immediately
variable: "--font-inter" // expose as CSS variable for Tailwind
})
// Brand font you own the files for:
export const brand = localFont({
src: [
{ path: "./brand-var.woff2", weight: "100 900", style: "normal" },
],
display: "swap",
adjustFontFallback: "Arial", // metric-tuned fallback against CLS
variable: "--font-brand",
})
// app/layout.tsx
// <html className={inter.variable + " " + brand.variable}>
// tailwind.config.js
// fontFamily: { sans: ["var(--font-inter)"], display: ["var(--font-brand)"] }Pola yang saya pakai di setiap proyek adalah satu file fonts yang mengekspor tiap font sekali, dengan CSS variable yang terhubung ke Tailwind. Memanggil font loader di banyak file akan menginstansiasi font berkali-kali — satu file definisi, diimpor di mana-mana, menjaga bundle maupun tag preload tetap terdeduplikasi.
Tahan keinginan untuk preload manual setiap weight yang Anda self-host. Preload melewati prioritisasi bawaan browser, dan tiga file font yang di-preload bersaing dengan gambar LCP Anda memperebutkan bandwidth pada milidetik paling diperebutkan dari pemuatan halaman. Preload satu-dua file yang benar-benar dibutuhkan teks above-the-fold; biarkan sisanya dimuat sesuai kebutuhan.
Format dulu: WOFF2 adalah satu-satunya format yang layak dikirim di 2026 — menurut web.dev kompresinya sekitar tiga puluh persen lebih baik dari WOFF, dan semua browser yang Anda pedulikan mendukungnya. Kalau tim desain memberi Anda TTF, konversikan sebelum menyentuh produksi.
Subsetting adalah tuas yang lebih besar. Font multibahasa penuh membawa Cyrillic, Yunani, Vietnam, dan ratusan glyph yang tidak pernah dirender situs Anda. Mendeklarasikan subsets latin di next/font otomatis memangkas font Google; untuk font brand lokal, proses lewat subsetter seperti pyftsubset dan simpan hanya rentang unicode yang Anda sajikan. Di sebuah proyek klien, font headline turun dari 218 KB ke 34 KB dengan subset latin — itu bukan optimasi mikro, itu bandwidth senilai satu gambar penuh yang dikembalikan ke critical path. Teks Indonesia sepenuhnya tercakup subset latin, yang membuat ini keputusan mudah untuk situs berbahasa Indonesia.
Variable font mengemas ruang desain kontinu — weight, width, slant — ke dalam satu file. Mereka mengubah hitungan default anggaran font saya:
Lebih sedikit request, sering kali bobot total lebih kecil
Regular plus medium plus semibold plus bold sebagai file statis berarti empat unduhan. Satu file variable yang mencakup rentang weight 100 sampai 900 adalah satu request dan biasanya lebih kecil dari gabungan tiga potongan statis.
Weight menjadi keputusan desain gratis
Dengan font statis, setiap weight baru yang diinginkan desainer adalah percakapan biaya jaringan tambahan. Dengan variable font, weight 450 atau 620 tidak menambah biaya — axis-nya sudah ada di dalam file.
Kelas satu di next/font
Variable font Google adalah jalur yang direkomendasikan: lewati opsi weight sepenuhnya atau berikan rentang seperti 100 900, dan next/font menyajikan file variable-nya. Axis ekstra seperti slnt bersifat opt-in lewat opsi axes untuk menjaga ukuran.
Awasi pembengkakan axis
Setiap axis tambahan memperbesar file. Font dengan optical-size plus width plus weight bisa melampaui potongan statis yang benar-benar Anda pakai. Subset axis sama seperti Anda men-subset glyph: kirim hanya yang dipakai design system.
Font layak mendapat perhatian engineering yang sama dengan gambar dan JavaScript, dan biasanya tidak mendapat apa-apa. Stack yang terbukti: self-host lewat next/font, satu file WOFF2 variable per family, subset latin, display swap dengan fallback metrik-kompatibel otomatis, dan menahan diri soal preload. Di portfolio ini kombinasi tersebut mengeluarkan font sepenuhnya dari critical path LCP — teks langsung dirender dengan fallback sistem yang dituning dan font brand masuk tanpa lompatan terlihat. Membosankan, terukur, dan layak dilakukan di setiap proyek.
Buka DevTools, ganti panel Network ke filter Font, dan reload dengan cache dimatikan. Kalau Anda melihat lebih dari dua file font sebelum first paint, atau request apa pun ke CDN font yang bukan milik Anda, Anda baru saja menemukan kemenangan performa termudah minggu ini.