Related Semantic, TLDR, dan Search Live
Related articles nyambung tag doang sering ngawur. Ganti cosine semantic, tambah box TLDR, dan bikin search dialog live, semua tetap tahan API lama.
Related articles di blog saya dulu cuma cocokin tag pertama. Artikel soal Python dapet "terkait" yang isinya artikel lain kenapa-kenapa karena sama-sama nyantol di satu tag. Nggak salah sih secara teknis, tapi nggak ada yang baca blog karena tagnya sama.
Jadi saya ubah tiga hal sekaligus: related pakai cosine similarity, box TL;DR di atas artikel, dan search dialog yang live. Tiga fitur kecil, satu commit. Dan yang paling penting, semuanya didesain supaya kalau API lama masih jalan, halaman tetap utuh.
Related yang sekarang ngerti konten
Sebelumnya, fetchRelated itu cuma ambil artikel dari tag pertama yang sama. Fetch per tag, filter, ambil paling mirip dari situ. Hasilnya: sharing label doang, bukan sharing ide.
Sekarang endpoint /related ngitung cosine embedding pake bge-m3. Satu lagi: artikel kembaran satu translation_group (versi EN/ID dari tulisan yang sama) di-exclude di sisi API. Jadi frontend nggak perlu ngecek duplikat sendiri.
Di app/[locale]/blog/[slug]/page.tsx, saya masukin fetchRelated ke dalam Promise.all yang udah ada, bareng fetchNeighbors dan fetchArticles per tag. Satu baris di array, tetep paralel. Response semantic-nya null-safe: kalau endpoint nggak ada (deploy lama, API belum di-upgrade), hasilnya null, dan frontend fallback ke daftar related lama berbasis tag pertama.
Pola ini sengaja. Fitur baru boleh mati kapan aja. Endpoint bisa hilang, API bisa di-deploy ulang tanpa embedding, tapi halaman blog nggak boleh rusak karena satu fetch gagal.
Hydrate manual, nggak fetch berlebihan
Related semantic cuma ngirim slug, title, excerpt, score, data ringan. Tapi timeline RelatedArticles butuh tanggal dan tags, yang nggak ada di response itu. Jadi frontend fetch full article per slug, maksimal 8. Satu Promise.all lagi, satu blok kecil. Nggak ada SPA-style optimistic loading. Loading selesai, konten muncul, selesai.
Saya mikir sebentar soal ini: kenapa nggak sekalian kirim tanggal dan tags dari API semantic? Karena API-nya generic, dipake juga bukan cuma buat blog. Nambah field spesifik blog ke response itu coupling yang nggak perlu. Fetch tambahan 8 item itu murah, dan response-nya bisa di-cache.
TL;DR box dari meta
ArticleTldr.tsx itu cuma nge-check article.tldr dari meta. Ada? Render box aria-label TL;DR di atas body. Nggak ada? Nggak render apa-apa. Komponen baru, nol risiko ke yang lama.
Saya taruh di atas karena pembaca biasanya nge-scan dulu sebelum commit baca panjang. Kalau tldr-nya cukup, mereka lanjut. Kalau nggak, mereka scroll. Dua-duanya oke.
Search dialog jadi live
SearchDialog.tsx dulu submit form, tunggu response, muncul hasil. Sekarang debounced input, pake useDeferredValue dan useTransition, hasil muncul saat ngetik. Search API-nya tetap yang sama (query expansion MariaDB, yang udah saya tulis sebelumnya), cuma frontend-nya yang beda.
Dua React hooks bawaan itu bisa dikombinasi jadi pola custom, dan dokumentasi resmi React [3] ngelist semua built-in hooks yang tersedia. useDeferredValue bikin input tetap responsif walau render hasil lambat. useTransition nandain fetch sebagai non-urgent update. Hasilnya: ngetik nggak lag, loading muncul halus.
Yang saya lewati
Nggak ada analytics tracking per fitur baru. Mau banget tau related semantic beneran dipake atau nggak, tapi bikin event khusus belum sebanding di ukuran blog ini. Nggak ada skeleton loader di search juga, debouncing udah cukup kasih feedback visual. Search hasilnya juga belum saya cache di sisi klien; kalau nanti query-nya makin berat, baru layak ditambah.
Artikel lain yang nyambung: pola related lama dengan satu fetch paralel, sisi API-nya di vector store Python tanpa NumPy, dan search API MariaDB yang dipake dialog ini.