L
Vue.js • Literally Web ID

Optimasi Performa Single-Page Applications dengan Vue.js 3 dan Vite di Production

⏱️ 18 menit baca • 📅 13 Oct 2026 • 👁️ 1 views
Optimasi Performa Single-Page Applications dengan Vue.js 3 dan Vite di Production

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap pengembangan aplikasi web modern skala enterprise, Single-Page Application (SPA) telah menjadi standar de facto untuk menghadirkan pengalaman pengguna yang interaktif, dinamis, dan menyerupai aplikasi desktop. Namun, arsitektur SPA membawa tantangan inheren yang berbanding terbalik dengan skalabilitasnya: penumpukan muatan bundle JavaScript awal (initial bundle bloat). Seiring bertambahnya fitur bisnis, integrasi pustaka pihak ketiga, dan kompleksitas visual, ukuran berkas aset JavaScript membengkak secara eksponensial dari ratusan kilobyte menjadi belasan megabyte jika tidak dikelola secara presisi.

Bottleneck Performa di Dunia Nyata

Pada infrastruktur produksi, ukuran bundle yang tidak terkontrol memicu serangkaian degradasi performa sistemik:

  1. High Latency & Slow Network Bottleneck: Pada jaringan seluler atau koneksi berkecepatan rendah (3G/4G dengan packet loss tinggi), unduhan berkas JavaScript sebesar 2–5 MB yang tidak dipecah dapat memakan waktu 5 hingga 15 detik, memicu bounce rate di atas 50%.
  2. Main Thread Saturation (CPU Bottleneck): Mengunduh bundle hanyalah tahap awal. Perangkat klien (terutama kelas menengah ke bawah) harus mem-parse, mengompilasi (JIT compilation), dan mengeksekusi bytecode JavaScript tersebut. Proses ini memblokir main thread browser, merusak metrik Core Web Vitals seperti Total Blocking Time (TBT) dan Interaction to Next Paint (INP).
  3. Memory Footprint & Garbage Collection Thrashing: Inisialisasi state monolithic raksasa secara serentak di runtime membebani heap memory browser klien. Hal ini memicu siklus Garbage Collection (GC) yang agresif, menyebabkan stuttering dan frame drops.
  4. Cache Invalidation Cascades: Ketika seluruh kode aplikasi dikemas ke dalam satu berkas vendor.js atau app.js, perubahan satu baris kode logika bisnis akan mengubah hash berkas tersebut, memaksa seluruh pengguna mengunduh ulang pustaka pihak ketiga yang tidak pernah berubah.

Analisis Komparatif: Sebelum vs Sesudah Penerapan Arsitektur Teroptimasi

Parameter Metrik Monolithic SPA Tradisional (Tanpa Optimasi) Production-Grade SPA (Vue 3 + Vite + Edge Tuning)
Initial Bundle Size (Gzip/Brotli) 3.8 MB (Gzip) / 14.2 MB (Raw) 148 KB (Brotli) / 480 KB (Raw initial chunk)
Largest Contentful Paint (LCP) 4.8 detik 0.95 detik
Total Blocking Time (TBT) 1.420 ms 45 ms
Interaction to Next Paint (INP) 380 ms (Poor) 48 ms (Good)
Cache Hit Ratio (Edge CDN) ~18% (sering invalidated) ~94% (granular vendor hashing)
Memory Consumption (Idle) ~185 MB Heap ~38 MB Heap

2. Konsep Inti & Deep Dive Arsitektur

Untuk merekayasa sistem SPA Vue.js 3 dengan Vite yang mampu melayani jutaan request dengan performa puncak, kita harus memahami cara kerja kompilator, bundler, dan runtime secara holistik dari browser hingga layer Edge CDN.

+-----------------------------------------------------------------------------------+
|                             BROWSER CLIENT RUNTIME                                |
+-----------------------------------------------------------------------------------+
       |                                           ^
       | 1. HTTP/2 GET /index.html                 | 4. Minimal HTML Returned
       v                                           |
