Salah satu pertanyaan yang sering muncul saat memulai proyek web adalah: "Bangun sendiri atau pakai yang sudah ada?" Untuk CMS, pilihannya biasanya WordPress, atau custom dengan framework seperti Laravel. Artikel ini merupakan breakdown jujur dari keputusan teknis dan arsitektural dalam membangun CMS berbasis Laravel dari nol — termasuk keputusan yang benar, yang salah, dan yang masih di-iterate.

Mengapa Tidak WordPress?

Pertanyaan yang wajar dan harus dijawab jujur. WordPress adalah pilihan yang excellent untuk sebagian besar use case konten. Alasan memilih custom build biasanya karena:

  • Integrasi sistem yang kompleks — Perlu terhubung ke internal API, sistem autentikasi, dan database yang sudah ada
  • Kebutuhan khusus yang sulit di-plug-in-kan — Struktur konten yang tidak standard, workflow editorial yang unik
  • Kontrol penuh atas performa — WordPress dengan banyak plugin bisa sangat lambat; custom build bisa dioptimasi spesifik
  • Keamanan — WordPress adalah target exploit yang sangat populer; custom build punya attack surface yang lebih kecil

Tapi ini juga bukan keputusan yang gratis — custom CMS berarti Anda bertanggung jawab atas semua fitur yang "sudah jadi" di WordPress: editor, media management, SEO, multi-user, caching, dll.

Keputusan Arsitektur #1: Post Type System

Alih-alih membuat model terpisah untuk Article, Tutorial, Review, dan Page, kami memilih satu model Post dengan post_type enum. Ini mengurangi duplikasi kode dan menyederhanakan query lintas tipe konten.

// Satu tabel posts dengan post_type
$table->enum('post_type', ['article', 'tutorial', 'review', 'page', 'product']);

// Query mudah lintas type
$latestContent = Post::whereIn('post_type', ['article', 'tutorial'])
    ->published()
    ->latest('published_at')
    ->take(10)
    ->get();

Trade-off: Beberapa kolom tidak relevan untuk semua tipe (misalnya price tidak relevan untuk artikel). Kami atasi dengan memindahkan field khusus ke model terpisah yang punya relasi polymorphic atau dengan metadata JSON.

Keputusan Arsitektur #2: Taxonomy System

Mengadopsi pola WordPress-inspired taxonomy: satu tabel taxonomies, satu tabel terms, dan pivot table post_term. Ini membuat sistem kategori dan tag sepenuhnya fleksibel dan bisa dikonfigurasi tanpa migration.

// Taxonomy (category, tag, skill, dll.) bisa ditambah via seeder/admin
taxonomies: id, name, hierarchical, post_types[]

// Term (PHP, Laravel, Tutorial, dll.)
terms: id, taxonomy_id, name, slug, parent_id, count

// Many-to-many posts dan terms
post_term: post_id, term_id

Pelajaran: Fleksibilitas ini terbayar. Kami bisa tambah taxonomy baru (misalnya "skill" untuk tutorial) tanpa migration, hanya dengan seeder.

Keputusan Arsitektur #3: Media Library via Spatie

Alih-alih membangun media management sendiri, kami menggunakan spatie/laravel-medialibrary. Ini memberikan: image conversions otomatis (thumbnail, webp), responsive images, cloud storage support, dan association ke model mana saja.

Yang tidak terduga: Image conversion yang berjalan synchronous di development bisa sangat lambat untuk upload batch. Pindah ke queue-based conversion dengan mudah karena Spatie sudah mendukungnya.

Keputusan Arsitektur #4: SEO Built-in, Bukan Plugin

SEO metadata (title, description, OG tags, JSON-LD) dibangun langsung sebagai fitur core, bukan plugin terpisah. Setiap model yang bisa dipublikasi punya relasi ke tabel seo_meta:

// Polymorphic SEO meta
Schema::create('seo_metas', function (Blueprint $table) {
    $table->id();
    $table->morphs('seoable');      // post_id + post_type
    $table->string('title')->nullable();
    $table->text('description')->nullable();
    $table->json('keywords')->nullable();
    $table->string('og_image')->nullable();
    $table->json('schema_markup')->nullable();
    $table->timestamps();
});

Keputusan Arsitektur #5: Caching Strategy

Arsitektur cache berlapis:

  1. Query cache (Redis) — Untuk query database yang mahal: trending posts, popular categories, site stats
  2. Page cache (partial) — Fragment caching untuk komponen yang sama muncul di banyak halaman
  3. HTTP cache — Cache-Control headers untuk halaman yang tidak memerlukan autentikasi

Cache invalidation via Model Observer: setiap kali Post di-save, semua cache yang relevan di-invalidate otomatis.

Tantangan yang Tidak Diantisipasi

  • Rich Text Editor — TinyMCE, Quill, Tiptap — setiap pilihan ada trade-off. Akhirnya memilih Tiptap karena outputnya HTML bersih dan extensible.
  • Search — Full-text search MySQL cukup untuk tahap awal, tapi untuk konten besar harus migrasi ke Meilisearch/Typesense.
  • Sitemap Generation — Sitemap dinamis untuk ribuan post harus di-generate async via queue dan di-cache, bukan generated on-request.
  • Image Optimization — Browser modern butuh WebP/AVIF; otomatisasi via Spatie media library conversions menyelesaikannya.

Metric Performa Setelah Optimasi

  • Time to First Byte (TTFB): <150ms untuk halaman yang di-cache
  • Largest Contentful Paint (LCP): <2.0s rata-rata
  • Database queries per halaman: 3-8 (dengan eager loading dan caching)
  • Cache hit rate: ~87% untuk public pages

Lesson Learned

Membangun CMS sendiri bukan keputusan yang ringan. Tapi jika dilakukan dengan arsitektur yang solid dan memanfaatkan ekosistem Laravel yang kaya (Spatie, Sanctum, Queue, Observer), hasilnya adalah sistem yang persis sesuai kebutuhan, performanya bisa dikontrol, dan mudah di-evolve seiring kebutuhan berubah.

Rekomendasi akhir: jika use case Anda standard, gunakan WordPress atau headless CMS. Jika ada kebutuhan custom yang signifikan dan tim Anda capable, custom build dengan Laravel adalah investasi yang worthwhile dalam jangka panjang.