Episode ini membahas komponen reusable dengan Single File Component, layouts untuk kerangka halaman yang dipakai bersama, nested layouts, slots untuk komposisi komponen yang fleksibel, serta styling dengan CSS Modules, Tailwind, dan component-scoped styles.

Halaman yang bagus dibangun dari potongan-potongan yang kecil dan bisa dipakai ulang. Episode 5 fokus pada dua alat utama untuk itu: komponen untuk elemen UI yang dipakai ulang, dan layouts untuk kerangka halaman yang dibagi bersama.
Masalah yang kita selesaikan: header dan footer seharusnya tidak ditulis ulang di setiap halaman, kartu produk seharusnya satu komponen yang dipakai di banyak tempat, dan gaya harus terisolasi agar tidak saling menimpa. Setelah episode ini, kalian akan memahami pola komposisi Vue yang menjadi fondasi aplikasi skala besar.
Folder app/components khusus untuk komponen reusable, dan semuanya ter-auto-import. Mari buat komponen kartu produk:
<script setup lang="ts">
defineProps<{
nama: string
harga: number
}>()
</script>
<template>
<article class="kartu">
<h2>{{ nama }}</h2>
<p>Rp {{ harga }}</p>
</article>
</template>defineProps<{ nama: string }>() mendeklarasikan props yang diterima komponen dengan type checking penuh. Komponen ini langsung bisa dipakai di halaman mana pun tanpa import.
Gunakan nama komponen dengan dua kata atau lebih, misalnya KartuProduk, bukan Kartu. Nama satu kata berisiko bentrok dengan elemen HTML asli. Nuxt juga mengenali struktur folder bertingkat — components/kartu/Header.vue bisa dipakai sebagai <KartuHeader />.
Layout adalah kerangka yang membungkus konten halaman. Layout default menampung header, <NuxtPage />, dan footer:
<template>
<div>
<header>Header Toko</header>
<main>
<NuxtPage />
</main>
<footer>Footer Toko</footer>
</div>
</template><NuxtPage /> merender halaman aktif di dalam layout. Semua halaman yang tidak menentukan layout lain otomatis memakai default.
Buat layout kedua misalnya app/layouts/clean.vue, lalu daftarkan di halaman tertentu:
<script setup lang="ts">
definePageMeta({ layout: "clean" })
</script>definePageMeta({ layout: "clean" }) membuat halaman tersebut dirender memakai layout clean. Untuk nested layouts — layout di dalam layout — cukup bungkus <NuxtPage /> dengan layout lain secara berlapis.
Slot memungkinkan komponen menerima konten dari luar. Komponen KotakInfo bisa dipakai dengan isi yang berbeda-beda:
<template>
<div class="kotak-info">
<slot />
</div>
</template><KotakInfo>
<p>Promo gratis ongkir minggu ini.</p>
</KotakInfo><slot /> adalah titik sisip untuk konten anak komponen. Pola ini membuat komponen fleksibel tanpa perlu props yang tak terbatas.
Untuk beberapa area dalam satu komponen, gunakan named slots. Slot juga bisa mengirim data kembali ke pemakai lewat slot props. Detail lengkapnya akan kalian pakai saat membangun komponen form di episode 10.
Gaya paling sederhana dan aman: tambahkan atribut scoped sehingga CSS hanya berlaku untuk komponen tersebut:
<style scoped>
.kartu {
border: 1px solid #ddd;
padding: 1rem;
}
</style>scoped membuat Nuxt menambahkan atribut unik ke elemen komponen dan menulis selector CSS yang sesuai, jadi h2 di komponen ini tidak memengaruhi h2 di tempat lain.
Untuk gaya yang lebih eksplisit, aktifkan CSS Modules:
<style module>
.kartu {
background: #f5f5f5;
}
</style><template>
<article :class="$style.kartu">
Konten kartu
</article>
</template>Dengan module, class diakses lewat $style.kartu dan di-hash agar tidak bentrok antar komponen.
Untuk utility-first styling, install module Tailwind:
npm install --save-dev @tailwindcss/viteimport tailwindcss from "@tailwindcss/vite"
export default defineNuxtConfig({
vite: {
plugins: [tailwindcss()],
},
})Setelah itu kalian bisa menulis kelas utility langsung di template, misalnya class="border p-4". Pilihan antara scoped styles, CSS Modules, atau Tailwind sebaiknya konsisten di satu project.
Episode 5 memberi kalian perangkat komposisi UI yang lengkap: komponen reusable dengan auto-import dan props, layouts untuk kerangka halaman bersama, slots untuk fleksibilitas, serta tiga pendekatan styling yang bisa dipilih sesuai kebutuhan project.
Inti yang harus dibawa pulang:
app/components ter-auto-import dan sebaiknya dinamai minimal dua kata.default.vue dipakai semua halaman secara otomatis.definePageMeta({ layout: "nama" }) mengalihkan halaman ke layout lain.scoped di <style> mengisolasi CSS per komponen.$style dan Tailwind ditambahkan lewat module Vite.Di episode 6 selanjutnya kita akan membahas data fetching dan server actions — mengambil data dengan useAsyncData dan useFetch, menulis server actions untuk form, memahami perbedaan fetch server dan client, serta caching dan revalidasi data. Toko kalian mulai mengonsumsi data nyata.