RUM REAL-USER-MONITORING FRONTEND-OBSERVABILITY WEB-PERFORMANCE USER-EXPERIENCE ANALYTICS MONITORING JAVASCRIPT BROWSER-API DATA-COLLECTION PERFORMANCE-OPTIMIZATION CUSTOM-METRICS PRIVACY WEB-DEVELOPMENT

Membangun Real User Monitoring (RUM) Kustom: Melacak dan Menganalisis Pengalaman Pengguna Secara Mendalam

⏱️ 13 menit baca
👨‍💻

Membangun Real User Monitoring (RUM) Kustom: Melacak dan Menganalisis Pengalaman Pengguna Secara Mendalam

1. Pendahuluan: Mengapa RUM Kustom?

Sebagai developer web, kita seringkali fokus pada performa di lingkungan pengembangan atau staging. Kita menggunakan tools seperti Lighthouse, WebPageTest, atau Synthetic Monitoring lainnya untuk mengukur seberapa cepat website kita dimuat. Tapi, ada satu pertanyaan krusial yang sering terlewat: bagaimana sebenarnya pengalaman pengguna di dunia nyata?

Di sinilah Real User Monitoring (RUM) berperan penting. RUM adalah metode untuk mengumpulkan data performa dan interaksi pengguna langsung dari browser pengguna asli. Berbeda dengan Synthetic Monitoring yang mensimulasikan pengguna dari lokasi dan perangkat tertentu, RUM memberikan gambaran jujur tentang apa yang dialami oleh setiap pengguna, di berbagai kondisi jaringan, perangkat, dan lokasi geografis.

Blog ini pernah membahas dasar-dasar RUM dalam artikel “Mengintip Pengalaman Pengguna: Memahami Synthetic Monitoring dan Real User Monitoring (RUM)”. Kali ini, kita akan melangkah lebih jauh. Kita akan membahas mengapa Anda mungkin ingin membangun sistem RUM kustom Anda sendiri, dan bagaimana cara melakukannya, dari pengumpulan data di frontend hingga penanganan di backend.

⚠️ Keterbatasan Tools Pihak Ketiga: Meskipun ada banyak penyedia RUM komersial yang hebat (seperti Sentry, Datadog, New Relic), membangun RUM kustom memberi Anda fleksibilitas penuh terhadap data yang ingin Anda kumpulkan, bagaimana Anda menyimpannya, dan yang terpenting, kontrol penuh atas aspek privasi. Ini juga bisa menjadi solusi yang lebih hemat biaya untuk proyek dengan skala tertentu.

🎯 Tujuan Artikel Ini: Setelah membaca artikel ini, Anda akan memiliki pemahaman yang kuat dan panduan praktis untuk mulai membangun sistem RUM kustom Anda sendiri, yang mampu memberikan wawasan mendalam tentang pengalaman pengguna aplikasi web Anda.

2. Memahami Metrik Kunci untuk RUM

Sebelum kita mulai mengumpulkan data, kita perlu tahu data apa yang penting. Metrik performa web terus berkembang, dan saat ini, Core Web Vitals adalah standar emas yang direkomendasikan Google untuk mengukur pengalaman pengguna.

📌 Metrik Esensial yang Harus Anda Lacak:

✅ Dengan metrik-metrik ini, Anda dapat membangun gambaran komprehensif tentang performa dan pengalaman pengguna di aplikasi web Anda.

3. Mengumpulkan Data Performa di Browser

Browser modern menyediakan berbagai API yang kuat untuk mengumpulkan data performa. Kita akan memanfaatkan PerformanceObserver dan navigator.sendBeacon sebagai fondasi utama.

Menggunakan PerformanceObserver untuk Core Web Vitals

PerformanceObserver adalah alat paling efisien untuk melacak metrik performa seiring waktu tanpa memblokir thread utama.

// rum.js
const reportEndpoint = '/api/rum'; // Endpoint API di backend Anda

// Fungsi untuk mengirim data ke backend
function sendMetrics(metrics) {
    if (navigator.sendBeacon) {
        navigator.sendBeacon(reportEndpoint, JSON.stringify(metrics));
    } else {
        // Fallback untuk browser lama
        fetch(reportEndpoint, {
            method: 'POST',
            body: JSON.stringify(metrics),
            headers: {
                'Content-Type': 'application/json'
            },
            keepalive: true // Penting agar request tidak dibatalkan saat halaman ditutup
        });
    }
}