+-----------------------------------------------------------------------------------+
|                       EDGE LAYER (Cloudflare CDN / WAF)                          |
|  - Brotli Compression Engine (Level 11)                                           |
|  - Cache Control: s-maxage=31536000, immutable (Static Chunks)                    |
|  - Cache Control: no-cache, no-store, must-revalidate (index.html)                |
+-----------------------------------------------------------------------------------+
       |                                           ^
       | 2. Edge Cache Miss: Request Origin        | 3. Upstream Response
       v                                           |
+-----------------------------------------------------------------------------------+
|                    ORIGIN WEB SERVER (Nginx / HTTP/2 Gateway)                    |
|  - Pre-compressed Static Serving (.br / .gz) via ngx_brotli_static                |
|  - Security Headers (CSP, HSTS, X-Content-Type-Options)                           |
+-----------------------------------------------------------------------------------+
                                  |
       +--------------------------+--------------------------+
       |                                                     |
       v                                                     v
+----------------------------------+   +----------------------------------+
|     VITE ROLLUP CHUNKING         |   |    PINIA REACTIVE STATE ENGINE   |
|  - Dynamic Import Boundaries     |   |  - Route-level Dynamic Stores    |
|  - Manual Granular Chunks        |   |  - ShallowRef for Big Datasets   |
|  - AST Tree Shaking Elimination  |   |  - Garbage Collected on Unmount  |
+----------------------------------+   +----------------------------------+

1. Vite & Rollup Module Bundling Under the Hood

Vite menggunakan esbuild saat mode development untuk pra-bundling dependensi dengan kecepatan luar biasa berbasis Go. Namun, pada fase build produksi, Vite mendelegasikan tugas ke Rollup. Rollup menganalisis Abstract Syntax Tree (AST) berbasis ECMAScript Modules (ESM) statis. Melalui proses Scope Hoisting dan analisis ketergantungan statis, Rollup melacak modul mana yang benar-benar dipanggil (sideEffects: false) dan secara matematis menghapus ekspor mati (Dead Code Elimination atau Tree Shaking).

2. Code Splitting Berbasis Dynamic Import

Ketika browser mengeksekusi sintaks import('./views/AnalyticsView.vue'), browser tidak langsung mengunduh modul tersebut saat startup. Rollup memotong dependensi tersebut dari graph utama dan membungkusnya menjadi chunk fisik terpisah. Di browser, fungsi ini diubah menjadi promise berbasis elemen <link rel="modulepreload"> atau penarikan modul runtime asinkron yang dieksekusi hanya ketika router mencocokkan navigasi URL target.

3. Reaktivitas Vue 3 & Pinia Engine

Sistem reaktivitas Vue 3 menggunakan JavaScript Proxy untuk mengintersepsi operasi baca/tulis (get/set) objek state. Pada aplikasi enterprise dengan puluhan ribu baris data tabel, membungkus setiap nested object ke dalam reactive() menghasilkan overhead memori yang signifikan. Pinia mengisolasi store ke dalam modul-modul modular, memungkinkan teknik pembongkaran store (store disposal) saat komponen di-unmount, mencegah memory leak yang sering luput dari monitoring standar.


3. Perbandingan Komparatif / Analisis Pendekatan

Sebelum menetapkan arsitektur, arsitek sistem harus memilih strategi chunking dan manajemen state yang tepat berdasarkan karakteristik beban kerja sistem.

Perbandingan Strategi Chunking Aset

