Struktur Folder Nuxt 3 untuk Project Besar (Best Practice 2026)
Struktur Folder Nuxt 3 untuk Project Besar (Best Practice 2026) - Kalau kamu pakai Nuxt 3 untuk project kecil, biasanya struktur default sudah cukup. Tapi begitu aplikasi mulai membesar (banyak fitur, tim bertambah, API makin banyak), struktur folder yang asal-asalan akan bikin:
- Developer bingung cari file
- Logic campur aduk di page
- Bug sulit dilacak
- Refactor makin mahal
Di artikel ini saya bagikan struktur folder Nuxt 3 yang rapi untuk project besar (best practice 2026), lengkap dengan pola composables, services, stores, dan standar penamaan file.
🎯 Tujuan Struktur Folder yang “Bener”
Struktur folder bukan soal “gaya”, tapi soal maintainability.
- Scalable: fitur baru masuk tanpa merusak struktur
- Readable: orang baru join cepat paham
- Separation of concerns: UI, logic, dan API tidak campur
- Testable: service & composable mudah diuji
📦 Struktur Folder Rekomendasi untuk Project Besar
Ini struktur yang saya rekomendasikan untuk Nuxt 3 yang sudah “serius”:
/ (root)
├─ app.vue
├─ nuxt.config.ts
├─ package.json
├─ .env
├─ public/
├─ server/
│ ├─ api/
│ ├─ middleware/
│ ├─ utils/
│ └─ plugins/
└─ src/
├─ assets/
├─ components/
│ ├─ base/
│ ├─ ui/
│ └─ feature/
├─ composables/
├─ layouts/
├─ middleware/
├─ pages/
├─ plugins/
├─ services/
├─ stores/
├─ types/
└─ utils/
Kenapa saya pisahkan ke folder src/?
Karena project besar akan punya banyak folder, dan src/ membantu rapihin “application code” supaya root tetap bersih.
Catatan: Nuxt 3 mendukung srcDir. Tambahkan ini di nuxt.config.ts:
export default defineNuxtConfig({
srcDir: 'src/',
})
🧱 Penjelasan Tiap Folder (yang paling penting)
1) pages/ = Routing + Page Composition
Prinsipnya: pages jangan jadi “tempat semua logic numpuk”. Pages cukup:
- ambil data dari composable/store
- susun komponen
- set metadata halaman (SEO)
Contoh page yang “bersih”:
<script setup>
definePageMeta({ middleware: ['auth'] })
useHead({
title: 'Dashboard',
meta: [{ name: 'description', content: 'Dashboard Nuxt 3' }],
})
const { items, loading, fetchItems } = useDashboard()
onMounted(fetchItems)
</script>
<template>
<div>
<h1>Dashboard</h1>
<DashboardTable :items="items" :loading="loading" />
</div>
</template>
2) components/ = UI Reusable
Untuk project besar, saya sarankan pola ini:
components/base/→ komponen super generic (Button, Input, Modal)components/ui/→ komponen UI reusable tapi lebih “app-specific” (DataTable, Pagination)components/feature/→ komponen spesifik fitur (UserForm, OrderList)
Contoh:
components/
├─ base/
│ ├─ BaseButton.vue
│ ├─ BaseInput.vue
│ └─ BaseModal.vue
├─ ui/
│ ├─ DataTable.vue
│ ├─ Pagination.vue
│ └─ LoadingSpinner.vue
└─ feature/
└─ users/
├─ UserForm.vue
├─ UserCard.vue
└─ UserTable.vue
3) composables/ = Logic Reusable (Bukan UI)
Composable adalah tempat logic yang dipakai lintas page/komponen: fetch data, pagination, state lokal, dan side-effect.
Contoh: useUsers yang fokus pada domain “users”.
// src/composables/useUsers.js
import { ref } from 'vue'
import { usersService } from '@/services/usersService'
export function useUsers() {
const users = ref([])
const loading = ref(false)
const error = ref(null)
const fetchUsers = async (params = {}) => {
loading.value = true
error.value = null
try {
users.value = await usersService.list(params)
} catch (e) {
error.value = e
} finally {
loading.value = false
}
}
return { users, loading, error, fetchUsers }
}
Rule penting: composable jangan langsung “tau” soal tampilan (misalnya toast UI khusus). Itu tugas layer UI atau plugin.
4) services/ = Tempat API Call
Ini bagian yang sering dilupakan. Banyak orang API call langsung di page/composable. Untuk project besar, pisahkan ke services.
Contoh service:
// src/services/api.js
export const api = {
async get(url) {
return await $fetch(url, { method: 'GET' })
},
async post(url, body) {
return await $fetch(url, { method: 'POST', body })
},
}
// src/services/usersService.js
import { api } from '@/services/api'
export const usersService = {
async list(params) {
return await api.get('/api/users?' + new URLSearchParams(params))
},
async create(payload) {
return await api.post('/api/users', payload)
},
}
Dengan pola ini, besok kalau ganti base URL, ganti auth header, atau pindah backend—kamu ubah di satu tempat.
5) stores/ (Pinia) = Global State
Kalau data dipakai lintas banyak page dan harus konsisten (misal user login, role, permission), taruh di store.
// src/stores/authStore.js
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
roles: [],
}),
getters: {
isLoggedIn: (state) => !!state.user,
},
actions: {
setUser(user) {
this.user = user
},
logout() {
this.user = null
this.roles = []
},
},
})
Rule simpel:
- Composable = logic
- Store = global state
6) middleware/ = Guard & Route Rules
Untuk project besar, middleware harus rapi. Pisahkan berdasarkan fungsi:
middleware/
├─ auth.js
└─ guest.js
// src/middleware/auth.js
export default defineNuxtRouteMiddleware(() => {
const token = useCookie('auth_token')
if (!token.value) return navigateTo('/login')
})
7) server/ (Nitro) = Backend Tipis / API
Kalau kamu pakai server route Nuxt (Nitro), rapikan:
server/
├─ api/
│ ├─ users.get.js
│ ├─ users.post.js
│ └─ auth/
│ └─ login.post.js
├─ middleware/
└─ utils/
Dengan struktur ini, backend tipis tetap maintainable.
📌 Konvensi Penamaan (biar tim rapi)
- Components: PascalCase →
UserTable.vue - Composables: camelCase dengan prefix use →
useUsers.js - Stores: camelCase + Store →
authStore.js - Services: camelCase + Service →
usersService.js - Utils: fungsi kecil →
formatCurrency.js
📊 “Do & Don’t” untuk Project Besar
| Area | Do ✅ | Don’t ❌ |
|---|---|---|
| Pages | Tipis, fokus susun UI + call composable/store | Taruh semua logic fetch & transform data di page |
| API Call | Pisahkan ke services | Fetch random di banyak tempat |
| State | Global di Pinia, lokal di composable | Semua dimasukin store |
| Components | Base/UI/Feature terpisah | Semua ditumpuk 1 folder tanpa grouping |
⚠️ Error Umum yang Sering Terjadi (dan Solusinya)
1) “Project makin besar, tapi folder makin berantakan”
Solusi: mulai grouping components/feature, pisahkan services dan composables, lalu konsisten naming.
2) “Logic fetch data tersebar, susah tracing bug”
Solusi: buat satu service per domain (users, orders, products). Semua API call lewat service.
3) “Store terlalu gemuk”
Solusi: store hanya global state. Logic yang reusable taruh composable. Helper taruh utils.
✅ Checklist Implementasi (biar kamu tinggal eksekusi)
- Gunakan
srcDiragar root bersih - Pages tipis, logic pindah ke composables/services
- Buat folder
components/base,components/ui,components/feature - API call hanya lewat
services/ - Global state lewat Pinia (stores)
- Middleware dipisah (auth/guest/role)
🔗 Baca Juga (Internal Link Seri Nuxt Authority)
- Vue 3 Composition API: Pola yang Benar untuk Project Nyata
- Pinia untuk State Management: Pola Scalable
- Authentication Login di Nuxt 3 (Cookie httpOnly)
Kesimpulan
Struktur folder yang benar akan menentukan apakah project Nuxt 3 kamu bisa berkembang dengan nyaman atau malah jadi “legacy” dalam beberapa bulan.
Kalau kamu ingin Nuxt 3 yang scalable, kuncinya adalah:
- Pages fokus UI
- Logic di composables
- API di services
- State global di stores
Dengan pola ini, project kamu lebih rapi, mudah dimaintain, dan siap dikerjakan tim.
FAQ
Q: Harus pakai src/ gak?
A: Tidak wajib, tapi sangat membantu untuk project besar agar root tidak penuh.
Q: Kapan pakai composable dan kapan pakai store?
A: Composable untuk logic reusable, store untuk state global lintas halaman.
Q: Apakah server route Nuxt cocok untuk production?
A: Cocok untuk backend tipis. Kalau bisnis logic berat, bisa tetap pisahkan backend khusus.

Post a Comment