L
React • Literally Web ID

Membangun Real-time Dashboard Finansial dengan React Server Components dan WebSockets

⏱️ 18 menit baca • 📅 20 Oct 2026 • 👁️ 2 views
Membangun Real-time Dashboard Finansial dengan React Server Components dan WebSockets

Membangun Real-time Dashboard Finansial dengan React Server Components dan WebSockets

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap teknologi finansial (Fintech) modern, latensi adalah satuan mata uang yang paling berharga. Sistem trading berfrekuensi tinggi (High-Frequency Trading), platform pemantauan transaksi sekuritas, dan dashboard manajemen kas perusahaan menuntut pembaruan data secara real-time tanpa penundaan (delay) milidetik pun yang tidak perlu. Tantangan terbesar dalam membangun dashboard finansial berbasis web bukanlah sekadar menampilkan angka, melainkan bagaimana mempertahankan performa render DOM yang mulus di sisi klien saat menerima aliran data (data stream) dengan volume tinggi—seringkali mencapai ratusan peristiwa per detik dari berbagai WebSocket feeds.

Secara historis, arsitektur Single Page Application (SPA) murni mengalami hambatan berat (bottleneck) dalam skenario ini. SPA tradisional membebankan seluruh proses pengunduhan bundle JavaScript, parsing, hidrasi (hydration), dan pengelolaan state dinamis di perangkat klien (browser pengguna). Ketika WebSocket menerima lonjakan pesan harga saham atau mutasi transaksi, siklus re-rendering berantai (cascade re-rendering) terjadi di seluruh komponen hierarki React, menyebabkan lonjakan penggunaan CPU, frame dropping pada animasi UI, dan akhirnya pengalaman pengguna yang patah-patah (janky).

Perbandingan kondisi:

  • Sebelum Penerapan (SPA Tradisional + Polling / WebSocket Monolitik): Penggunaan memori browser tinggi akibat state management global yang besar. Terjadi unnecessary re-renders pada seluruh komponen pohon DOM setiap kali satu titik data berubah. Waktu muat awal lambat karena ukuran bundle JavaScript masif (> 5MB). Latensi end-to-end tinggi akibat hambatan thread utama JavaScript (main-thread blocking).
  • Sesudah Penerapan (React Server Components + Zustand + WebSocket Terisolasi): Initial load sangat instan karena komponen statis dan layout dirender di server (RSC). Pembaruan data real-time diisolasi secara presisi ke dalam komponen klien tertentu menggunakan Zustand selector-based state subscription, memangkas re-render hingga 90%. Pemanfaatan Edge Caching dan Nginx reverse proxy mengoptimalkan pengiriman aset statis.

[!IMPORTANT] Jangan pernah mencampur logika rendering berat dengan penanganan event WebSocket di dalam komponen monolitik yang sama. Pisahkan tanggung jawab pemrosesan data, manajemen state reaktif, dan presentasi UI secara ketat.


2. Konsep Inti & Deep Dive Arsitektur

Arsitektur yang kami usulkan mengintegrasikan model hibrida antara React Server Components (RSC) untuk optimalisasi muatan awal dan SEO, serta Client Components yang dioptimalkan dengan Zustand untuk menangani aliran data real-time dari WebSocket.

Di balik layar, server Node.js atau Go bertindak sebagai pusat manajemen WebSocket connection pool, sementara Nginx di depan bertindak sebagai reverse proxy yang melakukan upgrade connection secara efisien menggunakan konfigurasi HTTP/1.1 atau HTTP/2.

+-----------------------------------------------------------------------+
|                        Client Browser                                 |----
|  +-----------------------+     +-----------------------------------+  |    |
|  | React Server Comps    |     | Client Components (Zustand Store) |  |    |
|  | (Initial Layout, SSR) |     | (High-Freq Ticker, Order Book)    |  |    |
|  +-----------------------+     +-----------------------------------+  |    |
+-----------------------------------------------------------------------+    |
                                                                             |
                                                                             | WSS / HTTP/2