Pendekatan Chunking Mekanisme Kelebihan Kekurangan Konsumsi Resource Klien
Single Monolithic Chunk Semua dependensi dan kode bisnis dijadikan satu file tunggal. Tidak ada latensi round-trip HTTP tambahan antar rute. Ukuran bundle masif; memblokir browser thread; cache invalidation total setiap rilis. Tinggi (RAM & CPU): Crash pada perangkat low-end.
Over-Aggressive Splitting Setiap single komponen dan library dipecah menjadi file individual. Ukuran file individual sangat kecil (<5 KB). Masalah HTTP Waterfall: Terlalu banyak round-trip TLS/TCP, meningkatkan overhead socket. Sedang (Jaringan): Latensi round-trip membengkak jika HTTP/2 tidak optimal.
Granular Domain Chunking (Rekomendasi) Mengelompokkan vendor berdasarkan domain fungsi (e.g., core, charts, ui) + Lazy load rute. Rasio caching optimal; dependensi besar diisolasi; zero-blocking pada rute awal. Membutuhkan arsitektur konfigurasi build manual yang teliti pada Vite. Optimal (CPU/RAM/Net): Seimbang dan terprediksi.

Trade-off Evaluasi Komparatif Arsitektur

  • Manual Chunks vs Auto-splitting: Auto-splitting bawaan Rollup kerap membuat circular dependency chunks. Mengatur manualChunks secara deterministik mencegah chunk duplikasi.
  • Pinia Stores: Monolithic vs Route-Scoped: Menyimpan seluruh domain data (User, Auth, Products, Invoices, Analytics) dalam satu store global akan meningkatkan memory footprint hingga 300%. Route-scoped store membebaskan memori segera setelah user meninggalkan halaman bersangkutan.

4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum melangkah ke konfigurasi, pastikan lingkungan deployment memenuhi standar enterprise berikut:

Spesifikasi Sistem Minimal

  • Node.js Environment: Node.js LTS v20.x atau v22.x (disarankan v20.12.0+) dengan runtime V8 modern.
  • Package Manager: pnpm v9.x (direkomendasikan untuk deduplikasi dependensi simlink hardlink yang ketat) atau npm v10.x.
  • Reverse Proxy / Origin Server: Nginx v1.24+ (dikompilasi dengan modul ngx_brotli) atau Caddy v2.7+.
  • Sistem Operasi Host: Ubuntu 22.04 LTS / Debian 12 (minimal 2 vCPU, 4 GB RAM untuk kompilasi build Rollup parallel).

Verifikasi Lingkungan Awal

Jalankan perintah berikut pada terminal server atau runner CI/CD:

# Verifikasi runtime Node.js dan package manager
node -v
pnpm -v

# Pastikan ketersediaan Brotli pada Nginx
nginx -V 2>&1 | grep -o 'ngx_brotli'

# Verifikasi batas memori runner untuk Vite build
node -e 'console.log(v8.getHeapStatistics().heap_size_limit / (1024 * 1024) + " MB")'

[!IMPORTANT] Pada runner CI/CD seperti GitHub Actions atau GitLab CI, pastikan alokasi heap Node.js minimal 4096 MB (export NODE_OPTIONS="--max-old-space-size=4096") agar proses AST parsing dan CSS extraction Rollup tidak mengalami Out of Memory (OOM) saat memproses basis kode enterprise.


5. Panduan Implementasi Langkah demi Langkah

Berikut implementasi produksi teruji tanpa placeholder, mencakup konfigurasi Vite, Router, State Management, dan Konfigurasi Reverse Proxy Nginx.

1. Konfigurasi Produksi Vite (vite.config.ts)

File konfigurasi ini mengaktifkan visualizer bundle, kompresi aset Brotli/Gzip otomatis, minifikasi Esbuild drop-console, dan chunking Rollup granuler deterministik.