// Objek untuk menyimpan metrik
const collectedMetrics = {
    pageUrl: window.location.href,
    userAgent: navigator.userAgent,
    timestamp: Date.now(),
    // ... bisa tambahkan info user ID (anonim), session ID, dsb.
};

// Mengumpulkan LCP, CLS, INP
const po = new PerformanceObserver((entryList) => {
    for (const entry of entryList.getEntries()) {
        if (entry.name === 'LCP') {
            collectedMetrics.lcp = entry.startTime;
        } else if (entry.name === 'CLS') {
            collectedMetrics.cls = entry.value;
        } else if (entry.name === 'INP') {
            // INP adalah akumulasi dari semua interaksi, kita perlu mengambil nilai terakhir
            // Atau mengumpulkan event interaksi individual
            // Untuk RUM kustom sederhana, kita bisa ambil entry.value (yang merupakan nilai akumulatif)
            // Atau lebih baik, lacak interaksi individual
            collectedMetrics.inp = entry.value;
        }
    }
});

// Perhatikan jenis entry yang dilacak
po.observe({ type: ['largest-contentful-paint', 'layout-shift', 'long-animation-frame'], buffered: true });
// 'long-animation-frame' digunakan untuk INP

// Mengumpulkan FCP
new PerformanceObserver((entryList) => {
    for (const entry of entryList.getEntries()) {
        if (entry.name === 'first-contentful-paint') {
            collectedMetrics.fcp = entry.startTime;
        }
    }
}).observe({ type: 'paint', buffered: true });

// Mengumpulkan TTFB (menggunakan Navigation Timing API)
function getTTFB() {
    const navEntry = performance.getEntriesByType('navigation')[0];
    return navEntry ? navEntry.responseStart - navEntry.requestStart : 0;
}
collectedMetrics.ttfb = getTTFB();

// Mengirim semua metrik saat halaman akan di-unload
window.addEventListener('beforeunload', () => {
    sendMetrics(collectedMetrics);
});

// Contoh Custom Metric: Waktu loading gambar utama
window.addEventListener('load', () => {
    const mainImage = document.querySelector('img.main-content');
    if (mainImage && mainImage.complete) {
        collectedMetrics.mainImageLoadTime = performance.now();
    }
});

// Contoh Custom Metric: Error JavaScript
window.addEventListener('error', (event) => {
    sendMetrics({
        type: 'error',
        message: event.message,
        filename: event.filename,
        lineno: event.lineno,
        colno: event.colno,
        stack: event.error ? event.error.stack : 'N/A',
        pageUrl: window.location.href,
        userAgent: navigator.userAgent,
        timestamp: Date.now()
    });
});

💡 Tips Praktis:

4. Mengirim Data ke Backend Anda

Setelah data performa berhasil dikumpulkan di browser, langkah selanjutnya adalah mengirimkannya ke backend Anda untuk disimpan dan dianalisis. Ada beberapa cara, namun navigator.sendBeacon() adalah pilihan terbaik untuk skenario RUM.

Menggunakan navigator.sendBeacon()

navigator.sendBeacon() dirancang khusus untuk mengirimkan data kecil ke server saat halaman sedang di-unload atau ditutup. Keunggulannya adalah:

// rincian implementasi sudah ada di bagian 3 dalam fungsi sendMetrics
function sendMetrics(metrics) {
    if (navigator.sendBeacon) {
        // ✅ Pilihan terbaik untuk RUM
        navigator.sendBeacon(reportEndpoint, JSON.stringify(metrics));
    } else {
        // ⚠️ Fallback untuk browser lama, kurang ideal karena bisa dibatalkan
        fetch(reportEndpoint, {
            method: 'POST',
            body: JSON.stringify(metrics),
            headers: {
                'Content-Type': 'application/json'
            },
            keepalive: true // Coba pertahankan request
        });
    }
}

// ... panggil sendMetrics saat event 'beforeunload' atau event lain yang relevan
window.addEventListener('beforeunload', () => {
    sendMetrics(collectedMetrics);
});

Strategi Batching Data

Untuk mengurangi jumlah permintaan ke server, terutama jika Anda melacak banyak custom event atau interaksi, Anda bisa menerapkan strategi batching. Kumpulkan metrik dalam buffer dan kirimkan secara berkala (misalnya, setiap 10-15 detik) atau saat buffer penuh, selain saat beforeunload.

