Di Batch 9 ini, PahamITian akan belajar cara mengoptimalkan artikel Laravel untuk mesin pencari (SEO) dengan slug dan meta data, serta mengelola status publikasi (draft/published) agar konten lebih terorganisir dan mudah ditemukan.
- 1 Pengenalan Laravel: Memulai Petualangan Web dengan PHP Framework (Batch 1)
- 2 Routing dan Blade: Mengatur Alur Aplikasi dan Tampilan Cantik di Laravel (Batch 2)
- 3 Belajar Laravel Batch 3: Controller dan Form, Mengatur Logika dan Interaksi Pengguna
- 4 Belajar Laravel Batch 4: Database, Migration, dan Model - Mengelola Data Aplikasi
- 5 Belajar Laravel Batch 5: CRUD Lengkap - Menguasai Operasi Data Aplikasi
- 7 Belajar Laravel Batch 7: Authentication Dasar - Mengamankan Aplikasi dengan Login dan Register
- 8 Belajar Laravel Batch 6: Relasi Database Dasar - Menghubungkan Data Aplikasi
- 8 Belajar Laravel Batch 8: Upload File dan Storage - Mengelola Media Aplikasi
- 9 Belajar Laravel Batch 9: SEO dan Publish Status - Membuat Artikel Lebih Optimal dan Teratur
- 10 Belajar Laravel Batch 10: Mini Project Akhir - Membangun Blog Sederhana
- 11 Belajar Laravel Batch 11: Membangun RESTful API dengan Laravel
Halo, PahamITian! Selamat datang di Batch 9 dari seri tutorial Laravel dasar kita. Setelah di batch sebelumnya kita berhasil membuat fitur upload file, kini saatnya kita membuat artikel kita lebih "pintar" dan "terlihat" oleh dunia luar, terutama mesin pencari seperti Google. Kita akan membahas tentang SEO dasar dan bagaimana mengelola status publikasi artikel.
Mengapa SEO dan Publish Status Penting?
Bayangkan kamu punya toko kue. Kue-kue kamu enak, tapi kalau tokonya tersembunyi di gang sempit dan tidak ada papan nama, siapa yang tahu? Begitu juga dengan artikel di website kita. Sebagus apapun kontennya, kalau tidak dioptimalkan untuk mesin pencari (SEO) dan tidak diatur status publikasinya, maka akan sulit ditemukan oleh pembaca. Di batch ini, kita akan belajar bagaimana "memasang papan nama" dan "mengatur jam buka" toko kue kita.
1. Slug: URL yang Cantik dan SEO-Friendly
Apa itu Slug?
Slug adalah bagian dari URL yang mengidentifikasi sebuah halaman web dengan cara yang mudah dibaca oleh manusia dan mesin pencari. Contohnya, daripada URL https://pahamit.com/artikel/123, akan lebih baik jika menjadi https://pahamit.com/artikel/belajar-laravel-batch-9-seo-publish-status. Slug biasanya dibuat dari judul artikel, dengan spasi diganti tanda hubung (-) dan karakter khusus dihilangkan.
Mengapa Penting?
- SEO: Mesin pencari lebih suka URL yang deskriptif dan mengandung kata kunci. Ini membantu mereka memahami isi halaman.
- User Experience: Pengguna lebih mudah mengingat dan memahami isi halaman dari URL yang jelas.
Implementasi di Laravel
Kita perlu menambahkan kolom slug di tabel posts kita. Pastikan slug ini unik.
Langkah 1: Menambahkan Kolom slug ke Migration
Buat migration baru atau tambahkan ke migration create_posts_table jika belum ada data.
php artisan make:migration add_slug_to_posts_table --table=posts
Edit file migration yang baru dibuat:
// database/migrations/xxxx_xx_xx_xxxxxx_add_slug_to_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->string('slug')->unique()->after('title');
});
}
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('slug');
});
}
};
Jalankan migration:
php artisan migrate
Langkah 2: Membuat Slug Otomatis Saat Menyimpan Artikel
Kita bisa menggunakan Str::slug() dari Laravel untuk membuat slug. Ini bisa dilakukan di Controller saat menyimpan atau mengupdate, atau lebih elegan lagi di Model menggunakan mutator atau event.
Untuk permulaan, kita lakukan di Controller PostController saat store dan update.
// app/Http/Controllers/PostController.php
use Illuminate\Support\Str; // Tambahkan ini di bagian atas
// ... dalam method store()
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
// ... validasi lainnya
]);
$post = new Post();
$post->title = $validated['title'];
$post->slug = Str::slug($validated['title']); // Membuat slug dari judul
$post->content = $validated['content'];
// ... simpan data lainnya
$post->save();
return redirect()->route('posts.index')->with('success', 'Artikel berhasil ditambahkan!');
}
// ... dalam method update()
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
// ... validasi lainnya
]);
$post->title = $validated['title'];
$post->slug = Str::slug($validated['title']); // Update slug juga
$post->content = $validated['content'];
// ... update data lainnya
$post->save();
return redirect()->route('posts.index')->with('success', 'Artikel berhasil diupdate!');
}
Catatan Penting: Untuk memastikan slug unik, kita bisa menambahkan logika pengecekan di controller atau menggunakan paket pihak ketiga seperti spatie/laravel-sluggable yang lebih canggih.
2. Status Draft/Published: Mengontrol Visibilitas Artikel
Apa itu Status Publikasi?
Status publikasi memungkinkan kita untuk menentukan apakah sebuah artikel sudah siap tayang (Published) atau masih dalam tahap pengerjaan (Draft). Ini sangat penting untuk alur kerja editorial.
Implementasi di Laravel
Kita akan menambahkan kolom is_published bertipe boolean ke tabel posts.
Langkah 1: Menambahkan Kolom is_published ke Migration
php artisan make:migration add_is_published_to_posts_table --table=posts
Edit file migration yang baru dibuat:
// database/migrations/xxxx_xx_xx_xxxxxx_add_is_published_to_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->boolean('is_published')->default(false)->after('slug');
});
}
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('is_published');
});
}
};
Jalankan migration:
php artisan migrate
Langkah 2: Menambahkan Input di Form dan Menyimpan Status
Di form create.blade.php dan edit.blade.php, tambahkan checkbox untuk is_published.
<!-- resources/views/posts/create.blade.php atau edit.blade.php -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="is_published" name="is_published" value="1" {{ old('is_published', $post->is_published ?? false) ? 'checked' : '' }}>
<label class="form-check-label" for="is_published">Publikasikan Artikel?</label>
</div>
Di PostController, pastikan is_published divalidasi dan disimpan.
// app/Http/Controllers/PostController.php
// ... dalam method store()
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'is_published' => 'boolean', // Tambahkan validasi ini
// ... validasi lainnya
]);
$post = new Post();
$post->title = $validated['title'];
$post->slug = Str::slug($validated['title']);
$post->content = $validated['content'];
$post->is_published = $request->has('is_published'); // Cek apakah checkbox dicentang
// ... simpan data lainnya
$post->save();
return redirect()->route('posts.index')->with('success', 'Artikel berhasil ditambahkan!');
}
// ... dalam method update()
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'is_published' => 'boolean', // Tambahkan validasi ini
// ... validasi lainnya
]);
$post->title = $validated['title'];
$post->slug = Str::slug($validated['title']);
$post->content = $validated['content'];
$post->is_published = $request->has('is_published'); // Cek apakah checkbox dicentang
// ... update data lainnya
$post->save();
return redirect()->route('posts.index')->with('success', 'Artikel berhasil diupdate!');
}
3. Meta Title dan Description: Wajah Artikel di Hasil Pencarian
Apa itu Meta Title dan Description?
- Meta Title: Judul yang muncul di tab browser dan di hasil pencarian Google. Ini adalah salah satu faktor SEO terpenting.
- Meta Description: Ringkasan singkat artikel yang muncul di bawah meta title di hasil pencarian. Ini tidak secara langsung mempengaruhi ranking, tapi sangat mempengaruhi click-through rate (CTR).
Implementasi di Laravel
Kita akan menambahkan kolom meta_title dan meta_description ke tabel posts.
Langkah 1: Menambahkan Kolom Meta ke Migration
php artisan make:migration add_meta_seo_to_posts_table --table=posts
Edit file migration yang baru dibuat:
// database/migrations/xxxx_xx_xx_xxxxxx_add_meta_seo_to_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->string('meta_title')->nullable()->after('is_published');
$table->text('meta_description')->nullable()->after('meta_title');
});
}
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn(['meta_title', 'meta_description']);
});
}
};
Jalankan migration:
php artisan migrate
Langkah 2: Menambahkan Input di Form dan Menyimpan Data
Di form create.blade.php dan edit.blade.php, tambahkan input text untuk meta title dan textarea untuk meta description.
<!-- resources/views/posts/create.blade.php atau edit.blade.php -->
<div class="mb-3">
<label for="meta_title" class="form-label">Meta Title (untuk SEO)</label>
<input type="text" class="form-control" id="meta_title" name="meta_title" value="{{ old('meta_title', $post->meta_title ?? '') }}" maxlength="60">
<div class="form-text">Maksimal 60 karakter. Akan muncul di hasil pencarian.</div>
</div>
<div class="mb-3">
<label for="meta_description" class="form-label">Meta Description (untuk SEO)</label>
<textarea class="form-control" id="meta_description" name="meta_description" rows="3">{{ old('meta_description', $post->meta_description ?? '') }}</textarea>
<div class="form-text">Maksimal 160 karakter. Ringkasan di hasil pencarian.</div>
</div>
Di PostController, pastikan meta_title dan meta_description divalidasi dan disimpan.
// app/Http/Controllers/PostController.php
// ... dalam method store()
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'is_published' => 'boolean',
'meta_title' => 'nullable|string|max:60', // Tambahkan validasi ini
'meta_description' => 'nullable|string|max:160', // Tambahkan validasi ini
// ... validasi lainnya
]);
$post = new Post();
$post->title = $validated['title'];
$post->slug = Str::slug($validated['title']);
$post->content = $validated['content'];
$post->is_published = $request->has('is_published');
$post->meta_title = $validated['meta_title'];
$post->meta_description = $validated['meta_description'];
// ... simpan data lainnya
$post->save();
return redirect()->route('posts.index')->with('success', 'Artikel berhasil ditambahkan!');
}
// ... dalam method update()
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'is_published' => 'boolean',
'meta_title' => 'nullable|string|max:60',
'meta_description' => 'nullable|string|max:160',
// ... validasi lainnya
]);
$post->title = $validated['title'];
$post->slug = Str::slug($validated['title']);
$post->content = $validated['content'];
$post->is_published = $request->has('is_published');
$post->meta_title = $validated['meta_title'];
$post->meta_description = $validated['meta_description'];
// ... update data lainnya
$post->save();
return redirect()->route('posts.index')->with('success', 'Artikel berhasil diupdate!');
}
Langkah 3: Menampilkan Meta Data di View
Di layout utama atau di view show.blade.php artikel, tambahkan tag <title> dan <meta name="description"> di bagian <head>.
<!-- resources/views/layouts/app.blade.php atau resources/views/posts/show.blade.php -->
<head>
<!-- ... tag lainnya ... -->
<title>{{ $post->meta_title ?? $post->title }} | Pahamit.com</title>
<meta name="description" content="{{ $post->meta_description ?? Str::limit(strip_tags($post->content), 150) }}">
<!-- ... tag lainnya ... -->
</head>
4. Published At: Kapan Artikel Dipublikasikan?
Apa itu published_at?
published_at adalah kolom timestamp yang menunjukkan kapan sebuah artikel benar-benar dipublikasikan. Ini berbeda dengan created_at yang menunjukkan kapan artikel dibuat. published_at bisa null jika artikel masih draft, dan akan terisi saat artikel dipublikasikan.
Implementasi di Laravel
Langkah 1: Menambahkan Kolom published_at ke Migration
php artisan make:migration add_published_at_to_posts_table --table=posts
Edit file migration yang baru dibuat:
// database/migrations/xxxx_xx_xx_xxxxxx_add_published_at_to_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->timestamp('published_at')->nullable()->after('meta_description');
});
}
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('published_at');
});
}
};
Jalankan migration:
php artisan migrate
Langkah 2: Mengisi published_at Saat Artikel Dipublikasikan
Di PostController, saat is_published diatur menjadi true, kita akan mengisi published_at dengan waktu saat ini. Jika is_published menjadi false (kembali ke draft), kita bisa mengosongkan published_at.
// app/Http/Controllers/PostController.php
use Carbon\Carbon; // Tambahkan ini di bagian atas
// ... dalam method store()
public function store(Request $request)
{
$validated = $request->validate([
// ... validasi lainnya
'is_published' => 'boolean',
]);
$post = new Post();
// ... simpan data lainnya
$post->is_published = $request->has('is_published');
$post->published_at = $post->is_published ? Carbon::now() : null; // Set published_at
$post->save();
return redirect()->route('posts.index')->with('success', 'Artikel berhasil ditambahkan!');
}
// ... dalam method update()
public function update(Request $request, Post $post)
{
$validated = $request->validate([
// ... validasi lainnya
'is_published' => 'boolean',
]);
// Jika status publikasi berubah dari tidak published menjadi published
if ($request->has('is_published') && !$post->is_published) {
$post->published_at = Carbon::now();
} elseif (!$request->has('is_published') && $post->is_published) {
// Jika status publikasi berubah dari published menjadi tidak published
$post->published_at = null;
}
$post->is_published = $request->has('is_published');
// ... update data lainnya
$post->save();
return redirect()->route('posts.index')->with('success', 'Artikel berhasil diupdate!');
}
5. Filter Artikel Published: Hanya Menampilkan yang Siap Tayang
Sekarang, kita punya kolom is_published dan published_at. Kita bisa menggunakannya untuk hanya menampilkan artikel yang benar-benar siap tayang di halaman publik.
Di PostController atau di mana pun kamu menampilkan daftar artikel publik (misalnya di HomeController atau PostController@index jika itu untuk publik):
// app/Http/Controllers/PostController.php (untuk daftar publik)
public function indexPublic()
{
$posts = Post::where('is_published', true)
->whereNotNull('published_at')
->where('published_at', '<=', Carbon::now())
->orderBy('published_at', 'desc')
->paginate(10);
return view('posts.public_index', compact('posts'));
}
Penjelasan filternya:
* where('is_published', true): Hanya ambil artikel yang statusnya published.
* whereNotNull('published_at'): Pastikan published_at tidak kosong (sudah pernah dipublikasikan).
* where('published_at', '<=', Carbon::now()): Pastikan tanggal publikasi sudah tiba atau sudah lewat. Ini berguna untuk artikel yang dijadwalkan publikasinya di masa depan.
Latihan: Halaman Artikel Publik Berdasarkan Slug
Mari kita buat halaman untuk menampilkan artikel secara individual, yang bisa diakses oleh publik menggunakan slug.
Langkah 1: Tambahkan Route Baru
Di routes/web.php, tambahkan route untuk artikel publik. Letakkan di luar group middleware auth jika kamu punya.
// routes/web.php
use App\Http\Controllers\PostController;
Route::get('/artikel/{slug}', [PostController::class, 'showPublic'])->name('artikel.show');
Langkah 2: Tambahkan Method showPublic di PostController
// app/Http/Controllers/PostController.php
use App\Models\Post;
use Carbon\Carbon;
// ...
public function showPublic(string $slug)
{
$post = Post::where('slug', $slug)
->where('is_published', true)
->whereNotNull('published_at')
->where('published_at', '<=', Carbon::now())
->firstOrFail(); // Jika tidak ditemukan, akan otomatis 404
return view('posts.public_show', compact('post'));
}
Langkah 3: Buat View public_show.blade.php
Buat file resources/views/posts/public_show.blade.php.
<!-- resources/views/posts/public_show.blade.php -->
@extends('layouts.app') {{-- Asumsikan Anda punya layout dasar --}}
@section('title', $post->meta_title ?? $post->title)
@section('description', $post->meta_description ?? Str::limit(strip_tags($post->content), 150))
@section('content')
<div class="container mt-5">
<article>
<h1 class="mb-3">{{ $post->title }}</h1>
<p class="text-muted">Dipublikasikan pada: {{ $post->published_at->format('d M Y H:i') }}</p>
@if ($post->featured_image)
<img src="{{ asset('storage/' . $post->featured_image) }}" class="img-fluid mb-4" alt="{{ $post->title }}">
@endif
<div class="blog-content">
{!! $post->content !!}
</div>
@if ($post->category)
<p class="mt-4">Kategori: <a href="#">{{ $post->category->name }}</a></p>
@endif
@if ($post->user)
<p>Penulis: {{ $post->user->name }}</p>
@endif
</article>
<a href="{{ url('/') }}" class="btn btn-primary mt-5">Kembali ke Beranda</a>
</div>
@endsection
Pastikan layouts.app Anda memiliki @yield('title') dan @yield('description') di bagian <head>.
<!-- resources/views/layouts/app.blade.php -->
<head>
<!-- ... -->
<title>@yield('title', 'Pahamit.com')</title>
<meta name="description" content="@yield('description', 'Pahamit.com - Bukan Sekadar Belajar. Tutorial IT, berita teknologi, dan solusi digital.')">
<!-- ... -->
</head>
Ringkasan Singkat Batch 9
Di batch ini, kita telah mempelajari bagaimana membuat artikel kita lebih ramah SEO dan terorganisir dengan baik:
* Slug: URL yang bersih dan deskriptif untuk SEO dan pengalaman pengguna.
* Status Publikasi (is_published): Mengontrol apakah artikel sudah siap tayang atau masih dalam draft.
* Meta Title & Description: Judul dan deskripsi khusus untuk hasil pencarian Google.
* published_at: Timestamp kapan artikel benar-benar dipublikasikan, berguna untuk penjadwalan.
* Filter Artikel Publik: Hanya menampilkan artikel yang sudah dipublikasikan dan sudah waktunya tayang.
* Halaman Artikel Publik Berdasarkan Slug: Mengimplementasikan tampilan artikel tunggal yang bisa diakses publik melalui slugnya.
Latihan Praktik Batch 9
- Implementasikan Kolom Baru: Pastikan semua kolom
slug,is_published,meta_title,meta_description, danpublished_atsudah ada di tabelpostsAnda dan terisi dengan benar saat membuat/mengedit artikel. - Uji Pembuatan Slug: Buat beberapa artikel dengan judul yang berbeda, pastikan slugnya terbentuk dengan benar dan unik.
- Uji Status Publikasi: Buat artikel dalam status draft dan published. Pastikan hanya yang published yang muncul di halaman publik.
- Uji Meta SEO: Isi meta title dan description. Cek di browser (View Page Source) apakah tag
<title>dan<meta name="description">sudah terisi sesuai data artikel. - Uji Halaman Publik: Akses artikel yang sudah dipublikasikan menggunakan URL dengan slug-nya (misal:
http://localhost:8000/artikel/judul-artikel-anda). Pastikan hanya artikel yangis_published = truedanpublished_atsudah lewat yang bisa diakses.
Pertanyaan Cek Pemahaman Batch 9
- Apa perbedaan utama antara
created_atdanpublished_atpada sebuah artikel? - Mengapa slug penting untuk SEO dan bagaimana cara membuatnya di Laravel?
- Apa fungsi
meta_titledanmeta_description? Di mana keduanya akan terlihat oleh pengguna? - Bagaimana cara memfilter artikel agar hanya menampilkan yang sudah dipublikasikan dan sudah waktunya tayang?
- Jika sebuah artikel memiliki
is_published = truetetapipublished_atdiatur ke tanggal di masa depan, apakah artikel tersebut akan muncul di halaman publik dengan filter yang kita buat?
Kesalahan Umum Pemula Batch 9
- Slug Tidak Unik: Lupa menambahkan validasi atau logika untuk memastikan slug unik, sehingga bisa terjadi error database atau artikel tidak bisa diakses dengan benar.
- Lupa
Str::slug(): Langsung menyimpan judul ke kolom slug tanpa mengonversinya menjadi format slug yang benar. - Masalah
published_at: Tidak mengaturpublished_atmenjadinullsaat artikel kembali ke draft, atau tidak mengisiCarbon::now()saat dipublikasikan. - Filter Kurang Lengkap: Hanya memfilter
is_published = truetanpa mempertimbangkanpublished_atataupublished_atyang masih di masa depan. - Meta Data Tidak Tampil: Lupa menambahkan
@yield('title')dan@yield('description')di layout utama atau di view artikel.
Persiapan untuk Batch Berikutnya
Selamat, PahamITian! Kamu sudah semakin mahir dalam mengelola artikel. Di Batch 10, kita akan menggabungkan semua yang sudah kita pelajari untuk membangun Mini Project Akhir: Blog Laravel Sederhana. Kita akan membuat sebuah blog yang fungsional dengan fitur login admin, CRUD artikel, kategori, upload gambar, status publish, dan halaman publik. Ini akan menjadi puncak dari perjalanan belajar dasar Laravel kita! Siapkan semangatmu!