import { defineConfig, type PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
import compression from 'vite-plugin-compression';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig(({ mode }) => {
  const isProd = mode === 'production';

  return {
    plugins: [
      vue({
        script: {
          defineModel: true,
          propsDestructure: true,
        },
      }),
      // Menghasilkan kompresi Brotli saat build
      compression({
        algorithm: 'brotliCompress',
        ext: '.br',
        threshold: 1024,
        deleteOriginFile: false,
      }),
      // Menghasilkan kompresi Gzip sebagai fallback klien lama
      compression({
        algorithm: 'gzip',
        ext: '.gz',
        threshold: 1024,
        deleteOriginFile: false,
      }),
      // Visualizer bundle report jika dibutuhkan inspeksi CI/CD
      isProd && (visualizer({
        filename: './dist/stats.html',
        open: false,
        gzipSize: true,
        brotliSize: true,
      }) as PluginOption),
    ],
    resolve: {
      alias: {
        '@': path.resolve(__dirname, './src'),
      },
    },
    build: {
      target: 'es2022',
      outDir: 'dist',
      assetsDir: 'assets',
      cssCodeSplit: true,
      sourcemap: false,
      chunkSizeWarningLimit: 600,
      minify: 'esbuild',
      rollupOptions: {
        output: {
          // Pola penamaan hash terstruktur untuk caching tingkat tinggi
          entryFileNames: 'assets/js/[name].[hash].js',
          chunkFileNames: 'assets/js/[name].[hash].js',
          assetFileNames: (assetInfo) => {
            const info = assetInfo.name ? assetInfo.name.split('.') : [];
            const ext = info[info.length - 1];
            if (/\.(css)$/i.test(assetInfo.name || '')) {
              return 'assets/css/[name].[hash][extname]';
            }
            if (/\.(png|jpe?g|gif|svg|webp|avif)$/i.test(assetInfo.name || '')) {
              return 'assets/images/[name].[hash][extname]';
            }
            if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name || '')) {
              return 'assets/fonts/[name].[hash][extname]';
            }
            return 'assets/[name].[hash][extname]';
          },
          manualChunks(id) {
            // Isolasi pustaka runtime inti Vue
            if (id.includes('node_modules/vue/') || 
                id.includes('node_modules/@vue/') || 
                id.includes('node_modules/vue-router/') || 
                id.includes('node_modules/pinia/')) {
              return 'vendor-vue-core';
            }
            // Isolasi pustaka visualisasi berat
            if (id.includes('node_modules/echarts/') || id.includes('node_modules/zrender/')) {
              return 'vendor-charts';
            }
            // Isolasi komponen antarmuka pihak ketiga
            if (id.includes('node_modules/@headlessui/') || id.includes('node_modules/@heroicons/')) {
              return 'vendor-ui';
            }
            // Isolasi pustaka utilitas
            if (id.includes('node_modules/lodash-es/') || id.includes('node_modules/dayjs/')) {
              return 'vendor-utils';
            }
            // Tangani node_modules generik lainnya
            if (id.includes('node_modules/')) {
              return 'vendor-others';
            }
          },
        },
      },
    },
    esbuild: {
      drop: isProd ? ['console', 'debugger'] : [],
      legalComments: 'none',
    },
  };
});

2. Implementasi Dynamic Routing & Preloading Vue Router (src/router/index.ts)

Konfigurasi router ini mengimplementasikan route lazy loading, dynamic chunk naming, dan dynamic prefetch saat navigasi hover pada elemen navigasi.

import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Dashboard',
    // Rute utama diimpor secara dinamis dengan magic comments Vite
    component: () => import(/* webpackChunkName: "view-dashboard" */ '@/views/DashboardView.vue'),
    meta: { requiresAuth: true },
  },
  {
    path: '/analytics',
    name: 'Analytics',
    component: () => import(/* webpackChunkName: "view-analytics" */ '@/views/AnalyticsView.vue'),
    meta: { requiresAuth: true },
  },
  {
    path: '/settings',
    name: 'Settings',
    component: () => import(/* webpackChunkName: "view-settings" */ '@/views/SettingsView.vue'),
    meta: { requiresAuth: true },
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/NotFoundView.vue'),
  },
];

export const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition;
    }
    return { top: 0, behavior: 'smooth' };
  },
});

