Menyusun kerangka halaman Livewire: layout dengan komponen Blade <x-layout>, dynamic page titles via #[Title], serta memecah UI menjadi komponen modular seperti header, sidebar, dan content untuk aplikasi yang rapi dan mudah dipelihara.

Sejauh ini kita fokus pada komponen kecil yang mandiri. Di aplikasi nyata, komponen-komponen itu tinggal di dalam layout — kerangka halaman yang berisi header, sidebar, dan area konten. Sebuah aplikasi admin panel yang baik tidak dibangun sebagai satu komponen raksasa, melainkan sebagai modul yang masing-masing punya tanggung jawab jelas.
Di episode ini kita membahas cara menyusun kerangka tersebut: layout full-page untuk komponen Livewire, pengaturan judul halaman yang dinamis, dan pola modularisasi UI menjadi komponen-komponen kecil. Ini adalah keputusan arsitektur yang menentukan seberapa mudah aplikasi kalian tumbuh.
Saat komponen dirender sebagai full-page (melalui Route::get('/x', Component::class)), Livewire perlu tahu layout mana yang dipakai. Ada dua cara:
Deklarasikan layout langsung di komponen:
<?php
namespace App\Livewire;
use Livewire\Attributes\Layout;
use Livewire\Component;
#[Layout('components.layouts.app')]
class Dashboard extends Component
{
public function render()
{
return view('livewire.dashboard');
}
}Jika layout perlu berbeda per route, atur lewat method layout:
use App\Livewire\Dashboard;
Route::get('/dashboard', Dashboard::class)->layout('components.layouts.app');Aturan default Livewire: jika tidak ada #[Layout], ia mencari components.layouts.app. Untuk satu aplikasi, biasanya cukup satu layout utama — pilih cara eksplisit (#[Layout]) agar jelas.
Layout adalah komponen Blade biasa yang memakai slot untuk area konten:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title ?? config('app.name') }}</title>
@vite(['resources/css/app.css'])
@livewireStyles
</head>
<body>
<x-layout.header />
<div class="flex">
<x-layout.sidebar />
<main class="flex-1 p-6">
{{ $slot }}
</main>
</div>
<x-layout.footer />
@livewireScripts
</body>
</html>Slot {{ $slot }} adalah tempat konten halaman disuntikkan. Bagian lain — header, sidebar, footer — dipecah menjadi komponen Blade kecil (<x-layout.header />, <x-layout.sidebar />) agar layout tetap pendek dan mudah dibaca.
Setiap halaman perlu judul yang benar — bukan hanya untuk UX, tapi untuk SEO dan bookmarking. Livewire menyediakan attribute #[Title]:
<?php
namespace App\Livewire;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Title('Dashboard')]
class Dashboard extends Component {}Untuk judul dinamis yang bergantung pada state (misalnya judul post yang sedang diedit), gunakan method title:
public function title()
{
return "Edit: {$this->post->title}";
}Livewire akan memakai hasil method title() sebagai <title> halaman, dan memperbaruinya saat state berubah — berguna saat kalian mengganti data dari dalam komponen tanpa reload.
Tip
Untuk full-page components, jangan menaruh <title> statis di layout utama jika halaman punya judul sendiri — biarkan Livewire mengisinya lewat #[Title]/method title(). Judul yang salah sering kali tidak terdeteksi sampai aplikasi online dan user menyimpan halaman tersebut.
Kunci aplikasi yang sehat adalah memecah UI menjadi komponen dengan satu tanggung jawab. Contoh nyata untuk dashboard:
<header class="bg-white shadow">
<nav>
<x-layout.nav-link href="{{ route('dashboard') }}">Dashboard</x-layout.nav-link>
<x-layout.nav-link href="{{ route('posts.index') }}">Posts</x-layout.nav-link>
</nav>
</header>Prinsip yang sama berlaku di dalam halaman. Daripada satu komponen PostDashboard yang mengerjakan segalanya, pecah menjadi komponen-komponen fungsional:
<div>
<x-badge :count="$posts->count()" label="Total post" />
<livewire:posts-table :posts="$posts" />
<livewire:posts-stats />
</div>Setiap blok punya logika dan state sendiri:
Badge — komponen presentasional, tidak punya state.PostsTable — komponen Livewire yang menangani sorting/pagination.PostsStats — komponen Livewire yang menangani event (seperti di episode 7).Hasilnya: perubahan pada satu modul tidak merambat ke modul lain, dan setiap modul bisa diuji mandiri.
components.layouts.app → resources/views/components/layouts/app.blade.php).@livewireScripts hilang dari layout: interaksi mati (sudah dibahas episode 3 — tetap jadi penyebab nomor satu).#[Title]: pilih salah satu sumber judul untuk konsistensi.Inti yang harus dibawa pulang:
#[Layout] atau layout dari route; default components.layouts.app.{{ $slot }}; pecah header/sidebar/footer menjadi komponen Blade kecil.#[Title('...')] untuk judul statis, method title() untuk judul dinamis.Di episode 9 selanjutnya kita akan membahas forms & validation — mengelola state form dengan property per field, validasi #[Validate]/validate(), validasi real-time dengan validateOnly(), custom error messages, dan multi-step forms. Ini akan menjadi inti dari hampir semua aplikasi yang kalian bangun. Sampai jumpa di episode 9!