+-----------------------------------------------------------------------+    |
|                      Edge & Reverse Proxy (Nginx)                     |<---+
|  - SSL Termination (Let's Encrypt)                                    |
|  - WebSocket Connection Upgrade ($http_upgrade)                       |
+-----------------------------------------------------------------------+
                                   |
                                   v
+-----------------------------------------------------------------------+
|                        Backend Services                               |
|  +-------------------------+     +---------------------------------+  |
|  | Node.js / Go WS Server  | <-> | Redis Pub/Sub (Event Broker)    |
|  +-------------------------+     +---------------------------------+  |
+-----------------------------------------------------------------------+

Alur eksekusi data:

  1. Klien memuat halaman; Next.js menyajikan HTML awal yang dirender oleh RSC (menghilangkan beban JavaScript awal untuk layout dasar).
  2. Klien menginisialisasi koneksi WebSocket aman (wss://) melalui Nginx reverse proxy menuju server backend.
  3. Server backend menerima stream data transaksi dari database atau message broker (Redis Pub/Sub).
  4. Pesan WebSocket masuk ke client-side store (Zustand). Zustand memperbarui state spesifik tanpa memicu re-render pada root layout.
  5. Komponen UI finansial yang berlangganan pada selector Zustand tertentu melakukan mutasi DOM secara instan dan efisien.

3. Perbandingan Komparatif / Analisis Pendekatan

Berikut adalah analisis mendalam mengenai tiga pendekatan arsitektur untuk membangun dashboard real-time:

Parameter Evaluasi Pendekatan A: SPA Tradisional (Redux + WS) Pendekatan B: SSR Murni + Polling HTTP Pendekatan C: RSC + Zustand + WebSocket (Usulan)
Initial Bundle Size Besar (> 4MB) Sedang (2-3MB) Sangat Kecil (< 1.5MB)
Time to First Byte (TTFB) Lambat (Render di client) Cepat (Render di server) Sangat Cepat (Edge + RSC)
CPU Usage (Client) Tinggi (Sering re-render massal) Sedang (Beban request berkala) Rendah (Pembaruan terisolasi)
Memory Footprint Boros (Global Store masif) Stabil Optimal (Pembersihan memori reaktif)
Network Overhead Efisien (Persistent Connection) Sangat Boros (Polling) Sangat Efisien (Persistent + RSC payload kecil)
Latensi Real-time Rendah (< 50ms) Tinggi (> 1000ms) Sangat Rendah (< 20ms)

[!WARNING] Hindari penggunaan HTTP Polling untuk dashboard finansial skala enterprise karena akan membebani Web Server (Nginx/Apache) dan Database dengan jutaan request kosong (empty reads).


4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum melangkah ke implementasi kode, pastikan lingkungan server dan pengembangan Anda memenuhi spesifikasi enterprise berikut:

  • Operating System: Ubuntu Server 22.04 LTS (Jammy Jellyfish)
  • Node.js Runtime: v20.x LTS (Minimum v18.17+)
  • Package Manager: pnpm (disarankan untuk efisiensi node_modules) atau npm
  • Web Server / Reverse Proxy: Nginx 1.18+
  • SSL/TLS Certificate: Let's Encrypt (Certbot)
  • Database & Cache: Redis 7.x (untuk WebSocket pub/sub broker)

Verifikasi instalasi dasar melalui terminal:

node -v
pnpm -v
nginx -v
redis-cli --version

Pastikan port firewall (UFW) dikonfigurasi dengan benar:

sudo ufw allow 'Nginx Full'
sudo ufw allow OpenSSH
sudo ufw enable

5. Panduan Implementasi Langkah demi Langkah

Langkah 1: Konfigurasi Nginx sebagai Reverse Proxy & WebSocket Gateway

Buat file konfigurasi Nginx untuk menangani upgrade koneksi WebSocket pada /etc/nginx/sites-available/fintech-dashboard:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name dashboard.enterprise.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name dashboard.enterprise.com;

    ssl_certificate /etc/letsencrypt/live/dashboard.enterprise.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/dashboard.enterprise.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Konfigurasi untuk Next.js Server & WebSocket Backend
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Timeout tuning untuk WebSocket jangka panjang
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

Aktifkan konfigurasi:

sudo ln -s /etc/nginx/sites-available/fintech-dashboard /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Langkah 2: Implementasi Zustand Store untuk High-Frequency WebSocket Stream

Buat file src/store/useFinancialStore.ts untuk mengelola state transaksi tanpa memicu render global:

import { create } from 'zustand';

export interface Transaction {
  id: string;
  symbol: string;
  price: number;
  volume: number;
  timestamp: number;
  type: 'BUY' | 'SELL';
}

interface FinancialState {
  transactions: Transaction[];
  activeSubscriptions: string[];
  addTransaction: (tx: Transaction) => void;
  setSubscriptions: (symbols: string[]) => void;
}

export const useFinancialStore = create<FinancialState>((set) => ({
  transactions: [],
  activeSubscriptions: [],
  addTransaction: (tx) =>
    set((state) => ({
      // Batasi array hingga 100 data terbaru untuk mencegah memory leak
      transactions: [tx, ...state.transactions].slice(0, 100),
    })),
  setSubscriptions: (symbols) => set({ activeSubscriptions: symbols }),
}));

Langkah 3: Membuat Komponen Klien WebSocket Manager (src/components/WebSocketProvider.tsx)

Komponen ini mengelola siklus hidup koneksi WebSocket secara aman di sisi klien:

'use client';

import { useEffect, useRef } from 'react';
import { useFinancialStore, Transaction } from '@/store/useFinancialStore';

export default function WebSocketProvider({ children }: { children: React.ReactNode }) {
  const addTransaction = useFinancialStore((state) => state.addTransaction);
  const wsRef = useRef<WebSocket | null>(null);

  useEffect(() => {
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsUrl = `${protocol}//${window.location.host}/ws-stream`;
    
    const connect = () => {
      wsRef.current = new WebSocket(wsUrl);

      wsRef.current.onopen = () => {
        console.log('[WebSocket] Terhubung ke server finansial.');
      };

      wsRef.current.onmessage = (event) => {
        try {
          const data: Transaction = JSON.parse(event.data);
          addTransaction(data);
        } catch (error) {
          console.error('[WebSocket] Gagal memparsing JSON:', error);
        }
      };

      wsRef.current.onclose = () => {
        console.warn('[WebSocket] Koneksi terputus. Mencoba menyambungkan kembali dalam 3 detik...');
        setTimeout(connect, 3000);
      };

      wsRef.current.onerror = (error) => {
        console.error('[WebSocket Error]:', error);
        wsRef.current?.close();
      };
    };

    connect();

    return () => {
      wsRef.current?.close();
    };
  }, [addTransaction]);

  return <>{children}</>;
}

Langkah 4: Implementasi Komponen Presentasi Real-Time dengan Selector (src/components/TransactionTicker.tsx)

'use client';

import React from 'react';
import { useFinancialStore } from '@/store/useFinancialStore';

export function TransactionTicker() {
  // Menggunakan selector Zustand agar komponen hanya merender ulang saat array 'transactions' berubah
  const transactions = useFinancialStore((state) => state.transactions);

  return (
    <div className="overflow-x-auto rounded-lg border border-slate-800 bg-slate-950 p-4 shadow-2xl">
      <h3 className="mb-4 text-lg font-semibold text-slate-100">Real-Time Transaction Stream</h3>
      <table className="w-full text-left text-sm text-slate-300">
        <thead className="border-b border-slate-800 text-xs uppercase text-slate-500">
          <tr>
            <th className="py-2">Symbol</th>
            <th className="py-2">Type</th>
            <th className="py-2">Price</th>
            <th className="py-2">Volume</th>
            <th className="py-2">Timestamp</th>
          </tr>
        </thead>
        <tbody>
          {transactions.map((tx) => (
            <tr key={tx.id} className="border-b border-slate-900/50 transition-colors hover:bg-slate-900/30">
              <td className="py-2 font-mono font-medium text-slate-100">{tx.symbol}</td>
              <td className={`py-2 font-bold ${tx.type === 'BUY' ? 'text-emerald-400' : 'text-rose-400'}`}>
                {tx.type}
              </td>
              <td className="py-2 font-mono">${tx.price.toFixed(2)}</td>
              <td className="py-2 font-mono">{tx.volume}</td>
              <td className="py-2 text-slate-500">
                {new Date(tx.timestamp).toLocaleTimeString()}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Langkah 5: Menggabungkan dalam Server Component Utama (src/app/page.tsx)

import { Suspense } from 'react';
import WebSocketProvider from '@/components/WebSocketProvider';
import { TransactionTicker } from '@/components/TransactionTicker';

// Ini adalah React Server Component (RSC)
export default async function FinancialDashboardPage() {
  return (
    <main className="min-h-screen bg-slate-900 p-8 text-slate-100">
      <div className="mx-auto max-w-7xl space-y-8">
        <header className="border-b border-slate-800 pb-4">
          <h1 className="text-3xl font-extrabold tracking-tight">Enterprise Financial Hub</h1>
          <p className="text-sm text-slate-400">Pemantauan Likuiditas & Order Book Real-Time via WebSocket & RSC</p>
        </header>

        <WebSocketProvider>
          <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
            <div className="lg:col-span-2">
              <Suspense fallback={<div className="text-slate-500">Memuat Ticker...</div>}>
                <TransactionTicker />
              </Suspense>
            </div>
            <div className="rounded-lg border border-slate-800 bg-slate-950 p-4">
              <h3 className="text-lg font-semibold text-slate-100">System Metrics</h3>
              <p className="mt-2 text-sm text-slate-400">Koneksi WebSocket: <span className="text-emerald-400 font-bold">Aktif</span></p>
            </div>
          </div>
        </WebSocketProvider>
      </div>
    </main>
  );
}

6. Bedah Kode & Penjelasan Detail Baris Kunci

  • proxy_set_header Upgrade $http_upgrade; & proxy_set_header Connection $connection_upgrade; (Nginx): Baris ini krusial untuk memberitahu reverse proxy Nginx agar meneruskan header HTTP Upgrade dari klien ke backend Node.js. Jika baris ini terlewat, Nginx akan menutup koneksi WebSocket dan mengembalikan error HTTP 400 Bad Request.
  • .slice(0, 100) (Zustand Store): Membatasi ukuran maksimum array transaksi dalam memori state. Tanpa pembatasan ini, streaming data berfrekuensi tinggi selama berjam-jam akan menyebabkan konsumsi memori browser meroket (memory bloat), berujung pada crash browser.
  • useFinancialStore((state) => state.transactions) (Selector Pattern): Penggunaan selektor mencegah komponen lain di luar TransactionTicker ikut merender ulang saat transaksi baru masuk. Ini adalah kunci utama optimalisasi performa DOM pada React modern.

7. Skenario Produksi & Studi Kasus Dunia Nyata

Dalam implementasi nyata di bursa aset digital skala menengah, sebuah server WebSocket tunggal dapat menerima hingga 15.000 pesan per detik. Untuk menangani beban ini tanpa single point of failure:

  1. Arsitektur Pub/Sub Redis: Backend Node.js tidak langsung mengirim data ke klien secara monolitik. Server menerima stream dari exchange eksternal, mempublishnya ke Redis Channel, lalu beberapa worker instance Node.js mendistribusikan pesan tersebut ke klien WebSocket yang terhubung melalui load balancer Nginx.
  2. Clustering Nginx & Sticky Sessions: Memastikan koneksi WebSocket persisten dipertahankan pada instance server backend yang sama menggunakan mekanisme hash IP klien pada konfigurasi Nginx upstream.

8. Common Pitfalls, Anti-Patterns & Solusi

  • Anti-Pattern 1: Menyimpan WebSocket Instance di dalam React State (useState). Dampak: Menyebabkan re-render tidak berkesudahan karena instansiasi objek WebSocket memicu pembaruan referensi memori. Solusi: Gunakan useRef atau simpan di dalam modul state eksternal seperti Zustand/Redux tanpa reaktivitas langsung.
  • Anti-Pattern 2: Tidak Menangani Rekoneksi Otomatis (Reconnection Strategy). Dampak: Ketika jaringan seluler pengguna terputus sesaat, dashboard membeku permanen dan memaksa pengguna melakukan hard refresh. Solusi: Terapkan exponential backoff retry algorithm pada event onclose WebSocket.
  • Pitfall 3: Mengabaikan Timeout Nginx/Proxy. Dampak: Koneksi WebSocket terputus secara sepihak oleh Nginx setiap 60 detik. Solusi: Tambahkan proxy_read_timeout 86400s; dan kirimkan ping/pong heartbeat frame secara berkala dari server.

9. Benchmark, Metrik Kinerja & Tuning Performa

Pengujian performa dilakukan menggunakan tool k6 untuk mensimulasikan 5.000 klien konkuren yang membuka koneksi WebSocket dan menerima 50 pesan per detik per klien.

  • Hasil Tanpa Optimasi Zustand Selector: Penggunaan CPU browser mencapai 85%-100%, frame rate drop hingga 12 FPS, latensi render DOM rata-rata 110ms.
  • Hasil Dengan RSC + Zustand + WebSocket Terisolasi: Penggunaan CPU browser turun menjadi 12%-18%, frame rate stabil di 60 FPS, latensi render DOM rata-rata < 12ms.

Langkah tuning lanjutan pada sistem operasi Linux (/etc/sysctl.conf) untuk menangani puluhan ribu koneksi TCP konkuren:

fs.file-max = 2097152
net.ipv4.tcp_max_syn_backlog = 65535
net.core.somaxconn = 65535
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

Terapkan perubahan dengan perintah:

sudo sysctl -p

10. Checklist Penerapan Produksi & Kesimpulan

Sebelum merilis dashboard finansial ke lingkungan produksi, pastikan seluruh checklist berikut terpenuhi:

  • Konfigurasi SSL/TLS Let's Encrypt aktif dan diperbarui otomatis via Certbot cronjob.
  • Nginx dikonfigurasi dengan proxy_pass, proxy_http_version 1.1, dan header Upgrade WebSocket yang valid.
  • Zustand store membatasi ukuran array data maksimum untuk mencegah memory leak.
  • Mekanisme Heartbeat (Ping/Pong) aktif pada server WebSocket untuk mendeteksi koneksi dead/ghost.
  • Parameter kernel Linux (somaxconn, file-max) telah disesuaikan untuk high concurrency.
  • Pemantauan Error (Sentry / Prometheus & Grafana) terintegrasi pada sisi klien dan server.

Dengan menggabungkan kekuatan React Server Components untuk efisiensi muatan awal, Zustand untuk manajemen state reaktif berbasis selektor, serta infrastruktur Nginx dan WebSocket yang di-tuning secara presisi, Anda kini memiliki fondasi arsitektur enterprise yang tangguh, aman, dan berkinerja tinggi untuk aplikasi finansial real-time.

🏷️ Tags: #Nginx #Websocket #Typescript #React #Zustand