// Utilitas Prefetch On-Hover untuk mempercepat respons UX
export function prefetchComponent(importer: () => Promise<unknown>): void {
  importer().catch((err) => {
    console.error('Failed to prefetch component asynchronously', err);
  });
}

3. Implementasi Enterprise Pinia Store Teroptimasi (src/stores/analytics.ts)

Store ini mendemonstrasikan isolasi data, pemanfaatan shallowRef untuk memangkas overhead reaktivitas data tabel besar, dan fungsi reset/teardown otomatis untuk membersihkan memori.

import { defineStore } from 'pinia';
import { ref, shallowRef } from 'vue';

export interface AnalyticsDataPoint {
  id: string;
  timestamp: number;
  metricValue: number;
  metadata: Record<string, string>;
}

export const useAnalyticsStore = defineStore('analytics', () => {
  // Menggunakan shallowRef agar engine Vue tidak membungkus ribuan nested properties dengan Proxy
  const rawMetrics = shallowRef<AnalyticsDataPoint[]>([]);
  const isLoading = ref<boolean>(false);
  const lastSynced = ref<Date | null>(null);

  async function fetchHeavyMetrics(dateRange: string): Promise<void> {
    isLoading.value = true;
    try {
      const response = await fetch(`/api/v1/metrics?range=${encodeURIComponent(dateRange)}`);
      if (!response.ok) {
        throw new Error(`HTTP Error: ${response.status}`);
      }
      const data: AnalyticsDataPoint[] = await response.json();
      // shallowRef bereaksi cepat saat referensi array baru di-assign
      rawMetrics.value = Object.freeze(data);
      lastSynced.value = new Date();
    } finally {
      isLoading.value = false;
    }
  }

  // Garbage collector helper: Panggil ini saat unmounting komponen view
  function $disposeStore(): void {
    rawMetrics.value = [];
    isLoading.value = false;
    lastSynced.value = null;
  }

  return {
    rawMetrics,
    isLoading,
    lastSynced,
    fetchHeavyMetrics,
    $disposeStore,
  };
});

4. Konfigurasi Nginx Web Server Produksi (/etc/nginx/sites-available/spa.conf)

File konfigurasi Nginx ini dikhususkan untuk menangani pengiriman berkas statis terkompresi pra-build (Brotli & Gzip), SPA fallback routing, serta immutable edge cache headers.

# Konfigurasi upstream jika ada reverse proxy backend API
upstream backend_api_cluster {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;
    keepalive 32;
}

server {
    listen 80;
    listen [::]:80;
    server_name portal.enterprise.domain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name portal.enterprise.domain.com;

    # Konfigurasi Sertifikat SSL
    ssl_certificate /etc/letsencrypt/live/portal.enterprise.domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/portal.enterprise.domain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    root /var/www/vue-spa/dist;
    index index.html;

    # Integrasi Modul Brotli Statis (ngx_brotli_static)
    brotli_static on;
    gzip_static on;

    # Security Hardening Headers
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.enterprise.domain.com;" always;

    # 1. Routing Entrypoint Utama (HTML Jangan Pernah Dicache secara Agresif)
    location / {
        try_files $uri $uri/ /index.html;
        add_header Cache-Control "no-cache, no-store, must-revalidate" always;
        add_header Pragma "no-cache" always;
        expires 0;
    }

    # 2. Aset Statis dengan Hash (Cache Selamanya di Edge & Browser)
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, max-age=31536000, immutable" always;
        access_log off;
        try_files $uri =404;
    }

    # 3. Proxy API Request ke Backend Cluster
    location /api/ {
        proxy_pass http://backend_api_cluster;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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;
        proxy_connect_timeout 5s;
        proxy_read_timeout 60s;
    }

    # Blokir akses ke file tersembunyi (.git, .env, dsb)
    location ~ /\.(?!well-known) {
        deny all;
    }
}

6. Bedah Kode & Penjelasan Detail Baris Kunci

