← Kembali ke BlogVue.js

Best Practice SPA Vue.js untuk Enterprise

M. Hidayatullah·2026-02-15·7 min read
/ cover

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 →