Pas Ikon Stack Blog Nggak Perlu Fetch Lagi
Ikon stack di list blog saya telat muncul karena fetch runtime. Saya ganti dengan SVG server-side dan fallback icon set.
Pas buka list blog, kartu-kartunya udah muncul duluan. Ikon tech stack nyusul belakangan, satu per satu. Bukan animasi yang saya pasang. Network tab nunjukin request ke api.iconify.design buat hampir setiap kartu, dan jedanya lebih dari satu detik di koneksi yang biasa.
Dugaan awal saya salah: saya kira ini masalah font atau layout shift. Ternyata sumbernya <Icon> dari @iconify/react. Komponen itu client component. Di Next.js, SVG-nya nggak dirender di server; dia nunggu mounted supaya hydration nggak error, lalu baru narik data ikon dari Iconify API.
Untuk ikon kecil di list, saya nggak mau halaman bergantung ke request pihak ketiga. Jadi jalur runtime itu saya potong.
SVG dibangun saat build, bukan saat halaman dibuka
Commit ini mengganti fetch runtime dengan @iconify/json dan @iconify/utils. Paket pertama berisi data icon set dalam format IconifyJSON. Paket kedua menyediakan utilitas buat mengambil data ikon dan membangun body serta atribut SVG.
Komponen TechIconTile saya jadikan server-rendered. Ikon diproses pakai iconToSVG, lalu body-nya masuk ke SVG inline. Browser menerima markup yang udah jadi, bukan instruksi buat fetch lagi.
import { iconToSVG, replaceIDs } from "@iconify/utils";
const { body, attributes } = iconToSVG(iconData, { height: "1em" });
const svg = `<svg ${attrToString(attributes)}>${replaceIDs(body)}</svg>`;Bagian replaceIDs bukan hiasan. SVG tertentu punya id untuk gradient, mask, atau clip-path. Kalo dua instance memakai id yang sama di satu dokumen, referensinya bisa menunjuk ke elemen yang salah. Dokumentasi replaceIDs menjelaskan fungsi ini memang buat mengganti id jadi unik saat konten SVG disisipkan.
Trade-off-nya jelas: data icon set bikin dependency build lebih besar. Saya ukur file JSON yang dipakai di checkout ini: simple-icons 4,6 MB, logos 7,2 MB, devicon-plain 2,6 MB, dan fa6-brands 497 KB. Itu ukuran file sumber di node_modules, bukan ukuran yang otomatis dikirim ke browser. Mapping di tech-icons.ts cuma mengambil ikon yang dipakai, lalu hasil akhirnya dirender di server.
Saya pilih biaya build itu. Untuk blog statis, request runtime ke CDN cuma demi gambar kecil adalah jenis optimasi yang salah arah.
Mapping tag perlu fallback dan tes batas kata
Daftar tag blog nggak selalu punya padanan di satu icon set. Simple Icons jadi sumber utama, tapi kebijakan Simple Icons memang memungkinkan brand dihapus atas permintaan pemilik atau pertimbangan mereka sendiri. Karena itu mapping saya punya beberapa sumber fallback: devicon, logos, arcticons, selfhst, dan fa6-brands.
Di versi simple-icons yang terpasang saat saya ngecek pada Agustus 2026 ada 3.730 ikon. systemd, haproxy, dan yandex nggak ada di set itu, jadi saya ambil dari set lain. Nama ikon yang salah juga sengaja dibuat keras: helper required() melempar error saat build. Lebih baik build merah daripada tile kosong baru ketahuan setelah deploy.
Masalah kecil lain muncul di ranking. Saya ingin ikon yang disebut di judul naik ke posisi utama. Pencocokan awal pakai includes(), sehingga tag go bisa ikut terpilih dari judul yang memuat google. Itu bukan ranking, itu kebetulan string.
const esc = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const re = new RegExp(`(?<![a-z0-9])${esc}(?![a-z0-9])`, "i");Regex-nya memakai lookaround yang memeriksa karakter sebelum dan sesudah alias. Hasilnya, go berdiri sendiri dan nggak nyangkut di google. Tes kecil ini penting karena daftar alias terus membesar; bug satu karakter bisa mengubah ikon utama di seluruh list.
Sekarang kartu blog menerima inline SVG sejak response pertama. Build memang mengerjakan lebih banyak hal, dan dependency lokal jadi lebih berat. Tapi pembaca nggak membayar round-trip tambahan, nggak melihat tile kosong sesaat, dan list tetap punya ikon walau layanan Iconify sedang lambat.