Setiap segmen konfigurasi di atas memiliki dampak teknis yang mendalam pada arsitektur produksi:

Vite Configuration Deep Dive

  • target: 'es2022': Secara default, bundler menargetkan standar ES lama, menyuntikkan polyfill pembengkak ukuran file. Dengan menargetkan es2022, kita memanfaatkan implementasi native browser modern untuk fitur seperti Promise.any, Class Fields, dan RegExp Match Indices.
  • manualChunks(id): Fungsi deterministik ini mengevaluasi jalur absolut node_modules. Pembagian ke dalam vendor-vue-core, vendor-charts, dan vendor-utils memastikan chunking terisolasi. Jika developer memperbarui fungsi pada dayjs (bagian dari vendor-utils), browser user tidak perlu mengunduh ulang vendor-vue-core dan vendor-charts karena hash mereka tidak bermutasi.
  • esbuild.drop: ['console', 'debugger']: Membersihkan seluruh log internal debug langsung pada level AST compilation tanpa membutuhkan plugin Babel terpisah yang lambat.

Pinia Store Optimization Deep Dive

  • rawMetrics = shallowRef<AnalyticsDataPoint[]>([]): Pada Vue 3, ref() standar mengeksekusi rekursi mendalam (deep reactive conversion) terhadap setiap elemen array dan anak-anak objeknya menggunakan Proxy handler. Ketika array berisi 10.000 data point, proses ini memakan CPU hingga ratusan milidetik. shallowRef hanya mengamati mutasi pada level root .value, mengabaikan nested properties. Hal ini memangkas waktu alokasi memori hingga 90%.
  • Object.freeze(data): Menginstruksikan JavaScript runtime engine bahwa array bersifat immutable, mematikan tracking internal V8 yang tidak perlu.
  • $disposeStore(): Dipanggil di hook onUnmounted() lifecycle komponen view. Ini menghapus referensi memori besar dari heap browser seketika saat navigasi berpindah rute.

Nginx Configuration Deep Dive

  • brotli_static on; gzip_static on;: Menginstruksikan Nginx untuk mencari berkas fisik .br dan .gz yang telah dikompilasi oleh plugin Vite sebelumnya. Nginx tidak perlu menghabiskan siklus CPU server untuk mengompresi stream file secara on-the-fly, menurunkan beban server CPU dari 80% menjadi di bawah 5% saat flash crowd traffic.
  • add_header Cache-Control "public, max-age=31536000, immutable": Berkas di /assets/ memiliki fingerprint unik (contoh: main.d92a4e.js). Atribut immutable memberi tahu browser modern bahwa berkas ini tidak akan pernah berubah kontennya, meniadakan conditional GET requests (status 304) ke server dan langsung menyajikannya dari disk cache lokal.
  • index.html -> Cache-Control: "no-cache, no-store, must-revalidate": Sangat vital. Berkas index HTML tidak boleh disimpan di cache browser. Jika dicache, browser klien tidak akan mengetahui bahwa URL hash pada skrip assets telah diperbarui saat deploy aplikasi baru.

7. Skenario Produksi & Studi Kasus Dunia Nyata

Studi Kasus: Enterprise Financial Dashboard (Traffic Spike Event)

Sebuah platform perbankan digital meluncurkan modul laporan pajak tahunan dengan lonjakan pengunjung bersamaan (high concurrency) mencapai 120.000 pengguna aktif per jam.

[120,000 Concurrent Users]
           |
           v
+-------------------------------------------------------------+
| Cloudflare Edge Network (Cache-Hit Ratio: 97.4%)            |
| - Static Assets (Vendor & App JS Chunks) served directly    |
| - Zero Origin Load for Static Requests                      |
+-------------------------------------------------------------+
           |
           | Only HTML verification & API calls pass through
           v
+-------------------------------------------------------------+
| Origin Nginx Load Balancer (CPU Utilization < 12%)          |
| - Serves index.html with no-cache headers                   |
| - Reverse proxy pass JSON API payloads                     |
+-------------------------------------------------------------+
           |
           v
