Best Practice SPA Vue.js untuk Enterprise
Saat saya mulai project PGN Web Admin di Areeta Amany, codebase Vue di-handover ke saya udah lewat 3 developer berbeda. Hasilnya: struktur folder chaos, state scattered di mana-mana, ada yang pakai Vuex, ada yang pakai composable, ada yang langsung props drilling 5 level. Refactor ke struktur yang sustainable jadi prioritas pertama.
1. Struktur Folder by Feature, Bukan by Type
- ❌ Anti-pattern: by-type folder
src/
components/ ← 200 file campur aduk
views/
store/
utils/- ✅ By-feature folder
src/
features/
auth/
components/
composables/
stores/
api.js
routes.js
dashboard/
...
permohonan/
...
shared/
components/ ← shared cross-feature
composables/Benefit: navigasi codebase 3x lebih cepet. Mau ubah sesuatu di fitur permohonan? Buka satu folder, semuanya di sana.
2. Pinia > Vuex
Untuk project baru, selalu pakai Pinia. Type-safe, devtools-friendly, lebih ringan. Vuex officially recommend Pinia sekarang.
// stores/permohonan.js
import { defineStore } from 'pinia';
export const usePermohonanStore = defineStore('permohonan', {
state: () => ({ items: [], loading: false }),
actions: {
async fetch() {
this.loading = true;
this.items = await api.permohonan.list();
this.loading = false;
},
},
});3. Composable untuk Reusable Logic
Logic yang di-share antar component → ekstrak ke composable. Bukan ke mixin (deprecated pattern).
// composables/usePagination.js
export function usePagination(fetcher) {
const page = ref(1);
const data = ref([]);
const loading = ref(false);
async function load() {
loading.value = true;
data.value = await fetcher(page.value);
loading.value = false;
}
watch(page, load, { immediate: true });
return { page, data, loading, load };
}4. Lazy Load Routes
Bundle size grew jadi 4MB? Lazy load.
const routes = [
{ path: '/dashboard', component: () => import('./features/dashboard/Dashboard.vue') },
{ path: '/permohonan', component: () => import('./features/permohonan/List.vue') },
];Dashboard.vue cuma di-load saat user buka /dashboard. Initial bundle bisa turun 50-70%.
5. API Layer Konsisten
Jangan campur axios call di component. Bikin API layer:
// features/permohonan/api.js
import http from '@/shared/http';
export const permohonanApi = {
list: (params) => http.get('/permohonan', { params }),
byId: (id) => http.get(`/permohonan/${id}`),
approve: (id) => http.post(`/permohonan/${id}/approve`),
};Mau swap REST ke GraphQL? Cuma ubah 1 file.
6. Error Boundary & Loading Pattern
Untuk SPA enterprise, selalu punya pattern standar untuk loading + error state:
<template>
<Loading v-if="loading" />
<ErrorState v-else-if="error" :error="error" @retry="load" />
<Content v-else :data="data" />
</template>Konsistensi UX = lebih kecil cognitive load buat user.
Kesimpulan
SPA enterprise scalable bukan tentang pakai library tercanggih, tapi tentang konsistensi & disiplin struktur. Tim baru di-onboard 1 minggu, bukan 1 bulan. Itu ROI sesungguhnya dari best practice.
Tulisan oleh M. Hidayatullah — Software Engineer Indonesia.
Diskusi Proyek →