let metricsBuffer = [];
const BATCH_INTERVAL = 10000; // Kirim setiap 10 detik
const MAX_BATCH_SIZE = 10; // Atau jika buffer mencapai 10 item

function addToBufferAndSend(metric) {
    metricsBuffer.push(metric);
    if (metricsBuffer.length >= MAX_BATCH_SIZE) {
        sendMetrics(metricsBuffer);
        metricsBuffer = [];
    }
}

// Kirim sisa buffer sebelum unload
window.addEventListener('beforeunload', () => {
    if (metricsBuffer.length > 0) {
        sendMetrics(metricsBuffer);
        metricsBuffer = [];
    }
});

// Kirim buffer secara berkala
setInterval(() => {
    if (metricsBuffer.length > 0) {
        sendMetrics(metricsBuffer);
        metricsBuffer = [];
    }
}, BATCH_INTERVAL);

// Ganti sendMetrics(collectedMetrics) menjadi addToBufferAndSend(collectedMetrics)
// ketika mengumpulkan metrik LCP, CLS, dll.
// Misalnya, ketika PerformanceObserver mendeteksi entri baru:
// addToBufferAndSend({ type: 'lcp', value: entry.startTime });

5. Pertimbangan Backend: Ingesti dan Penyimpanan Data

Sisi backend bertanggung jawab untuk menerima data RUM yang dikirim dari frontend, memvalidasinya, dan menyimpannya.

Endpoint API untuk Ingesti Data

Buat endpoint API yang ringan dan cepat untuk menerima data RUM. Endpoint ini harus seefisien mungkin karena akan menerima banyak permintaan.

// Contoh sederhana dengan Node.js Express
const express = require('express');
const app = express();
const bodyParser = require('body-parser');

app.use(bodyParser.json());

app.post('/api/rum', (req, res) => {
    const metrics = req.body;
    // Lakukan validasi data di sini
    if (!metrics || !metrics.pageUrl || !metrics.timestamp) {
        return res.status(400).send('Invalid metrics data');
    }

    // ✅ Log atau simpan data ke database
    console.log('Received RUM metrics:', metrics);
    // Contoh: saveMetricsToDatabase(metrics);

    // Respon secepat mungkin
    res.status(204).send(); // 204 No Content adalah respons yang baik untuk sendBeacon
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`RUM Ingestion API running on port ${PORT}`);
});

Pilihan Database untuk Data RUM

Data RUM bersifat time-series (terkait waktu) dan volume tinggi. Pilihan database yang cocok meliputi:

💡 Saran: Untuk memulai, Anda bisa menggunakan PostgreSQL dengan ekstensi TimescaleDB atau MongoDB karena fleksibilitasnya. Seiring pertumbuhan data, migrasi ke solusi TSDB khusus akan sangat bermanfaat.

6. Privasi dan Etika dalam RUM

⚠️ Ini adalah bagian KRUSIAL. Saat mengumpulkan data dari pengguna nyata, privasi harus menjadi prioritas utama. Melanggar privasi dapat merusak kepercayaan pengguna dan berujung pada masalah hukum (misalnya, GDPR di Eropa, UU PDP di Indonesia).

📌 Praktik Terbaik untuk Privasi:

✅ Dengan mematuhi prinsip-prinsip privasi ini, Anda dapat membangun sistem RUM yang kuat tanpa mengorbankan kepercayaan pengguna.

Kesimpulan

Membangun sistem Real User Monitoring (RUM) kustom adalah investasi yang sangat berharga untuk memahami dan meningkatkan pengalaman pengguna aplikasi web Anda. Dengan RUM, Anda tidak lagi menebak-nebak, melainkan melihat data nyata tentang bagaimana performa aplikasi Anda di tangan pengguna sesungguhnya.

Kita telah membahas langkah-langkah penting, mulai dari mengidentifikasi metrik kunci seperti Core Web Vitals, teknik pengumpulan data efisien menggunakan PerformanceObserver dan navigator.sendBeacon(), hingga pertimbangan backend untuk ingesti dan penyimpanan data, serta aspek privasi yang tidak boleh diabaikan.

Sekarang, giliran Anda untuk memulai! Dengan fondasi yang kuat ini, Anda bisa mulai mengumpulkan wawasan berharga, mengidentifikasi bottleneck, dan membuat keputusan berbasis data untuk terus mengoptimalkan aplikasi web Anda. Selamat mencoba!

🔗 Baca Juga