+-------------------------------------------------------------+
| Client-side Browser Execution                               |
| - Vue Router dynamic chunking: Only 120KB loaded on entry   |
| - Pinia shallowRef: High-volume charts rendered in <60fps   |
+-------------------------------------------------------------+

Kendala Sebelum Optimasi

Aplikasi SPA sebelumnya berukuran 8,2 MB dalam satu file JavaScript bundle. Ketika traffic melonjak:

  1. Bandwidth origin server saturated pada 10 Gbps uplink, memicu packet drop.
  2. Server CPU melonjak ke 100% karena Nginx melakukan on-the-fly gzip compression untuk ribuan koneksi bersamaan.
  3. Waktu muat halaman rata-rata mencapai 14,2 detik di jaringan seluler pengguna.

Hasil Setelah Penerapan Solusi

  1. Penerapan Granular Chunking: Initial payload turun dari 8,2 MB menjadi 112 KB untuk halaman login dan dasbor utama.
  2. Offloading Kompresi Statis: Dengan brotli_static on, Nginx langsung menyajikan stream berkas .br yang telah dibundle oleh Vite. CPU load origin server stabil di angka 8–12% meski traffic meningkat 4x lipat.
  3. Edge Caching Cloudflare: Karena aset JS diberi label immutable, 97.4% request aset ditangani langsung oleh Cloudflare Edge tanpa pernah menyentuh server origin kita.
  4. Waktu Muat (LCP): Turun drastis dari 14,2 detik menjadi 0,84 detik.

8. Common Pitfalls, Anti-Patterns & Solusi

1. The Barrel File Import Anti-Pattern

Mengimpor fungsi utilitas dari berkas index barrel yang mengimpor seluruh dependensi pustaka.

Anti-Pattern:

// Mengimpor dari library besar melalui index root
import { cloneDeep } from 'lodash'; // Mengimpor seluruh lodash library (500KB+)
import { Button } from 'lucide-vue-next'; // Berpotensi menarik ratusan modul ikon

Solusi Rekayasa: Gunakan sub-path ekspor modular ESM statis agar Vite/Rollup dapat melakukan tree shaking murni:

import cloneDeep from 'lodash-es/cloneDeep';
import Button from 'lucide-vue-next/dist/esm/icons/button';

2. Leaking Stores pada Single Page Applications

Membiarkan Pinia menyimpan state halaman sebelumnya di memori tanpa membersihkannya saat berpindah rute.

Anti-Pattern:

// Pengguna meninggalkan halaman analitik, tapi array 50.000 row data tetap menggantung di memory
// Router berpindah ke /profile

Solusi Rekayasa: Manfaatkan lifecycle navigation guards atau component cleanup:

import { onUnmounted } from 'vue';
import { useAnalyticsStore } from '@/stores/analytics';

const store = useAnalyticsStore();

onUnmounted(() => {
  store.$disposeStore();
});

3. Misconfigured Caching pada index.html

Menyetel header Cache-Control: public, max-age=86400 pada seluruh direktori web root, termasuk index.html.

[!WARNING] Jika index.html dicache oleh browser pengguna atau CDN Edge, ketika Anda melakukan build baru dengan chunk hash baru (misal: app.a1b2c3.js berubah menjadi app.x9y8z7.js), browser pengguna akan tetap membaca referensi file lama dari cache lokal index.html. Hasilnya: aplikasi menampilkan layar putih (white screen of death / 404 Chunk Not Found) hingga pengguna membersihkan cache browser secara manual.


9. Benchmark, Metrik Kinerja & Tuning Performa

Untuk memverifikasi efektivitas optimasi secara empiris, kita menggunakan Lighthouse CI dan pengujian beban jaringan menggunakan wrk.

Pengujian Beban Origin Web Server dengan wrk

Jalankan benchmark berikut untuk melihat efisiensi penyajian aset Brotli statis Nginx dibandingkan penyajian dinamis:

# Simulasi 200 koneksi bersamaan selama 30 detik untuk aset bundle chunk
wrk -t8 -c200 -d30s -H "Accept-Encoding: br" https://portal.enterprise.domain.com/assets/js/vendor-vue-core.e4b2d1.js

Hasil Evaluasi:

  • Brotli Dinamis (On-the-fly compression): ~2.400 Requests/sec, Latency p99: 145ms, CPU Usage: 94%.
  • Brotli Static (ngx_brotli_static on): ~18.900 Requests/sec, Latency p99: 12ms, CPU Usage: 6%.

Audit Core Web Vitals Melalui Lighthouse CI

Jalankan automasi audit menggunakan Node.js CLI:

npx lhci collect --url=https://portal.enterprise.domain.com --numberOfRuns=5
npx lhci assert

Target Ambang Batas Metrik Produksi:

{
  "ci": {
    "assert": {
      "assertions": {
        "first-contentful-paint": ["error", { "maxNumericValue": 1000 }],
        "largest-contentful-paint": ["error", { "maxNumericValue": 1500 }],
        "total-blocking-time": ["error", { "maxNumericValue": 100 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.05 }],
        "interaction-to-next-paint": ["error", { "maxNumericValue": 100 }]
      }
    }
  }
}

Analisis Visualisasi Bundle

Analisis berkas ./dist/stats.html yang dihasilkan oleh rollup-plugin-visualizer. Pastikan tidak ada dependensi kembar (duplicated modules). Contoh: jika ditemukan pustaka lodash versi CommonJS dan lodash-es berada dalam satu bundle, tambahkan resolusi alias pada vite.config.ts untuk memaksakan varian ESM.


10. Checklist Penerapan Produksi & Kesimpulan

Sebelum menandai rilis aplikasi siap untuk traffic enterprise publik, verifikasi setiap parameter berikut:

Infrastructure & Network Readiness

  • HTTP/2 atau HTTP/3 (QUIC) telah aktif secara penuh pada Origin dan Edge CDN.
  • Modul ngx_brotli_static terkompilasi dan aktif pada konfigurasi server Nginx.
  • Edge Cache TTL disetel ke immutable (1 tahun) khusus direktori /assets/.
  • index.html memiliki header eksplisit: no-cache, no-store, must-revalidate.
  • DNS Record terhubung dengan perlindungan WAF dan Edge CDN (Cloudflare Proxy ON).

Build & Application Code Readiness

  • Code Splitting Rute: Seluruh halaman sekunder menggunakan lazy loading dynamic import().
  • AST Tree Shaking: Tidak ada barrel file import untuk pustaka ikon atau utilitas.
  • Minifier: console.log dan debugger telah di-drop sepenuhnya oleh Esbuild pada mode production.
  • Pinia State: Dataset array besar menggunakan shallowRef dan memiliki implementasi pembersihan teardown saat komponen di-unmount.
  • Asset Bundle Threshold: Chunk JS entrypoint awal tidak melebihi 200 KB (Brotli compressed).

Kesimpulan

Optimasi Single-Page Application pada tingkat enterprise menuntut integrasi yang harmonis antara penulisan kode komponen pada layer frontend, konfigurasi bundler Rollup/Vite, manajemen lifecycle memori di browser klien, hingga optimasi throughput pada level web server Nginx dan Edge CDN. Dengan mengimplementasikan pemisahan bundle secara granuler, memanfaatkan kompresi statis Brotli, meminimalisir overhead reaktivitas Vue dengan shallowRef, serta menerapkan strategi HTTP caching yang disiplin, aplikasi web Vue 3 dapat beroperasi dengan latensi sub-detik yang stabil di bawah beban lalu lintas berskala besar.

🏷️ Tags: #Nginx #Vite #Web Performance #Vue.js #Pinia