<?xml version="1.0"?>
<rss version="2.0"><channel><title>Articles</title><link>https://lalu.pro/articles/</link><description>Baca artikel-artikel menarik kami</description><language>en</language><item><title>Perbedaan SEO dan SEM: Mana yang Cocok untuk Bisnis Anda?</title><link>https://lalu.pro/articles/seo/perbedaan-seo-dan-sem-mana-yang-cocok-untuk-bisnis-anda-r8/</link><description><![CDATA[
<p><img src="https://lalu.pro/uploads/monthly_2026_07/Perbedaan_SEO_dan_SEM_mana_yang_cocok_untuk_bisnis.avif.55dab0b52aebe611459d85787b9a6819.avif" /></p>
<p>Coba buka Google sekarang dan ketik "baju bayi perempuan" atau "vpn indonesia". Perhatikan hasilnya baik-baik: beberapa posisi teratas biasanya memuat label kecil bertuliskan <strong>"Produk Bersponsor" </strong>atau<strong> "Hasil Bersponsor" </strong>intinya<strong> "bersponsor"</strong>. Di bawahnya, barulah deretan hasil "biasa" tanpa label.</p><p>Lihat contoh berikut:</p><p><img class="ipsImage ipsImage_thumbnailed ipsRichText__align--block" data-fileid="10" src="https://lalu.pro/uploads/monthly_2026_07/Contoh_Perbedaan_SEO_dan_SEM_baju_bayi_permpuan.thumb.avif.5315bd1a1cd2b3fb6cc1bac8eda22650.avif" alt="Contoh perbedaan SEO dan SEM" title="Contoh perbedaan SEO dan SEM" width="624" height="750" data-full-image="https://lalu.pro/uploads/monthly_2026_07/Contoh_Perbedaan_SEO_dan_SEM_baju_bayi_permpuan.avif.3e90cdbf85e62fdd3cfb8089b9265ab7.avif" loading="lazy"></p><p>Dua wilayah itu diperebutkan dengan cara yang sama sekali berbeda. Wilayah berlabel diperebutkan dengan uang — siapa menawar dan mengelola iklannya paling baik, dia tampil. Wilayah tanpa label diperebutkan dengan kualitas — siapa paling layak menjawab pencarian itu di mata Google, dia menang. Yang pertama adalah ranah <strong>SEM</strong>, yang kedua ranah <strong>SEO</strong>. Kalau Anda masih meraba-raba <a rel="" href="https://lalu.pro/articles/seo/apa-itu-seo-panduan-lengkap-untuk-pemula-r7/">apa itu SEO dan cara kerjanya secara umum</a>, baca dulu panduan pilar kami sebelum lanjut — artikel ini mengasumsikan Anda sudah memegang konsep dasarnya.</p><h2>Pengertian Singkat: SEO dan SEM Itu Apa?</h2><p><strong>SEO (Search Engine Optimization)</strong> adalah upaya mengoptimalkan website agar muncul tinggi di hasil pencarian <em>organik</em> — hasil yang tidak berlabel iklan. Anda tidak membayar Google per klik; Anda "membayar" dengan kerja: membuat konten yang layak, merapikan teknis situs, dan membangun reputasi.</p><p><strong>SEM (Search Engine Marketing)</strong> dalam praktik sehari-hari di Indonesia merujuk pada iklan pencarian berbayar, terutama lewat Google Ads. Anda menentukan kata kunci, menulis iklan, menetapkan anggaran, dan membayar setiap kali seseorang mengeklik iklan Anda (model <em>pay-per-click</em> atau PPC).</p><p>Catatan kecil yang sering membingungkan pemula: secara akademis, SEM sebenarnya payung besar yang <em>mencakup</em> SEO dan iklan berbayar sekaligus. Tetapi dalam percakapan industri sehari-hari — termasuk di artikel ini — "SEM" hampir selalu berarti sisi berbayarnya. Ikuti saja konvensi itu agar tidak salah paham saat berdiskusi dengan agensi atau membaca artikel luar.</p><h2>Perbedaan SEO dan SEM dalam 6 Aspek</h2><h3>1. Biaya: bayar per klik vs bayar dengan kerja</h3><p>SEM memungut biaya setiap klik. Untuk kata kunci komersial yang diperebutkan (asuransi, pinjaman, properti), biaya per klik bisa mencapai puluhan ribu rupiah — dan begitu anggaran habis hari itu, iklan Anda hilang dari peredaran detik itu juga.</p><p>SEO tidak memungut biaya per klik, tetapi bukan berarti gratis. Anda berinvestasi di depan: waktu menulis konten, biaya penulis atau jasa profesional, dan tools. Bedanya, hasil investasi itu tidak hangus saat "anggaran habis".</p><h3>2. Kecepatan: hari vs bulan</h3><p>Iklan yang disetujui bisa tampil di posisi atas <em>hari ini juga</em>. SEO umumnya butuh 3–6 bulan untuk pergerakan berarti — Google perlu waktu merayapi, memahami, dan menguji konten Anda, sebuah proses yang kami jelaskan runtut di <a rel="" href="/">cara kerja SEO dalam 5 langkah sederhana</a>.</p><h3>3. Daya tahan: menyewa vs membangun</h3><p>Ini perbedaan paling fundamental. <strong>Iklan itu menyewa, SEO itu membangun.</strong> Posisi iklan lenyap begitu Anda berhenti membayar. Posisi organik yang sudah diraih terus mendatangkan pengunjung setiap hari — bertahun-tahun untuk artikel yang bagus — tanpa biaya tambahan per klik, cukup dengan perawatan berkala.</p><h3>4. Kepercayaan pengguna</h3><p>Sebagian pengguna secara sadar melewati hasil beriklan dan langsung menggulir ke hasil organik, karena menganggapnya lebih "netral" — Google merekomendasikan, bukan dibayar untuk menampilkan. Ini tidak berlaku untuk semua orang, tetapi cukup nyata untuk diperhitungkan, terutama pada pencarian riset dan perbandingan.</p><h3>5. Cakupan kata kunci</h3><p>Iklan efektif untuk kata kunci bernilai transaksi langsung ("beli", "harga", "jasa", "terdekat"). Menayangkan iklan untuk ribuan pencarian informasional ("apa itu…", "cara…", "kenapa…") biasanya boros. SEO justru unggul di wilayah informasional ini: satu artikel bisa menjaring puluhan variasi pencarian sekaligus dan mengantar calon pelanggan sejak fase mereka baru bertanya-tanya.</p><h3>6. Data dan pembelajaran</h3><p>SEM memberi data cepat: dalam seminggu Anda tahu kata kunci mana yang menghasilkan penjualan. Banyak praktisi cerdik memakai data iklan ini sebagai "riset berbayar" — kata kunci yang terbukti menghasilkan konversi di iklan lalu dijadikan target konten SEO jangka panjang.</p><h2>Tabel Ringkas SEO vs SEM</h2><div class="ipsRichText__table-wrapper"><table style="width: 631px;"><colgroup><col style="width:199px;"><col style="width:203px;"><col style="width:229px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Aspek</p></th><th colspan="1" rowspan="1"><p>SEO</p></th><th colspan="1" rowspan="1"><p>SEM (Iklan)</p></th></tr><tr><td colspan="1" rowspan="1"><p>Biaya per klik</p></td><td colspan="1" rowspan="1"><p>Tidak ada</p></td><td colspan="1" rowspan="1"><p>Ada, dibayar tiap klik</p></td></tr><tr><td colspan="1" rowspan="1"><p>Kecepatan hasil</p></td><td colspan="1" rowspan="1"><p>3–6 bulan+</p></td><td colspan="1" rowspan="1"><p>Hitungan jam</p></td></tr><tr><td colspan="1" rowspan="1"><p>Daya tahan</p></td><td colspan="1" rowspan="1"><p>Jangka panjang</p></td><td colspan="1" rowspan="1"><p>Berhenti saat anggaran habis</p></td></tr><tr><td colspan="1" rowspan="1"><p>Posisi di SERP</p></td><td colspan="1" rowspan="1"><p>Hasil organik</p></td><td colspan="1" rowspan="1"><p>Slot berlabel "Bersponsor"</p></td></tr><tr><td colspan="1" rowspan="1"><p>Cocok untuk</p></td><td colspan="1" rowspan="1"><p>Membangun aset &amp; otoritas</p></td><td colspan="1" rowspan="1"><p>Kebutuhan cepat &amp; validasi</p></td></tr><tr><td colspan="1" rowspan="1"><p>Risiko utama</p></td><td colspan="1" rowspan="1"><p>Lambat, butuh konsistensi</p></td><td colspan="1" rowspan="1"><p>Biaya membengkak tanpa hasil</p></td></tr></tbody></table></div><h2>Jadi, Mana yang Cocok untuk Bisnis Anda?</h2><p>Jawaban jujurnya: tergantung tahap dan kebutuhan bisnis Anda. Gunakan tiga skenario ini sebagai kompas.</p><p><strong>Pilih SEM (dulu) jika:</strong> Anda butuh penjualan bulan ini, sedang meluncurkan produk baru, menjalankan promo musiman, atau ingin memvalidasi cepat apakah pasar benar-benar mencari produk Anda. Iklan adalah keran yang bisa dibuka-tutup sesuka Anda — kekuatan yang tidak dimiliki SEO.</p><p><strong>Pilih SEO (dulu) jika:</strong> anggaran pemasaran Anda terbatas tetapi waktu dan tenaga tersedia, produk Anda dicari lewat banyak pertanyaan informasional, atau margin Anda terlalu tipis untuk menanggung biaya per klik dalam jangka panjang. Ini situasi umum banyak UMKM — dan alasan kami menulis panduan khusus tentang <a rel="" href="/">manfaat SEO untuk bisnis kecil dan UMKM</a>.</p><p><strong>Kombinasikan keduanya jika:</strong> anggaran memungkinkan. Ini strategi yang paling sering menang: iklan menutup kebutuhan jangka pendek sambil SEO membangun fondasi jangka panjang. Seiring posisi organik menguat, porsi anggaran iklan bisa digeser ke kata kunci yang belum terjangkau organik. Iklan dan SEO bukan pesaing — mereka pemain di babak yang berbeda.</p><p>Kesalahan yang paling mahal justru bukan salah pilih, melainkan <strong>menilai keduanya dengan ukuran yang salah</strong>: menuntut SEO memberi hasil secepat iklan (lalu menyerah di bulan kedua), atau menuntut iklan membangun aset jangka panjang (lalu kaget traffic nol begitu iklan dimatikan).</p><p>Apa pun pilihan Anda, keduanya bermuara ke tempat yang sama: halaman hasil pencarian Google. Memahami <a rel="" href="/bagaimana-mesin-pencari-bekerja-crawling-indexing-dan-ranking/">bagaimana mesin pencari bekerja — dari crawling, indexing, sampai ranking</a> — akan membuat keputusan SEO maupun SEM Anda jauh lebih tajam.</p><h2>FAQ Seputar Perbedaan SEO dan SEM</h2><p><strong>Apa perbedaan utama SEO dan SEM?</strong> SEO mengejar posisi di hasil pencarian organik tanpa membayar per klik — hasilnya lambat tetapi tahan lama. SEM (iklan pencarian seperti Google Ads) membayar per klik untuk tampil di slot bersponsor — hasilnya instan tetapi berhenti begitu anggaran habis.</p><p><strong>Apakah SEM sama dengan Google Ads?</strong> Dalam percakapan sehari-hari di Indonesia, ya — SEM hampir selalu merujuk pada iklan pencarian berbayar, dan Google Ads adalah platform yang paling dominan. Secara definisi luas, SEM sebenarnya mencakup SEO juga, tetapi konvensi industri memakainya untuk sisi berbayar.</p><p><strong>Mana yang lebih murah, SEO atau SEM?</strong> Dalam jangka pendek SEM sering terasa lebih "murah" karena hasilnya cepat terlihat. Dalam jangka panjang, biaya per pengunjung dari SEO umumnya jauh lebih rendah karena artikel yang sudah menempati posisi baik terus mendatangkan traffic tanpa biaya per klik.</p><p><strong>Apakah memasang iklan Google membantu peringkat organik?</strong> Tidak secara langsung. Google berulang kali menegaskan bahwa iklan tidak memengaruhi peringkat organik. Manfaatnya tidak langsung: data konversi dari iklan bisa memandu strategi konten SEO Anda.</p><p><strong>Bisakah SEO dan SEM dijalankan bersamaan?</strong> Bisa, dan sering kali ini strategi terbaik: iklan untuk kebutuhan cepat dan kata kunci transaksional, SEO untuk membangun aset traffic jangka panjang. Banyak bisnis menggeser porsi anggaran iklan secara bertahap seiring posisi organiknya menguat.</p><hr><h2>Baca Juga</h2><ul><li><p><a rel="" href="https://lalu.pro/articles/seo/apa-itu-seo-panduan-lengkap-untuk-pemula-r7/">Apa Itu SEO? Panduan Lengkap untuk Pemula</a></p></li><li><p><a rel="" href="/cara-kerja-seo-dijelaskan-dalam-5-langkah-sederhana/">Cara Kerja SEO Dijelaskan dalam 5 Langkah Sederhana</a></p></li><li><p><a rel="" href="/bagaimana-mesin-pencari-bekerja-crawling-indexing-dan-ranking/">Bagaimana Mesin Pencari Bekerja: Crawling, Indexing, dan Ranking</a></p></li></ul>]]></description><guid isPermaLink="false">8</guid><pubDate>Tue, 07 Jul 2026 23:45:17 +0000</pubDate></item><item><title>Apa Itu SEO? Panduan Lengkap untuk Pemula</title><link>https://lalu.pro/articles/seo/apa-itu-seo-panduan-lengkap-untuk-pemula-r7/</link><description><![CDATA[
<p><img src="https://lalu.pro/uploads/monthly_2026_07/apa_itu_SEO_panduan_lengkap_untuk_pemula.avif.40577969770bdf93e9143093eff11b03.avif" /></p>
<p>Bayangkan Anda membuka toko di gang sempit tanpa papan nama. Produk Anda bagus, harga bersaing, pelayanan ramah — tetapi tidak ada yang tahu toko itu ada. Di internet, situasi ini terjadi setiap hari pada jutaan website. Kontennya layak, produknya nyata, tetapi tidak pernah muncul ketika calon pelanggan mencarinya di Google.</p><p>SEO adalah cara memindahkan toko Anda dari gang sempit itu ke jalan utama.</p><p>Panduan ini ditulis untuk Anda yang benar-benar mulai dari nol: pemilik UMKM, blogger pemula, pemasar yang baru diberi tanggung jawab "urus website", atau siapa pun yang penasaran kenapa sebagian situs selalu nangkring di halaman satu Google. Kita akan membahas pengertian SEO, cara kerjanya, tiga pilar utamanya, bedanya dengan iklan berbayar, sampai langkah konkret pertama yang bisa Anda kerjakan minggu ini.</p><h2>Pengertian SEO</h2><p>SEO adalah singkatan dari <em>Search Engine Optimization</em>, atau optimasi mesin pencari. Sederhananya, SEO adalah serangkaian upaya untuk membuat halaman website Anda lebih mudah ditemukan, dipahami, dan dipercaya oleh mesin pencari seperti Google — sehingga muncul di posisi tinggi pada hasil pencarian yang relevan, tanpa membayar per klik.</p><p>Ada tiga kata kunci dalam definisi itu yang sering terlewat.</p><p><strong>Pertama, "ditemukan".</strong> Google harus bisa menjelajahi dan menyimpan halaman Anda ke dalam basis datanya. Kalau halaman tidak terindeks, sebagus apa pun isinya, ia tidak akan pernah muncul.</p><p><strong>Kedua, "dipahami".</strong> Google perlu tahu halaman Anda membahas apa dan cocok untuk pencarian yang mana. Di sinilah struktur konten, pemilihan kata kunci, dan penulisan yang jelas berperan.</p><p><strong>Ketiga, "dipercaya".</strong> Untuk kata kunci yang diperebutkan banyak situs, Google memilih halaman yang paling kredibel — dinilai antara lain dari kualitas konten, pengalaman pengguna, dan reputasi situs di mata web secara keseluruhan.</p><p>Jadi SEO bukan sekadar "menaruh kata kunci sebanyak-banyaknya". SEO adalah pekerjaan membangun website yang layak direkomendasikan Google kepada penggunanya. Perbedaan cara pandang ini kedengarannya sepele, tetapi menentukan apakah strategi Anda bertahan bertahun-tahun atau tumbang setiap kali algoritma diperbarui.</p><h2>Cara Kerja SEO dalam Gambaran Besar</h2><p>Untuk memahami SEO, Anda perlu tahu sedikit tentang cara kerja mesin pencari. Prosesnya bisa diringkas menjadi tiga tahap.</p><ul><li><p><strong>Crawling (perayapan).</strong> Google menjalankan program bernama Googlebot yang menjelajahi web dengan mengikuti tautan dari halaman ke halaman, menemukan konten baru dan konten yang diperbarui.</p></li><li><p><strong>Indexing (pengindeksan).</strong> Halaman yang ditemukan lalu dianalisis dan disimpan dalam indeks Google — perpustakaan raksasa berisi ratusan miliar halaman. Di tahap ini Google mencoba memahami topik, bahasa, dan kualitas halaman Anda.</p></li><li><p><strong>Ranking (pemeringkatan).</strong> Ketika seseorang mengetik pencarian, Google menyaring indeksnya dalam sepersekian detik dan menyusun hasil berdasarkan ratusan sinyal: relevansi konten terhadap maksud pencarian, kualitas dan kedalaman pembahasan, pengalaman pengguna di halaman, hingga reputasi situs.</p></li></ul><p>Pekerjaan SEO pada dasarnya adalah memastikan ketiga tahap itu berjalan mulus untuk website Anda: halaman bisa dirayapi, mudah dipahami saat diindeks, dan punya alasan kuat untuk menang saat diperingkat. Penjelasan lengkap tiap tahap kami bahas di panduan [Bagaimana Mesin Pencari Bekerja: Crawling, Indexing, dan Ranking](<u>/bagaimana-mesin-pencari-bekerja-crawling-indexing-dan-ranking/</u>).</p><p>Satu konsep lagi yang wajib Anda kenal sejak awal: <strong>search intent</strong>, yaitu maksud di balik sebuah pencarian. Orang yang mengetik "sepatu lari" bisa jadi ingin belajar memilih sepatu, membandingkan merek, atau langsung membeli. Google berusaha menebak maksud itu, dan halaman yang menjawab maksud paling tepatlah yang menang — bukan sekadar halaman yang memuat kata "sepatu lari" paling banyak.</p><h2>Mengapa SEO Penting untuk Bisnis Anda</h2><p>Mesin pencari adalah salah satu pintu masuk terbesar ke internet. Sebagian besar perjalanan online — mencari resep, membandingkan harga, mencari bengkel terdekat, meriset skripsi — dimulai dari kolom pencarian. Bagi bisnis, ini berarti tiga hal.</p><p><strong>Traffic yang datang sudah berniat.</strong> Berbeda dengan iklan yang menyela orang saat menonton video atau menggulir media sosial, pengunjung dari pencarian datang karena <em>mereka sendiri</em> sedang mencari sesuatu. Orang yang mengetik "jasa servis AC Manado" bukan sedang iseng — dia punya AC rusak dan siap menghubungi seseorang hari ini.</p><p><strong>Biaya per pengunjung menurun seiring waktu.</strong> Iklan berhenti mendatangkan traffic begitu anggaran habis. Artikel yang berhasil menempati posisi baik bisa mendatangkan pengunjung setiap hari selama bertahun-tahun tanpa biaya tambahan per klik. Investasinya di depan (waktu dan tenaga membuat konten), hasilnya menetes panjang ke belakang.</p><p><strong>Peringkat tinggi membangun persepsi kredibilitas.</strong> Suka atau tidak, banyak pengguna menganggap situs yang muncul teratas sebagai yang paling tepercaya. Muncul konsisten di halaman satu untuk topik di bidang Anda adalah bentuk branding yang sulit dibeli.</p><p>Tentu SEO bukan obat segala penyakit. Hasilnya lambat, persaingannya nyata, dan tidak semua model bisnis cocok mengandalkannya. Pembahasan jujur soal untung-ruginya bisa Anda baca di [Manfaat SEO untuk Bisnis Kecil dan UMKM di Indonesia](<u>/manfaat-seo-untuk-bisnis-kecil-dan-umkm-di-indonesia/</u>).</p><h2>Tiga Pilar SEO: On-Page, Off-Page, dan Teknis</h2><p>Hampir semua pekerjaan SEO bisa dikelompokkan ke dalam tiga pilar. Memahami pembagian ini membantu Anda tahu sedang mengerjakan apa — dan apa yang belum tersentuh.</p><h3>1. SEO On-Page</h3><p>Semua optimasi yang dilakukan <em>di dalam</em> halaman itu sendiri: kualitas dan kedalaman konten, pemilihan serta penempatan kata kunci, judul halaman (title tag), meta description, struktur heading, tautan internal antar halaman, dan optimasi gambar. Ini pilar yang paling bisa Anda kendalikan penuh, dan tempat terbaik untuk memulai. Kami mengupasnya tuntas di panduan [SEO On-Page dari A sampai Z](<u>/seo-on-page-panduan-optimasi-halaman-dari-a-sampai-z/</u>).</p><h3>2. SEO Off-Page</h3><p>Semua sinyal kepercayaan yang datang <em>dari luar</em> website Anda — terutama backlink, yaitu tautan dari situs lain menuju situs Anda. Ketika media, blog, atau institusi yang kredibel menautkan konten Anda, di mata Google itu semacam surat rekomendasi. Off-page juga mencakup penyebutan brand dan reputasi secara umum. Pilar ini paling sulit dikendalikan dan paling rawan jalan pintas berbahaya seperti membeli backlink.</p><h3>3. SEO Teknis</h3><p>Fondasi yang memastikan mesin pencari bisa mengakses dan membaca situs Anda tanpa hambatan: kecepatan pemuatan, keamanan HTTPS, kenyamanan tampilan di ponsel, struktur URL, sitemap, dan bebasnya situs dari error. Konten sebagus apa pun tidak akan menyelamatkan situs yang lambat, sering error, atau tidak bisa dirayapi.</p><p>Ketiganya saling menopang. Konten hebat di situs yang lambat, atau situs kencang tanpa konten yang layak dibaca — dua-duanya sama-sama sulit menang. Perbandingan lebih dalam antara dua pilar pertama ada di artikel [SEO On-Page vs Off-Page: Penjelasan untuk Pemula](<u>/seo-on-page-vs-off-page-penjelasan-untuk-pemula/</u>).</p><h2>SEO vs Iklan Berbayar (SEM): Apa Bedanya?</h2><p>Buka Google dan cari "sewa mobil Jakarta". Beberapa hasil teratas biasanya berlabel kecil bertuliskan "Bersponsor" — itu iklan, bagian dari SEM (<em>Search Engine Marketing</em>) yang berbayar per klik. Di bawahnya barulah hasil organik, wilayah yang diperebutkan lewat SEO.</p><p>Perbedaan praktisnya sederhana: <strong>iklan itu menyewa, SEO itu membangun.</strong> Iklan memberi hasil instan — pasang hari ini, tampil hari ini — tetapi berhenti total saat anggaran habis. SEO berjalan lambat di awal, tetapi posisi yang sudah diraih terus bekerja tanpa biaya per klik.</p><p>Keduanya bukan musuh. Banyak bisnis memakai iklan untuk kebutuhan cepat (peluncuran produk, promo musiman) sambil membangun SEO untuk fondasi jangka panjang. Yang keliru adalah menganggap keduanya sama, atau berharap SEO memberi kecepatan iklan. Panduan memilih porsi yang tepat untuk situasi Anda ada di <a rel="" href="https://lalu.pro/articles/seo/perbedaan-seo-dan-sem-mana-yang-cocok-untuk-bisnis-anda-r8/">Perbedaan SEO dan SEM</a> dan [Traffic Organik vs Iklan Berbayar](<u>/traffic-organik-vs-iklan-berbayar-kelebihan-dan-kekurangan/</u>).</p><h2>Berapa Lama SEO Menunjukkan Hasil?</h2><p>Ini pertanyaan yang paling sering diajukan pemula — dan jawabannya jarang disukai: <strong>umumnya 3 sampai 6 bulan untuk melihat pergerakan berarti, dan 6 sampai 12 bulan untuk hasil yang stabil</strong>, tergantung usia situs, tingkat persaingan kata kunci, dan konsistensi Anda memproduksi konten.</p><p>Kenapa selama itu? Karena Google butuh waktu untuk menemukan konten Anda, memahaminya, mengujinya di hasil pencarian, dan mengumpulkan sinyal bahwa pengguna memang terbantu. Situs baru tanpa rekam jejak harus membuktikan diri lebih lama daripada situs yang sudah bertahun-tahun dipercaya.</p><p>Justru karena itulah Anda perlu waspada terhadap siapa pun yang <strong>menjanjikan "peringkat 1 dalam 2 minggu, dijamin"</strong>. Janji semacam itu hampir selalu berarti dua hal: kata kunci yang terlalu mudah sehingga tidak bernilai bisnis, atau teknik manipulatif yang cepat naik dan lebih cepat lagi dihukum Google. Ekspektasi waktu yang realistis per jenis situs kami rinci di [Berapa Lama SEO Menunjukkan Hasil?](<u>/berapa-lama-seo-menunjukkan-hasil-ekspektasi-yang-realistis/</u>), dan panduan menyaring penyedia jasa yang jujur ada di [Cara Memilih Jasa SEO yang Terpercaya](<u>/cara-memilih-jasa-seo-yang-terpercaya-dan-tanda-bahayanya/</u>).</p><h2>Cara Memulai SEO: 5 Langkah Pertama untuk Pemula</h2><p>Teori cukup. Berikut lima langkah pertama yang bisa Anda kerjakan sendiri, gratis, mulai minggu ini.</p><ol><li><p><strong>Pastikan situs Anda terindeks Google.</strong> Ketik <code>site:namadomainanda.com</code> di Google. Kalau halaman-halaman Anda muncul, bagus. Kalau kosong, ada masalah mendasar yang harus dibereskan lebih dulu.</p></li><li><p><strong>Pasang dua alat gratis dari Google.</strong> Google Search Console (untuk melihat kata kunci apa yang membawa orang ke situs Anda dan masalah apa yang Google temukan) dan Google Analytics (untuk memahami perilaku pengunjung). Keduanya gratis dan menjadi mata Anda selamanya.</p></li><li><p><strong>Lakukan riset kata kunci sederhana.</strong> Cari tahu apa yang benar-benar diketik audiens Anda — bukan apa yang Anda <em>kira</em> mereka ketik. Mulailah dari Google Autocomplete dan fitur "Orang lain juga bertanya". Ini keterampilan paling fundamental di SEO; panduan lengkapnya ada di [Riset Kata Kunci: Panduan Lengkap dari Nol sampai Mahir](<u>/riset-kata-kunci-panduan-lengkap-dari-nol-sampai-mahir/</u>).</p></li><li><p><strong>Buat satu konten yang sungguh-sungguh menjawab satu pencarian.</strong> Pilih satu kata kunci dengan persaingan rendah, lalu tulis halaman terbaik yang bisa Anda buat untuk pencarian itu — lebih jelas, lebih lengkap, lebih jujur daripada yang sudah ada di halaman satu.</p></li><li><p><strong>Jalankan checklist dasar.</strong> Judul halaman yang jelas, URL yang rapi, situs yang nyaman dibuka di HP, dan gambar yang tidak memberatkan. Daftar periksa lengkapnya kami siapkan di [Checklist SEO Dasar untuk Website Baru](<u>/checklist-seo-dasar-untuk-website-baru/</u>).</p></li></ol><p>Ulangi langkah 3–5 secara konsisten, dan Anda sudah mengerjakan SEO yang lebih benar daripada banyak situs yang sibuk mengejar trik.</p><h2>Kesalahan yang Paling Sering Dilakukan Pemula</h2><p>Tiga kesalahan ini menghabiskan waktu paling banyak dengan hasil paling sedikit.</p><ul><li><p><strong>Mengoptimasi untuk robot, bukan manusia.</strong> Menjejalkan kata kunci berulang-ulang, menulis kaku demi "kepadatan keyword", membuat konten yang menjawab algoritma tetapi menyiksa pembaca. Google semakin pintar mengenali ini, dan pengguna sudah lama muak.</p></li><li><p><strong>Menyerah di bulan kedua.</strong> SEO punya "lembah kesunyian" — periode ketika Anda sudah bekerja keras tetapi grafik masih datar. Mayoritas yang gagal bukan karena strateginya salah, melainkan karena berhenti tepat sebelum kurvanya menanjak.</p></li><li><p><strong>Mengejar jalan pintas.</strong> Membeli ribuan backlink murah, menyalin konten situs lain, memakai trik yang "katanya masih ampuh". Sebagian trik memang bisa naik sebentar — sampai pembaruan algoritma berikutnya menghapus semuanya, kadang beserta reputasi domain Anda.</p></li></ul><p>Daftar lengkap 15 kesalahan beserta cara menghindarinya ada di [Kesalahan SEO Pemula yang Paling Sering Terjadi](<u>/15-kesalahan-seo-pemula-yang-paling-sering-terjadi/</u>).</p><h2>FAQ Seputar SEO</h2><p><strong>Apa itu SEO dan contohnya?</strong></p><p>SEO (Search Engine Optimization) adalah upaya mengoptimalkan website agar muncul di posisi tinggi hasil pencarian organik. Contohnya: sebuah toko kue di Bandung menulis artikel "cara menyimpan kue ulang tahun agar tahan lama", artikel itu muncul di halaman satu Google, dan setiap bulan mendatangkan ratusan calon pembeli tanpa biaya iklan.</p><p><strong>Apakah SEO berbayar?</strong></p><p>Muncul di hasil organik tidak dipungut biaya oleh Google — Anda tidak bisa membayar Google untuk menaikkan peringkat organik. Namun mengerjakan SEO tetap butuh investasi: waktu Anda sendiri, atau biaya jasa profesional, penulis, dan tools jika menyewa pihak lain.</p><p><strong>Berapa lama SEO menunjukkan hasil?</strong></p><p>Umumnya 3–6 bulan untuk pergerakan awal dan 6–12 bulan untuk hasil stabil, tergantung persaingan kata kunci, usia situs, dan konsistensi konten. Janji hasil instan dalam hitungan hari patut dicurigai.</p><p><strong>Apa perbedaan SEO dan SEM?</strong></p><p>SEO mengejar posisi di hasil pencarian organik (tidak membayar per klik, hasilnya lambat tetapi tahan lama). SEM mencakup iklan pencarian berbayar (hasil instan, tetapi berhenti begitu anggaran habis). Keduanya bisa dan sering dipakai bersamaan.</p><p><strong>Apakah SEO bisa dipelajari sendiri?</strong></p><p>Bisa. Sebagian besar praktisi SEO di Indonesia belajar otodidak dari dokumentasi resmi Google, blog industri, dan praktik langsung di situs sendiri. Modal utamanya konsistensi dan kemauan bereksperimen — panduan belajarnya ada di roadmap 90 hari kami.</p><p><strong>Apakah SEO masih relevan di era AI seperti ChatGPT?</strong></p><p>Masih, meski bentuknya bergeser. Mesin jawab berbasis AI justru mengambil dan mengutip konten dari situs-situs yang kredibel — dan kredibilitas itu dibangun dengan prinsip yang sama dengan SEO yang baik: konten bermanfaat, terstruktur, dan tepercaya. Yang mati bukan SEO-nya, melainkan trik-trik murahannya.</p>]]></description><guid isPermaLink="false">7</guid><pubDate>Tue, 07 Jul 2026 23:36:44 +0000</pubDate></item><item><title>Vibe Coding: Ilusi Produktivitas yang Menguras Waktu dan Biaya</title><link>https://lalu.pro/articles/ai-untuk-blogging/vibe-coding-ilusi-produktivitas-yang-menguras-waktu-dan-biaya-r6/</link><description><![CDATA[
<p><img src="https://lalu.pro/uploads/monthly_2026_07/vibe_coding_hobi_mahal.avif.949bce75e353578d136499a8147a7c88.avif" /></p>
<h3>Pendahuluan: Mengapa Artikel Ini Penting Dibaca</h3><p><em>Vibe coding</em>—praktik menggunakan AI untuk menghasilkan kode tanpa pemahaman mendalam—telah menjadi tren signifikan di kalangan developer dan <em>entrepreneur</em>. Namun, realitas di lapangan menunjukkan bahwa mayoritas proyek yang dihasilkan melalui metode ini tidak pernah mencapai tahap produksi. Bahkan jika berhasil dirilis, banyak yang gagal mendapatkan adopsi pasar. Artikel ini menguraikan mengapa fenomena ini terjadi dan berapa biaya sebenarnya yang harus dibayar oleh para praktisinya.</p><h3>Poin Utama: Vibe Coding Bukan Solusi, Melainkan Hobi Mahal</h3><p>Kesalahpahaman terbesar seputar <em>vibe coding</em> adalah persepsi bahwa AI telah mengeliminasi semua hambatan untuk membangun sebuah produk. Kenyataannya, AI hanya menghilangkan hambatan teknis—bukan hambatan pasar.</p><p>Meskipun seseorang kini bisa menghasilkan kode dengan jauh lebih cepat, mereka rata-rata menghabiskan $1.200 hingga $4.000 per tahun untuk menghasilkan produk yang berakhir dengan tiga nasib tragis:</p><ul><li><p>Mangkrak di tengah jalan (terjebak di tahap pengembangan).</p></li><li><p>Tidak pernah dirilis sama sekali.</p></li><li><p>Dirilis, tetapi tidak ada permintaan pasar.</p></li></ul><p>Ini bukan masalah ketidakmampuan teknis. Ini tentang jebakan psikologis yang menganggap kesibukan (<em>motion</em>) sebagai sebuah kemajuan (<em>progress</em>).</p><h3>Bagian 1: Dua Nasib yang Menunggu Produk Hasil Vibe-Coding</h3><h4>Nasib Pertama: Ditinggalkan di Tengah Jalan</h4><p>Mayoritas proyek <em>vibe-coded</em> tidak pernah selesai. Pola umum yang sering teramati adalah:</p><ul><li><p><strong>Fase Awal:</strong> Antusiasme tinggi, produksi kode berjalan sangat cepat.</p></li><li><p><strong>Fase Tengah:</strong> Momentum mulai menurun setelah mencapai target (<em>milestone</em>) tertentu dan mulai menemui eror yang rumit.</p></li><li><p><strong>Fase Akhir:</strong> Proyek ditinggalkan begitu saja demi mengejar ide baru yang terasa lebih "segar".</p></li></ul><p>Fenomena ini menciptakan apa yang disebut <em>“vibe cemetery”</em>—kumpulan proyek yang hampir selesai tetapi tidak pernah diluncurkan. Dampaknya bukan hanya pemborosan finansial, melainkan juga memicu siklus kegagalan berulang yang mengikis kesehatan mental pelakunya.</p><p><img class="ipsImage ipsImage_thumbnailed ipsRichText__align--block" data-fileid="8" src="https://lalu.pro/uploads/monthly_2026_07/Vibe_Coding.thumb.avif.058af756f5bc82d8ad4d292be9923f4c.avif" alt="Vibe_Coding.avif" title="Vibe_Coding.avif" width="1000" height="454" data-full-image="https://lalu.pro/uploads/monthly_2026_07/Vibe_Coding.avif.c13b6f12b5b815b4ee126f684cd4986c.avif" data-extension="core_Attachment" loading="lazy"></p><h4>Nasib Kedua: Peluncuran Tanpa Pengguna</h4><p>Skenario alternatifnya—di mana produk benar-benar dirilis—justru bisa terasa lebih menyakitkan. Setelah menginvestasikan waktu dan sumber daya yang besar, sang pencipta produk harus menghadapi kenyataan bahwa:</p><ul><li><p>Pasar tidak membutuhkan solusi yang mereka tawarkan.</p></li><li><p>Kompetitor yang sudah mapan telah mendominasi ceruk (<em>niche</em>) tersebut.</p></li><li><p>Pengguna dapat merasakan pendekatan instan (<em>low-effort approach</em>) dalam desain dan fungsionalitas produk.</p></li></ul><p>Tren terbaru menunjukkan bahwa pengguna mulai mengalami <em>“vibe fatigue”</em>—kelelahan menghadapi banjiran produk baru yang terasa seperti cetakan <em>template</em> AI, alih-alih sebuah solusi yang dipikirkan matang-matang.</p><h3>Bagian 2: Analisis Biaya Tersembunyi yang Jarang Diperhitungkan</h3><h4>Biaya Finansial</h4><p>Seorang praktisi <em>vibe coding</em> yang serius biasanya mengalokasikan anggaran yang tidak sedikit:</p><ul><li><p><strong>Kelas Pemula:</strong> ~$100/bulan untuk langganan berbagai alat AI (sekitar $1.200 setahun).</p></li><li><p><strong>Kelas Menengah:</strong> Langganan beberapa lisensi sekaligus yang bisa mencapai $2.400 setahun.</p></li><li><p><strong>Kelas Ekstrem:</strong> Menggunakan <em>agentic loops</em> terparalel dengan pengeluaran token mencapai $2.000–$4.000 per bulan.</p></li></ul><p>Itu perkiraan untuk wilayah Eropa dan Amerika yang menggunakan mata uang dolar. Sementara wilayah lain yang tidak menggunakan mata uang dolar menurut saya menghabiskan lebih sedikit. </p><p>Sering kali pengeluaran ini menghasilkan nol konversi yang berhasil dikomersialkan.</p><h4>Biaya Waktu dan Kesehatan Mental</h4><p><em>Vibe coding</em> memiliki karakteristik yang adiktif. Sensasi produktivitas semu (<em>false productivity</em>) yang dihasilkan—melihat baris kode terbangkitkan secara otomatis dengan cepat—memberikan letupan dopamin yang mirip seperti bermain <em>video game</em>, bukan seperti bekerja nyata. Konsekuensinya:</p><ul><li><p>Waktu berharga teralihkan dari interaksi sosial, aktivitas fisik, dan relaksasi.</p></li><li><p>Siklus proyek mangkrak yang berulang menciptakan beban psikologis negatif.</p></li><li><p>Munculnya rasa pencapaian semu (<em>false sense of accomplishment</em>) yang tidak pernah diterjemahkan menjadi keberhasilan nyata.</p></li></ul><h4>Biaya Peluang (<em>Opportunity Cost</em>)</h4><p>Sumber daya yang dihabiskan untuk proyek <em>vibe-coded</em>—baik waktu, fokus, maupun uang—seharusnya bisa dialokasikan untuk aktivitas lain yang jauh lebih produktif, bernilai, atau menghasilkan keuntungan nyata.</p><h3>Bagian 3: Mengapa Pasar Menolak Produk Vibe-Coded?</h3><h4>Kejenuhan Pasar dan Hilangnya Keunikan</h4><p>Premis fundamental <em>vibe coding</em> adalah bahwa AI menghilangkan hambatan untuk membangun (<em>building</em>). Namun, premis ini mengabaikan hukum ekonomi dasar: <strong>jika semua orang bisa membuat suatu produk dengan mudah, maka produk tersebut tidak lagi memiliki keunikan.</strong></p><p>Ketika perusahaan AI mendorong adopsi massal, dampaknya adalah:</p><ul><li><p>Ribuan produk serupa muncul secara bersamaan dengan tawaran nilai (<em>value proposition</em>) yang identik.</p></li><li><p>Pengguna akhir mengalami kelumpuhan pilihan (<em>choice paralysis</em>), yang membuat mereka cenderung tetap memilih produk lama yang sudah tepercaya.</p></li><li><p><em>Startup</em> dengan modal besar dan tim berpengalaman tetap mendominasi setiap kategori.</p></li></ul><h4>Pentingnya Momentum dan Konteks Pasar</h4><p>Kesuksesan produk digital sering kali tidak terletak pada kecanggihan teknis atau keaslian ide, melainkan pada momentum (<em>timing</em>) masuk ke pasar.</p><p>Sebagai gambaran, seorang pengembang aplikasi untuk iOS bisa dengan mudah mendatangkan ratusan ribu unduhan, dulu ketika pasar masih baru dan sepi. Namun, mencoba hal yang sama hari ini akan menghasilkan angka yang mendekati nol karena saturasi pasar yang luar biasa padat. Secepat apa pun AI menghasilkan kode Anda, teknologi tersebut tidak bisa mempercepat kesiapan pasar (<em>market readiness</em>).</p><h3>Bagian 4: Satu-Satunya Model yang Viable: Bangun untuk Diri Sendiri</h3><h4>Kriteria Produk yang Layak Dipertahankan</h4><p>Analisis mendalam terhadap produk-produk independen yang bertahan lama menunjukkan satu pola universal: produk yang terus berkembang adalah produk yang <strong>menyelesaikan masalah nyata dari si pembuatnya sendiri.</strong></p><p>Tanda bahwa proyek Anda layak dilanjutkan:</p><ul><li><p><strong>Kebutuhan Nyata:</strong> Anda menggunakan produk tersebut setiap hari untuk produktivitas pribadi.</p></li><li><p><strong>Didorong Gairah (<em>Passion-Driven</em>):</strong> Anda rela melakukan pemeliharaan (<em>maintenance</em>) dan perbaikan bug tanpa kompensasi finansial instan.</p></li><li><p><strong>Pengembangan Iteratif:</strong> Pembaruan fitur didorong oleh pengalaman penggunaan personal, bukan sekadar menebak-nebak keinginan pasar.</p></li></ul><h4>Jebakan Pola Pikir Optimalisasi</h4><p>Banyak orang terjebak dalam logika bahwa peningkatan efisiensi kecil (misalnya membuat alur kerja 15% lebih cepat) adalah alasan kuat untuk terus mengembangkan sebuah produk komersial.</p><p>Kenyataannya, antusiasme awal akan selalu menurun seiring berjalannya waktu, sementara tingkat kesulitan teknis yang muncul belakangan justru semakin meningkat. Produk yang hanya menawarkan nilai tambah kecil (<em>incremental value</em>) lambat laun akan ditinggalkan oleh pembuatnya sendiri karena kelelahan emosional.</p><h3>Bagian 5: Konteks Industri dan Langkah ke Depan</h3><h4>Komoditisasi AI dan Dampak Jangka Panjang</h4><p>Model AI saat ini bukan lagi benteng pertahanan bisnis (<em>competitive moat</em>). Kehadiran model-model sumber terbuka (<em>open-source</em>) yang semakin kuat dan efisien dari berbagai negara membuat kemampuan dasar menghasilkan kode menjadi komoditas murah.</p><p>Bagi para <em>vibe coders</em>, ini berarti:</p><ul><li><p>Kualitas keluaran (<em>output</em>) dari model gratisan/lokal akan segera menyamai model komersial terkemuka dalam waktu dekat.</p></li><li><p>Saturasi pasar akan semakin parah, memperburuk masalah diferensiasi produk yang dialami oleh produk <em>vibe-coded</em>.</p></li></ul><h4>Jalan Keluar yang Berkelanjutan (<em>Sustainable Path Forward</em>)</h4><p>Bagi Anda yang tetap ingin berkreasi menggunakan bantuan AI, ubah strateginya:</p><ol><li><p><strong>Turunkan Ekspektasi:</strong> Fokuslah membangun alat (<em>tools</em>) yang nyata-nyata Anda gunakan sendiri, bukan alat yang "Anda pikir" akan dibeli orang lain.</p></li><li><p><strong>Terima Realitas Pasar:</strong> Probabilitas membangun perusahaan bernilai miliaran dolar secara instan lewat <em>vibe coding</em> sangatlah kecil. Alihkan fokus pada utilitas personal yang konkret.</p></li><li><p><strong>Diversifikasi Penggunaan AI:</strong> Gunakan AI untuk pemecahan masalah yang memiliki dampak nyata pada efisiensi kerja harian Anda, alih-alih terus-menerus memproduksi aplikasi konsumen (<em>consumer apps</em>) baru yang generik.</p></li></ol><h3>Kesimpulan: Mengevaluasi Ulang Nilai Vibe Coding</h3><p><em>Vibe coding</em> tidak gagal karena teknologi AI-nya buruk. Sebaliknya, fenomena ini gagal karena memberikan <strong>ilusi</strong> bahwa menyingkirkan hambatan teknis sama dengan menyingkirkan hambatan pasar.</p><p>Kenyataan pahit yang harus diterima:</p><ul><li><p>Hambatan pasar jauh lebih besar dan kejam daripada hambatan menulis kode.</p></li><li><p>Sibuk menghasilkan baris kode (<em>motion</em>) tidak sama dengan membuat kemajuan produk (<em>progress</em>).</p></li><li><p>Tingkat penyelesaian (<em>completion rate</em>) hingga produk sukses dari metode <em>vibe-coding</em> murni mendekati nol persen.</p></li></ul><p>Sebelum Anda memulai proyek koding berbasis AI berikutnya, jawablah pertanyaan ini dengan jujur:</p><blockquote class="ipsQuote" cite="" data-ipsquote=""><div class="ipsQuote_contents" data-ipstruncate=""><p><em>"Apakah saya membangun ini karena saya benar-benar membutuhkannya, atau saya hanya ingin merasa menjadi seorang entrepreneur yang terlihat produktif di media sosial?"</em></p></div></blockquote><p>Jika jawabannya adalah yang kedua, artikel ini adalah pengingat untuk menata ulang prioritas Anda. Namun, jika jawabannya adalah yang pertama, silakan lanjutkan—tetapi dengan ekspektasi yang realistis dan fokus pada kebermanfaatan bagi diri sendiri.</p><p>Apakah Anda sedang terjebak atau justru berkembang dalam tren <em>vibe coding</em> ini? Mari refleksikan strategi Anda. Bagikan pengalaman, insights, atau sudut pandang Anda di kolom komentar!</p>]]></description><guid isPermaLink="false">6</guid><pubDate>Mon, 29 Jun 2026 07:01:19 +0000</pubDate></item><item><title>Membuat Template Blogger Mulai dari Nol</title><link>https://lalu.pro/articles/blogger/membuat-template-blogger-mulai-dari-nol-r5/</link><description><![CDATA[<blockquote class="ipsQuote" cite="" data-ipsquote=""><div class="ipsQuote_contents" data-ipstruncate=""><p><strong>Tujuan:</strong> Membangun template Blogger dari kerangka kosong secara bertahap — mulai dari header, footer, sidebar, hingga area postingan — sehingga pemula bisa melihat langsung perubahan yang terjadi di blog mereka.</p></div></blockquote><hr><h2>Prasyarat</h2><ul><li><p>Akun Google &amp; blog Blogger yang sudah dibuat</p></li><li><p>Akses ke <strong>Tema → Edit HTML</strong> di dashboard Blogger</p></li><li><p>Browser (disarankan Chrome/Firefox)</p></li><li><p>Semangat belajar</p></li></ul><hr><h2>Cara Mengganti Template di Blogger</h2><ol><li><p>Login ke <a rel="external nofollow" href="https://www.blogger.com/">blogger.com</a></p></li><li><p>Pilih blog Anda → klik <strong>Tema</strong> di menu kiri</p></li><li><p>Klik tombol <strong>Edit HTML</strong> (ikon pensil)</p></li><li><p>Hapus semua kode yang ada</p></li><li><p>Tempel kode baru dari tutorial ini</p></li><li><p>Klik <strong>Simpan tema</strong></p></li><li><p>Buka blog Anda di tab baru untuk melihat hasilnya</p></li></ol><blockquote class="ipsQuote" cite="" data-ipsquote=""><div class="ipsQuote_contents" data-ipstruncate=""><p><span class="ipsEmoji" title="">💡</span> <strong>Tips:</strong> Buka blog Anda di tab terpisah, lalu setiap selesai menyimpan, refresh tab tersebut untuk melihat perubahannya langsung.</p></div></blockquote><hr><h2>Tahap 1 — Kerangka Dasar (Hanya Tulang)</h2><p>Kita mulai dari kerangka paling sederhana: hanya struktur HTML tanpa konten apapun. Ini untuk memastikan template terbaca oleh Blogger tanpa error.</p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE html&gt;
&lt;html b:css='false' b:layoutsversion='3'
  xmlns='http://www.w3.org/1999/xhtml'
  xmlns:b='http://www.google.com/2005/gml/b'
  xmlns:data='http://www.google.com/2005/gml/data'
  xmlns:expr='http://www.google.com/2005/gml/expr'&gt;

&lt;head&gt;
  &lt;meta charset='utf-8'/&gt;
  &lt;meta content='width=device-width,initial-scale=1' name='viewport'/&gt;
  &lt;title&gt;&lt;data:view.title.escape/&gt;&lt;/title&gt;

  &lt;b:skin&gt;&lt;![CDATA[
    body {
      font-family: sans-serif;
      margin: 0;
      padding: 0;
      background-color: #f4f4f4;
      color: #333;
    }
    .wrapper {
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }
  ]]&gt;&lt;/b:skin&gt;
&lt;/head&gt;

&lt;body&gt;
  &lt;div class='wrapper'&gt;
    &lt;p&gt;Blog saya sedang dibangun...&lt;/p&gt;
  &lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre><h3>Apa yang terjadi?</h3><p>Simpan dan refresh blog Anda. Anda akan melihat halaman putih dengan tulisan <strong>"Blog saya sedang dibangun..."</strong></p><p>Ini artinya:</p><ul><li><p>Blogger menerima template Anda</p></li><li><p>Judul tab browser sudah menggunakan nama blog Anda (lihat <code>&lt;data:view.title.escape/&gt;</code>)</p></li><li><p>CSS dasar sudah aktif</p></li></ul><h3>Penjelasan Kode Penting</h3><div class="ipsRichText__table-wrapper"><table style="width: 692px;"><colgroup><col style="width:336px;"><col style="width:356px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Kode</p></th><th colspan="1" rowspan="1"><p>Fungsi</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>b:css='false'</code></p></td><td colspan="1" rowspan="1"><p>Mematikan CSS bawaan Blogger agar kita pakai CSS sendiri</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>b:layoutsversion='3'</code></p></td><td colspan="1" rowspan="1"><p>Menggunakan versi layout Blogger terbaru</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>xmlns:b</code>, <code>xmlns:data</code>, <code>xmlns:expr</code></p></td><td colspan="1" rowspan="1"><p>Namespace yang dibutuhkan agar tag Blogger (<code>b:section</code>, <code>data:</code>, dll) bisa dibaca</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;data:view.title.escape/&gt;</code></p></td><td colspan="1" rowspan="1"><p>Tag Blogger untuk menampilkan judul halaman secara otomatis</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;b:skin&gt;&lt;![CDATA[ ... ]]&gt;&lt;/b:skin&gt;</code></p></td><td colspan="1" rowspan="1"><p>Tempat menulis CSS utama template</p></td></tr></tbody></table></div><hr><h2>Tahap 2 — Menambahkan Header</h2><p>Sekarang kita tambahkan <strong>header</strong> — bagian paling atas blog yang biasanya berisi nama blog dan deskripsi.</p><p>Ganti seluruh kode dengan yang berikut:</p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE html&gt;
&lt;html b:css='false' b:layoutsversion='3'
  xmlns='http://www.w3.org/1999/xhtml'
  xmlns:b='http://www.google.com/2005/gml/b'
  xmlns:data='http://www.google.com/2005/gml/data'
  xmlns:expr='http://www.google.com/2005/gml/expr'&gt;

&lt;head&gt;
  &lt;meta charset='utf-8'/&gt;
  &lt;meta content='width=device-width,initial-scale=1' name='viewport'/&gt;
  &lt;title&gt;&lt;data:view.title.escape/&gt;&lt;/title&gt;

  &lt;b:skin&gt;&lt;![CDATA[
    * { box-sizing: border-box; }
    body {
      font-family: sans-serif;
      margin: 0;
      padding: 0;
      background-color: #f4f4f4;
      color: #333;
    }
    .wrapper {
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }

    /* === HEADER === */
    header {
      background: #2c3e50;
      color: #fff;
      padding: 30px 20px;
      margin-bottom: 20px;
      border-radius: 6px;
    }
    header a {
      color: #fff;
      text-decoration: none;
    }
  ]]&gt;&lt;/b:skin&gt;
&lt;/head&gt;

&lt;body&gt;
  &lt;div class='wrapper'&gt;

    &lt;header role='banner'&gt;
      &lt;b:section class='header-section' id='header-section' maxwidgets='1' showaddelement='yes'/&gt;
    &lt;/header&gt;

  &lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre><h3>Cara Menambahkan Widget Header</h3><p>Setelah menyimpan dan refresh blog, Anda akan melihat <strong>area gelap di atas</strong> (header). Tapi masih kosong! Sekarang:</p><ol><li><p>Kembali ke dashboard Blogger → <strong>Tata Letak</strong></p></li><li><p>Anda akan melihat kotak bertuliskan <strong>"header-section"</strong></p></li><li><p>Klik tombol <strong>Tambah Widget</strong> di kotak header</p></li><li><p>Pilih widget <strong>Header</strong> → Simpan</p></li><li><p>Refresh blog Anda</p></li></ol><p><strong>Hasilnya:</strong> Nama blog dan deskripsi muncul di header dengan latar belakang gelap.</p><h3>Penjelasan <code>&lt;b:section&gt;</code></h3><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>&lt;b:section id='header-section' maxwidgets='1' showaddelement='yes'/&gt;
</code></pre><div class="ipsRichText__table-wrapper"><table style="width: 554px;"><colgroup><col style="width:207px;"><col style="width:347px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Atribut</p></th><th colspan="1" rowspan="1"><p>Fungsi</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>id</code></p></td><td colspan="1" rowspan="1"><p>Nama unik untuk section ini (wajib, tidak boleh sama)</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>maxwidgets='1'</code></p></td><td colspan="1" rowspan="1"><p>Membatasi hanya 1 widget yang bisa ditambahkan di sini</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>showaddelement='yes'</code></p></td><td colspan="1" rowspan="1"><p>Menampilkan tombol "Tambah Widget" di mode Tata Letak</p></td></tr></tbody></table></div><hr><h2>Tahap 3 — Menambahkan Footer</h2><p>Footer adalah bagian paling bawah blog. Mari kita tambahkan sekarang.</p><p>Ganti seluruh kode dengan:</p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE html&gt;
&lt;html b:css='false' b:layoutsversion='3'
  xmlns='http://www.w3.org/1999/xhtml'
  xmlns:b='http://www.google.com/2005/gml/b'
  xmlns:data='http://www.google.com/2005/gml/data'
  xmlns:expr='http://www.google.com/2005/gml/expr'&gt;

&lt;head&gt;
  &lt;meta charset='utf-8'/&gt;
  &lt;meta content='width=device-width,initial-scale=1' name='viewport'/&gt;
  &lt;title&gt;&lt;data:view.title.escape/&gt;&lt;/title&gt;

  &lt;b:skin&gt;&lt;![CDATA[
    * { box-sizing: border-box; }
    body {
      font-family: sans-serif;
      margin: 0;
      padding: 0;
      background-color: #f4f4f4;
      color: #333;
    }
    .wrapper {
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }

    /* === HEADER === */
    header {
      background: #2c3e50;
      color: #fff;
      padding: 30px 20px;
      margin-bottom: 20px;
      border-radius: 6px;
    }
    header a {
      color: #fff;
      text-decoration: none;
    }

    /* === FOOTER === */
    footer {
      background: #2c3e50;
      color: #ccc;
      padding: 20px;
      margin-top: 20px;
      border-radius: 6px;
      text-align: center;
      font-size: 0.9em;
    }
    footer a {
      color: #aed6f1;
      text-decoration: none;
    }
  ]]&gt;&lt;/b:skin&gt;
&lt;/head&gt;

&lt;body&gt;
  &lt;div class='wrapper'&gt;

    &lt;header role='banner'&gt;
      &lt;b:section class='header-section' id='header-section' maxwidgets='1' showaddelement='yes'/&gt;
    &lt;/header&gt;

    &lt;!-- Area tengah (isi &amp; sidebar) akan ditambahkan nanti --&gt;
    &lt;div style='background:#fff; padding:20px; border-radius:6px; min-height:200px; margin-bottom:20px;'&gt;
      &lt;p style='color:#999; text-align:center;'&gt;[ Area Postingan &amp;amp; Sidebar — segera hadir ]&lt;/p&gt;
    &lt;/div&gt;

    &lt;footer role='contentinfo'&gt;
      &lt;b:section class='footer-section' id='footer-section' showaddelement='yes'/&gt;
    &lt;/footer&gt;

  &lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre><h3>Cara Menambahkan Widget Footer</h3><ol><li><p>Kembali ke <strong>Tata Letak</strong></p></li><li><p>Di area <strong>footer-section</strong>, klik <strong>Tambah Widget</strong></p></li><li><p>Pilih <strong>Teks</strong> → ketik "<span class="ipsEmoji">©</span> 2025 Blog Saya. Hak cipta dilindungi." → Simpan</p></li><li><p>Refresh blog</p></li></ol><p><strong>Hasilnya:</strong> Sekarang blog Anda sudah punya header di atas, area kosong di tengah (placeholder), dan footer di bawah.</p><hr><h2>Tahap 4 — Menambahkan Sidebar</h2><p>Sekarang kita bentuk layout dua kolom: <strong>konten utama</strong> di kiri dan <strong>sidebar</strong> di kanan.</p><p>Ganti seluruh kode dengan:</p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE html&gt;
&lt;html b:css='false' b:layoutsversion='3'
  xmlns='http://www.w3.org/1999/xhtml'
  xmlns:b='http://www.google.com/2005/gml/b'
  xmlns:data='http://www.google.com/2005/gml/data'
  xmlns:expr='http://www.google.com/2005/gml/expr'&gt;

&lt;head&gt;
  &lt;meta charset='utf-8'/&gt;
  &lt;meta content='width=device-width,initial-scale=1' name='viewport'/&gt;
  &lt;title&gt;&lt;data:view.title.escape/&gt;&lt;/title&gt;

  &lt;b:skin&gt;&lt;![CDATA[
    * { box-sizing: border-box; }
    body {
      font-family: sans-serif;
      margin: 0;
      padding: 0;
      background-color: #f4f4f4;
      color: #333;
    }
    .wrapper {
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }

    /* === HEADER === */
    header {
      background: #2c3e50;
      color: #fff;
      padding: 30px 20px;
      margin-bottom: 20px;
      border-radius: 6px;
    }
    header a {
      color: #fff;
      text-decoration: none;
    }

    /* === LAYOUT TENGAH === */
    .main-container {
      display: flex;
      flex-direction: column;
      gap: 20px;
      margin-bottom: 20px;
    }
    @media (min-width: 768px) {
      .main-container {
        flex-direction: row;
      }
    }

    /* === AREA KONTEN UTAMA === */
    main {
      flex: 3;
      background: #fff;
      padding: 20px;
      border-radius: 6px;
      min-height: 300px;
    }

    /* === SIDEBAR === */
    aside {
      flex: 1;
      background: #fff;
      padding: 20px;
      border-radius: 6px;
    }
    aside h2, aside h3 {
      font-size: 1em;
      border-bottom: 2px solid #2c3e50;
      padding-bottom: 6px;
      margin-top: 0;
    }

    /* === FOOTER === */
    footer {
      background: #2c3e50;
      color: #ccc;
      padding: 20px;
      border-radius: 6px;
      text-align: center;
      font-size: 0.9em;
    }
    footer a {
      color: #aed6f1;
      text-decoration: none;
    }
  ]]&gt;&lt;/b:skin&gt;
&lt;/head&gt;

&lt;body&gt;
  &lt;div class='wrapper'&gt;

    &lt;header role='banner'&gt;
      &lt;b:section class='header-section' id='header-section' maxwidgets='1' showaddelement='yes'/&gt;
    &lt;/header&gt;

    &lt;div class='main-container'&gt;

      &lt;main id='main-content' role='main'&gt;
        &lt;!-- Postingan akan ditambahkan di tahap berikutnya --&gt;
        &lt;p style='color:#999; text-align:center;'&gt;[ Area Postingan — segera hadir ]&lt;/p&gt;
      &lt;/main&gt;

      &lt;aside id='sidebar' role='complementary'&gt;
        &lt;b:section class='sidebar-section' id='sidebar-section' showaddelement='yes'/&gt;
      &lt;/aside&gt;

    &lt;/div&gt;

    &lt;footer role='contentinfo'&gt;
      &lt;b:section class='footer-section' id='footer-section' showaddelement='yes'/&gt;
    &lt;/footer&gt;

  &lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre><h3>Cara Mengisi Sidebar</h3><ol><li><p>Buka <strong>Tata Letak</strong> di dashboard</p></li><li><p>Di kotak <strong>sidebar-section</strong>, klik <strong>Tambah Widget</strong></p></li><li><p>Coba tambahkan:</p><ul><li><p><strong>Arsip Blog</strong> — menampilkan arsip postingan per bulan</p></li><li><p><strong>Label</strong> — menampilkan semua label/kategori</p></li><li><p><strong>Tentang Saya</strong> — profil singkat Anda</p></li></ul></li><li><p>Simpan dan refresh blog</p></li></ol><p><strong>Hasilnya:</strong> Blog Anda sekarang punya dua kolom — kiri untuk konten, kanan untuk sidebar. Di layar HP (&lt; 768px), keduanya akan tampil berurutan ke bawah secara otomatis berkat CSS <code>flexbox</code> + <code>@media</code>.</p><h3>Memahami Flexbox yang Digunakan</h3><pre spellcheck="" class="ipsCode language-css" data-language="CSS"><code>.main-container {
  display: flex;        /* aktifkan flexbox */
  flex-direction: column; /* default: kolom (untuk HP) */
  gap: 20px;
}
@media (min-width: 768px) {
  .main-container {
    flex-direction: row; /* di layar lebar: jadi baris (kolom-kolom berdampingan) */
  }
}

main  { flex: 3; } /* ambil 3 bagian dari ruang tersedia */
aside { flex: 1; } /* ambil 1 bagian dari ruang tersedia */
</code></pre><p>Artinya: main mendapat <strong>75%</strong> lebar, sidebar mendapat <strong>25%</strong> lebar.</p><hr><h2>Tahap 5 — Menambahkan Area Postingan (Blog1)</h2><p>Ini tahap terakhir dan paling penting. Kita akan mengganti placeholder dengan <code>&lt;b:widget id='Blog1'&gt;</code> yang menampilkan postingan sungguhan.</p><blockquote class="ipsQuote" cite="" data-ipsquote=""><div class="ipsQuote_contents" data-ipstruncate=""><p><span class="ipsEmoji" title="">⚠️</span> <strong>Perhatian:</strong> Widget Blog1 memang menghasilkan kode yang panjang. Tapi di tahap ini Anda sudah paham strukturnya, jadi tidak perlu khawatir.</p></div></blockquote><p>Ganti seluruh kode dengan versi <strong>final</strong> berikut:</p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE html&gt;
&lt;html b:css='false' b:layoutsversion='3'
  xmlns='http://www.w3.org/1999/xhtml'
  xmlns:b='http://www.google.com/2005/gml/b'
  xmlns:data='http://www.google.com/2005/gml/data'
  xmlns:expr='http://www.google.com/2005/gml/expr'&gt;

&lt;head&gt;
  &lt;meta charset='utf-8'/&gt;
  &lt;meta content='width=device-width,initial-scale=1' name='viewport'/&gt;
  &lt;title&gt;&lt;data:view.title.escape/&gt;&lt;/title&gt;

  &lt;b:skin&gt;&lt;![CDATA[
    * { box-sizing: border-box; }
    body {
      font-family: sans-serif;
      margin: 0;
      padding: 0;
      background-color: #f4f4f4;
      color: #333;
    }
    .wrapper {
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }

    /* === HEADER === */
    header {
      background: #2c3e50;
      color: #fff;
      padding: 30px 20px;
      margin-bottom: 20px;
      border-radius: 6px;
    }
    header a {
      color: #fff;
      text-decoration: none;
    }

    /* === LAYOUT TENGAH === */
    .main-container {
      display: flex;
      flex-direction: column;
      gap: 20px;
      margin-bottom: 20px;
    }
    @media (min-width: 768px) {
      .main-container {
        flex-direction: row;
      }
    }

    /* === AREA KONTEN UTAMA === */
    main {
      flex: 3;
      background: #fff;
      padding: 20px;
      border-radius: 6px;
    }

    /* === POSTINGAN === */
    .post {
      margin-bottom: 40px;
      padding-bottom: 40px;
      border-bottom: 1px solid #eee;
    }
    .post:last-child {
      border-bottom: none;
      margin-bottom: 0;
    }
    .post-title {
      font-size: 1.5em;
      margin: 0 0 8px 0;
    }
    .post-title a {
      color: #2c3e50;
      text-decoration: none;
    }
    .post-title a:hover {
      color: #e74c3c;
    }
    .post-meta {
      font-size: 0.85em;
      color: #888;
      margin-bottom: 14px;
    }
    .post-body {
      line-height: 1.7;
    }
    .post-labels {
      margin-top: 12px;
      font-size: 0.85em;
    }
    .post-labels a {
      background: #eaf2ff;
      color: #2980b9;
      padding: 2px 8px;
      border-radius: 12px;
      text-decoration: none;
      margin-right: 4px;
    }

    /* === NAVIGASI HALAMAN === */
    .blog-pager {
      display: flex;
      justify-content: space-between;
      margin-top: 20px;
      font-size: 0.9em;
    }
    .blog-pager a {
      background: #2c3e50;
      color: #fff;
      padding: 8px 16px;
      border-radius: 4px;
      text-decoration: none;
    }
    .blog-pager a:hover {
      background: #e74c3c;
    }

    /* === SIDEBAR === */
    aside {
      flex: 1;
      background: #fff;
      padding: 20px;
      border-radius: 6px;
    }
    aside h2, aside h3 {
      font-size: 1em;
      border-bottom: 2px solid #2c3e50;
      padding-bottom: 6px;
      margin-top: 0;
    }

    /* === FOOTER === */
    footer {
      background: #2c3e50;
      color: #ccc;
      padding: 20px;
      border-radius: 6px;
      text-align: center;
      font-size: 0.9em;
    }
    footer a {
      color: #aed6f1;
      text-decoration: none;
    }
  ]]&gt;&lt;/b:skin&gt;
&lt;/head&gt;

&lt;body&gt;
  &lt;div class='wrapper'&gt;

    &lt;header role='banner'&gt;
      &lt;b:section class='header-section' id='header-section' maxwidgets='1' showaddelement='yes'/&gt;
    &lt;/header&gt;

    &lt;div class='main-container'&gt;

      &lt;main id='main-content' role='main'&gt;
        &lt;b:section class='main-section' id='main-section' showaddelement='yes'&gt;

          &lt;b:widget id='Blog1' locked='false' title='Postingan Blog' type='Blog' version='2'&gt;
            &lt;b:widget-settings&gt;
              &lt;b:widget-setting name='showDateHeader'&gt;false&lt;/b:widget-setting&gt;
            &lt;/b:widget-settings&gt;
            &lt;b:includable id='main' var='top'&gt;

              &lt;!-- Loop semua postingan --&gt;
              &lt;b:loop values='data:posts' var='post'&gt;
                &lt;article class='post' expr:id='"post-" + data:post.id'&gt;

                  &lt;!-- Judul postingan --&gt;
                  &lt;h2 class='post-title'&gt;
                    &lt;a expr:href='data:post.url'&gt;&lt;data:post.title/&gt;&lt;/a&gt;
                  &lt;/h2&gt;

                  &lt;!-- Meta: tanggal &amp; penulis --&gt;
                  &lt;div class='post-meta'&gt;
                    &lt;data:post.dateHeader/&gt; &amp;#8226; oleh &lt;data:post.author/&gt;
                  &lt;/div&gt;

                  &lt;!-- Isi postingan --&gt;
                  &lt;div class='post-body'&gt;
                    &lt;b:if cond='data:view.isPost'&gt;
                      &lt;!-- Di halaman postingan tunggal: tampilkan semua isi --&gt;
                      &lt;data:post.body/&gt;
                    &lt;b:else/&gt;
                      &lt;!-- Di halaman beranda: tampilkan ringkasan / snippet --&gt;
                      &lt;data:post.snippets/&gt;
                      &lt;a expr:href='data:post.url'&gt;Baca selengkapnya &amp;#8594;&lt;/a&gt;
                    &lt;/b:if&gt;
                  &lt;/div&gt;

                  &lt;!-- Label / kategori --&gt;
                  &lt;b:if cond='data:post.labels'&gt;
                    &lt;div class='post-labels'&gt;
                      Label:
                      &lt;b:loop values='data:post.labels' var='label'&gt;
                        &lt;a expr:href='data:label.url'&gt;&lt;data:label.name/&gt;&lt;/a&gt;
                      &lt;/b:loop&gt;
                    &lt;/div&gt;
                  &lt;/b:if&gt;

                &lt;/article&gt;
              &lt;/b:loop&gt;

              &lt;!-- Navigasi halaman (Lebih Baru / Lebih Lama) --&gt;
              &lt;div class='blog-pager'&gt;
                &lt;b:if cond='data:newerPageUrl'&gt;
                  &lt;a expr:href='data:newerPageUrl'&gt;&amp;#8592; Postingan lebih baru&lt;/a&gt;
                &lt;/b:if&gt;
                &lt;b:if cond='data:olderPageUrl'&gt;
                  &lt;a expr:href='data:olderPageUrl'&gt;Postingan lebih lama &amp;#8594;&lt;/a&gt;
                &lt;/b:if&gt;
              &lt;/div&gt;

            &lt;/b:includable&gt;
          &lt;/b:widget&gt;

        &lt;/b:section&gt;
      &lt;/main&gt;

      &lt;aside id='sidebar' role='complementary'&gt;
        &lt;b:section class='sidebar-section' id='sidebar-section' showaddelement='yes'/&gt;
      &lt;/aside&gt;

    &lt;/div&gt;

    &lt;footer role='contentinfo'&gt;
      &lt;b:section class='footer-section' id='footer-section' showaddelement='yes'/&gt;
    &lt;/footer&gt;

  &lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre><h3>Cara Kerja Widget Blog1</h3><p>Widget ini berisi <strong>tag-tag Blogger</strong> untuk menampilkan data dari database blog Anda:</p><div class="ipsRichText__table-wrapper"><table style="width: 752px;"><colgroup><col style="width:389px;"><col style="width:363px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Tag</p></th><th colspan="1" rowspan="1"><p>Fungsi</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;b:loop values='data:posts' var='post'&gt;</code></p></td><td colspan="1" rowspan="1"><p>Mengulang (loop) untuk setiap postingan yang ada</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;data:post.title/&gt;</code></p></td><td colspan="1" rowspan="1"><p>Menampilkan judul postingan</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;data:post.url/&gt;</code></p></td><td colspan="1" rowspan="1"><p>URL / link ke postingan</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;data:post.dateHeader/&gt;</code></p></td><td colspan="1" rowspan="1"><p>Tanggal postingan</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;data:post.author/&gt;</code></p></td><td colspan="1" rowspan="1"><p>Nama penulis</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;data:post.body/&gt;</code></p></td><td colspan="1" rowspan="1"><p>Isi lengkap postingan</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;data:post.snippets/&gt;</code></p></td><td colspan="1" rowspan="1"><p>Ringkasan/preview postingan (untuk beranda)</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;b:if cond='data:view.isPost'&gt;</code></p></td><td colspan="1" rowspan="1"><p>Kondisi: apakah ini halaman postingan tunggal?</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>&lt;b:loop values='data:post.labels'&gt;</code></p></td><td colspan="1" rowspan="1"><p>Loop untuk setiap label postingan</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>data:newerPageUrl</code></p></td><td colspan="1" rowspan="1"><p>URL halaman postingan lebih baru</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>data:olderPageUrl</code></p></td><td colspan="1" rowspan="1"><p>URL halaman postingan lebih lama</p></td></tr></tbody></table></div><hr><h2>Ringkasan</h2><div class="ipsRichText__table-wrapper"><table style="min-width: 611px;"><colgroup><col style="min-width:20px;"><col style="width:269px;"><col style="width:322px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Tahap</p></th><th colspan="1" rowspan="1"><p>Yang Dilakukan</p></th><th colspan="1" rowspan="1"><p>Hasilnya</p></th></tr><tr><td colspan="1" rowspan="1"><p>1</p></td><td colspan="1" rowspan="1"><p>Kerangka HTML dasar</p></td><td colspan="1" rowspan="1"><p>Halaman terbaca, judul dari nama blog</p></td></tr><tr><td colspan="1" rowspan="1"><p>2</p></td><td colspan="1" rowspan="1"><p>Header + b:section</p></td><td colspan="1" rowspan="1"><p>Area header gelap, bisa tambah widget nama blog</p></td></tr><tr><td colspan="1" rowspan="1"><p>3</p></td><td colspan="1" rowspan="1"><p>Footer + placeholder tengah</p></td><td colspan="1" rowspan="1"><p>Blog punya kepala &amp; kaki</p></td></tr><tr><td colspan="1" rowspan="1"><p>4</p></td><td colspan="1" rowspan="1"><p>Sidebar + flexbox layout</p></td><td colspan="1" rowspan="1"><p>Dua kolom: konten &amp; sidebar</p></td></tr><tr><td colspan="1" rowspan="1"><p>5</p></td><td colspan="1" rowspan="1"><p>Widget Blog1</p></td><td colspan="1" rowspan="1"><p>Postingan asli tampil lengkap</p></td></tr></tbody></table></div><hr><h2>Langkah Selanjutnya</h2><p>Setelah template berjalan, Anda bisa mengembangkannya lebih lanjut:</p><ul><li><p><strong>Ubah warna</strong> — ganti nilai <code>#2c3e50</code> dan <code>#e74c3c</code> dengan warna pilihan Anda</p></li><li><p><strong>Ganti font</strong> — tambahkan Google Fonts di dalam <code>&lt;head&gt;</code></p></li><li><p><strong>Sempurnakan responsif</strong> — uji di berbagai ukuran layar</p></li><li><p><strong>Tambah SEO</strong> — tambahkan meta description dan Open Graph tags</p></li><li><p><strong>Dark mode</strong> — tambahkan CSS <code>@media (prefers-color-scheme: dark)</code></p></li><li><p><strong>Komentar</strong> — tambahkan widget komentar di bawah <code>&lt;data:post.body/&gt;</code></p></li></ul><hr><p><em>Tutorial ini dibuat untuk pemula yang ingin memahami template Blogger dari dalam. Selamat bereksperimen!</em></p>]]></description><guid isPermaLink="false">5</guid><pubDate>Sun, 21 Jun 2026 00:45:49 +0000</pubDate></item><item><title>Tutorial: Membuat Cover Forum Style IPS untuk XenForo</title><link>https://lalu.pro/articles/forum-software/tutorial-membuat-cover-forum-style-ips-untuk-xenforo-r4/</link><description><![CDATA[
<p><img src="https://lalu.pro/uploads/monthly_2026_07/Membuat_Cover_Forum_Style_IPS_untuk_XenForo.avif.6cad68ff1a2416fdde711d4e309d6a42.avif" /></p>
<p>Panduan lengkap membangun tampilan <strong>cover forum</strong> dengan layout 2 kolom (gambar + info di kiri, daftar thread terbaru di kanan) — mirip gaya Invision Community / IPS, tapi natif di XenForo. Tanpa beli style komersial.</p><p><strong>Estimasi waktu:</strong> 30–45 menit <strong>Tingkat:</strong> Menengah (perlu nyaman edit file PHP via FTP dan template XenForo)</p><p>Konsep tampilan akhir: cover di kiri, daftar thread terbaru di kanan, border atas warna berbeda per kategori.</p><p>Lihat demo: <a rel="external nofollow" href="https://forum.intutekno.com/">https://forum.intutekno.com/</a></p><hr><h2>1. Yang Akan Dibuat</h2><p>Setiap forum di halaman utama akan menampilkan:</p><p><strong>Kolom kiri (30% lebar):</strong></p><ul><li><p>Cover image 3:2 (SVG/WebP/JPG)</p></li><li><p>Judul forum (link ke forum)</p></li><li><p>Deskripsi forum</p></li><li><p>Statistik (jumlah threads &amp; messages)</p></li></ul><p><strong>Kolom kanan (70% lebar):</strong></p><ul><li><p>5 thread terbaru dengan avatar, judul, username, tanggal, jumlah replies</p></li><li><p>Atau ikon + pesan "Belum ada thread" kalau forum masih kosong</p></li></ul><p><strong>Bonus:</strong> Border-top tiap card forum bisa diwarnai berbeda berdasarkan kategori induk-nya.</p><hr><h2>2. Prasyarat</h2><ul><li><p>XenForo 2.3.x (panduan ini diuji di 2.3.10)</p></li><li><p>Akses <strong>AdminCP</strong></p></li><li><p>Akses <strong>FTP/File Manager</strong> ke server (untuk upload PHP files)</p></li><li><p>Akses <strong>cPanel</strong> atau setara untuk backup database (opsional tapi disarankan)</p></li><li><p>Pemahaman dasar HTML/CSS akan membantu</p></li></ul><hr><h2>3. Backup Dulu</h2><p>Sebelum apa pun, lakukan dua backup:</p><p><strong>Backup database:</strong></p><ul><li><p>cPanel → phpMyAdmin → pilih database forum → Export → Quick → Go</p></li></ul><p><strong>Backup file:</strong></p><ul><li><p>File Manager cPanel → kompres folder forum jadi <code>.zip</code> → download</p></li></ul><p>Kalau ada yang salah saat ngoprek, kita bisa balik ke kondisi awal.</p><hr><h2>4. Aktifkan Development Mode</h2><p>Buka <code>/src/config.php</code> lewat File Manager atau FTP. Tambahkan dua baris ini di paling bawah:</p><pre spellcheck="" class="ipsCode language-php" data-language="PHP"><code>$config['development']['enabled'] = true;
$config['development']['defaultAddOn'] = 'Intutekno/CoverForum';
</code></pre><p>Ganti <code>Intutekno/CoverForum</code> sesuai nama yang Anda inginkan (format: <code>VendorName/AddOnName</code>). Setelah save, refresh AdminCP — menu <strong>Development</strong> dan <strong>Add-ons</strong> akan muncul di sidebar.</p><hr><h2>5. Bikin Add-on PHP</h2><p>Add-on ini bertugas menjalankan satu fungsi: mengambil 5 thread terbaru dari sebuah forum dan menyediakannya untuk dipakai di template.</p><h3>5.1. Buat folder add-on</h3><p>Via FTP, buat struktur folder:</p><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>/src/addons/Intutekno/CoverForum/
</code></pre><h3>5.2. Buat file <code>addon.json</code></h3><p>Isi:</p><pre spellcheck="" class="ipsCode language-json" data-language="JSON"><code>{
    "title": "Intutekno Cover Forum",
    "version_string": "1.0.0",
    "version_id": 1000070,
    "dev": "Intutekno",
    "supported": true
}
</code></pre><h3>5.3. Buat file <code>Listener.php</code></h3><pre spellcheck="" class="ipsCode language-php" data-language="PHP"><code>&lt;?php
namespace Intutekno\CoverForum;

class Listener
{
    public static function templaterSetup(\XF\Container $container, \XF\Template\Templater &amp;$templater)
    {
        $templater-&gt;addFunction('latest_forum_threads',
            ['Intutekno\CoverForum\TemplateHelper', 'templaterFunction']
        );
    }
}
</code></pre><h3>5.4. Buat file <code>TemplateHelper.php</code></h3><pre spellcheck="" class="ipsCode language-php" data-language="PHP"><code>&lt;?php
namespace Intutekno\CoverForum;

class TemplateHelper
{
    public static function templaterFunction($templater, &amp;$escape, $nodeId, $limit = 5)
    {
        $escape = false;
        return self::getLatestThreads($nodeId, $limit);
    }

    public static function getLatestThreads($nodeId, $limit = 5)
    {
        $finder = \XF::finder('XF:Thread')
            -&gt;where('node_id', $nodeId)
            -&gt;where('discussion_state', 'visible')
            -&gt;where('discussion_type', '&lt;&gt;', 'redirect')
            -&gt;with('User')
            -&gt;with('Prefix')
            -&gt;order('last_post_date', 'DESC')
            -&gt;limit($limit);

        return $finder-&gt;fetch();
    }
}
</code></pre><p><strong>Struktur akhir folder:</strong></p><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>/src/addons/Intutekno/CoverForum/
├── addon.json
├── Listener.php
└── TemplateHelper.php
</code></pre><hr><h2>6. Install Add-on</h2><p>Di AdminCP:</p><ol><li><p>Klik menu <strong>Add-ons</strong> di sidebar</p></li><li><p>Cari <strong>"Intutekno Cover Forum"</strong> di list</p></li><li><p>Klik tombol <strong>Install</strong> di sampingnya</p></li><li><p>Confirm</p></li></ol><p>Status add-on harus jadi <strong>Active</strong> (toggle hijau).</p><hr><h2>7. Register Code Event Listener</h2><p>Ini langkah yang menghubungkan add-on PHP kita ke template engine XenForo.</p><ol><li><p>AdminCP → <strong>Development</strong> → <strong>Code event listeners</strong></p></li><li><p>Klik <strong>+ Add code event listener</strong> di pojok kanan atas</p></li><li><p>Isi form:</p></li></ol><div class="ipsRichText__table-wrapper"><table style="width: 641px;"><colgroup><col style="width:223px;"><col style="width:418px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Field</p></th><th colspan="1" rowspan="1"><p>Isi</p></th></tr><tr><td colspan="1" rowspan="1"><p><strong>Listen to event</strong></p></td><td colspan="1" rowspan="1"><p><code>templater_setup</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Event hint</strong></p></td><td colspan="1" rowspan="1"><p><em>(kosongkan)</em></p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Execute callback: Class</strong></p></td><td colspan="1" rowspan="1"><p><code>Intutekno\CoverForum\Listener</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Execute callback: Method</strong></p></td><td colspan="1" rowspan="1"><p><code>templaterSetup</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Callback execution order</strong></p></td><td colspan="1" rowspan="1"><p><code>10</code> (default)</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Enable callback execution</strong></p></td><td colspan="1" rowspan="1"><p><span class="ipsEmoji" title="">✅</span> centang</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Description</strong></p></td><td colspan="1" rowspan="1"><p><code>Register latest_forum_threads function</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Add-on</strong></p></td><td colspan="1" rowspan="1"><p><code>Intutekno Cover Forum 1.0.0</code></p></td></tr></tbody></table></div><ol start="4"><li><p>Klik <strong>Save</strong></p></li></ol><hr><h2>8. Edit Template <code>node_list_forum</code></h2><p>AdminCP → <strong>Appearance → Templates</strong> → cari template <code>node_list_forum</code> → <strong>Edit</strong>.</p><p><strong>Ganti seluruh isi template</strong> dengan kode berikut:</p><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>&lt;xf:macro id="depth1" arg-node="!" arg-extras="!" arg-children="!" arg-childExtras="!" arg-depth="1"&gt;
	&lt;div class="block"&gt;
		&lt;div class="block-container"&gt;
			&lt;div class="block-body"&gt;
				&lt;xf:macro id="forum"
					arg-node="{$node}"
					arg-extras="{$extras}"
					arg-children="{$children}"
					arg-childExtras="{$childExtras}"
					arg-depth="{$depth}" /&gt;
			&lt;/div&gt;
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/xf:macro&gt;

&lt;xf:macro id="depth2" arg-node="!" arg-extras="!" arg-children="!" arg-childExtras="!" arg-depth="1"&gt;
	&lt;xf:macro id="forum"
		arg-node="{$node}"
		arg-extras="{$extras}"
		arg-children="{$children}"
		arg-childExtras="{$childExtras}"
		arg-depth="{$depth}" /&gt;
&lt;/xf:macro&gt;

&lt;xf:macro id="depthN" arg-node="!" arg-extras="!" arg-children="!" arg-childExtras="!" arg-depth="1"&gt;
	&lt;li&gt;
		&lt;a href="{{ link('forums', $node) }}" class="subNodeLink subNodeLink--forum {{ $extras.hasNew ? 'subNodeLink--unread' : '' }}"&gt;
			&lt;xf:fa icon="{{ $node.Data.TypeHandler.getTypeIconClass() ?: 'fa-comments' }}" class="subNodeLink-icon" /&gt;{$node.title}
		&lt;/a&gt;
		&lt;xf:macro id="forum_list::sub_node_list"
			arg-children="{$children}"
			arg-childExtras="{$childExtras}"
			arg-depth="{{ $depth + 1 }}" /&gt;
	&lt;/li&gt;
&lt;/xf:macro&gt;

&lt;xf:macro id="forum"
	arg-node="!"
	arg-extras="!"
	arg-children="!"
	arg-childExtras="!"
	arg-depth="!"
	arg-chooseName=""
	arg-bonusInfo=""&gt;

	&lt;xf:if is="$depth == 1 OR $depth == 2"&gt;

		&lt;!-- ===== COVER STYLE (forum utama) ===== --&gt;
		&lt;div class="node node--id{$node.node_id} node--depth{$depth} node--forum intuteknoCover intuteknoCover--cat{$node.parent_node_id} {{ $extras.hasNew ? 'node--unread' : 'node--read' }}"&gt;
			&lt;div class="intuteknoCover-body"&gt;

				&lt;div class="intuteknoCover-side"&gt;
					&lt;a href="{{ link('forums', $node) }}" class="intuteknoCover-img"&gt;
						&lt;img src="/data/forum_covers/{$node.node_id}.svg"
							 onerror="this.src='/data/forum_covers/default.svg'"
							 alt="{$node.title}" loading="lazy" /&gt;
					&lt;/a&gt;
					&lt;h3 class="intuteknoCover-title"&gt;
						&lt;a href="{{ link('forums', $node) }}"&gt;{$node.title}&lt;/a&gt;
					&lt;/h3&gt;
					&lt;xf:if is="$node.description"&gt;
						&lt;div class="intuteknoCover-desc"&gt;{$node.description|raw}&lt;/div&gt;
					&lt;/xf:if&gt;
					&lt;div class="intuteknoCover-stats"&gt;
						&lt;span&gt;&lt;b&gt;{{ phrase('threads') }}:&lt;/b&gt; {$extras.discussion_count|number_short(1)}&lt;/span&gt;
						&lt;span&gt;&lt;b&gt;{{ phrase('messages') }}:&lt;/b&gt; {$extras.message_count|number_short(1)}&lt;/span&gt;
					&lt;/div&gt;
				&lt;/div&gt;

				&lt;div class="intuteknoCover-threads"&gt;
					&lt;xf:set var="$latest" value="{{ latest_forum_threads($node.node_id, 5) }}" /&gt;
					&lt;xf:if is="$latest is not empty"&gt;
						&lt;ul class="intuteknoThread-list"&gt;
							&lt;xf:foreach loop="$latest" value="$thread"&gt;
								&lt;li class="intuteknoThread-item"&gt;
									&lt;xf:avatar user="{$thread.User}" size="xxs" defaultname="{$thread.username}" class="intuteknoThread-avatar" /&gt;
									&lt;div class="intuteknoThread-main"&gt;
										&lt;div class="intuteknoThread-title"&gt;
											&lt;xf:if is="$thread.prefix_id"&gt;{{ prefix('thread', $thread) }}&lt;/xf:if&gt;
											&lt;a href="{{ link('threads', $thread) }}"&gt;{$thread.title}&lt;/a&gt;
										&lt;/div&gt;
										&lt;div class="intuteknoThread-meta"&gt;
											&lt;a href="{{ link('members', $thread.User) }}" class="username"&gt;{$thread.username}&lt;/a&gt;
											&lt;span&gt;·&lt;/span&gt;
											&lt;xf:date time="{$thread.last_post_date}" /&gt;
											&lt;span&gt;·&lt;/span&gt;
											&lt;span&gt;&lt;xf:fa icon="fa-comment-alt" /&gt; {$thread.reply_count|number_short(1)}&lt;/span&gt;
										&lt;/div&gt;
									&lt;/div&gt;
								&lt;/li&gt;
							&lt;/xf:foreach&gt;
						&lt;/ul&gt;
					&lt;xf:else /&gt;
						&lt;div class="intuteknoThread-empty"&gt;
							&lt;xf:fa icon="fa-comments" /&gt;
							&lt;span&gt;Belum ada thread di forum ini&lt;/span&gt;
						&lt;/div&gt;
					&lt;/xf:if&gt;
				&lt;/div&gt;

			&lt;/div&gt;
		&lt;/div&gt;

	&lt;xf:else /&gt;

		&lt;!-- ===== ORIGINAL STYLE (sub-forum, depth &gt;= 3) ===== --&gt;
		&lt;div class="node node--id{$node.node_id} node--depth{$depth} node--forum {{ $extras.hasNew ? 'node--unread' : 'node--read' }}"&gt;
			&lt;div class="node-body"&gt;
				&lt;span class="node-icon" aria-hidden="true"&gt;
					&lt;xf:fa icon="{{ $node.Data.TypeHandler.getTypeIconClass() ?: 'fa-comments' }}" /&gt;
				&lt;/span&gt;
				&lt;div class="node-main js-nodeMain"&gt;
					&lt;xf:if is="$chooseName"&gt;
						&lt;xf:checkbox standalone="true"&gt;
							&lt;xf:option labelclass="u-pullRight" class="js-chooseItem" name="{$chooseName}[]" value="{$node.node_id}" /&gt;
						&lt;/xf:checkbox&gt;
					&lt;/xf:if&gt;

					&lt;xf:set var="$descriptionDisplay" value="{{ property('nodeListDescriptionDisplay') }}" /&gt;
					&lt;h3 class="node-title"&gt;
						&lt;a href="{{ link('forums', $node) }}" data-xf-init="{{ $descriptionDisplay == 'tooltip' ? 'element-tooltip' : '' }}" data-shortcut="node-description"&gt;{$node.title}&lt;/a&gt;
					&lt;/h3&gt;
					&lt;xf:if is="$descriptionDisplay != 'none' &amp;&amp; $node.description"&gt;
						&lt;div class="node-description {{ $descriptionDisplay == 'tooltip' ? 'node-description--tooltip js-nodeDescTooltip' : '' }}"&gt;{$node.description|raw}&lt;/div&gt;
					&lt;/xf:if&gt;

					&lt;div class="node-meta"&gt;
						&lt;xf:if is="!{$extras.privateInfo}"&gt;
							&lt;div class="node-statsMeta"&gt;
								&lt;dl class="pairs pairs--inline"&gt;
									&lt;dt&gt;{{ phrase('threads') }}&lt;/dt&gt;
									&lt;dd&gt;{$extras.discussion_count|number_short(1)}&lt;/dd&gt;
								&lt;/dl&gt;
								&lt;dl class="pairs pairs--inline"&gt;
									&lt;dt&gt;{{ phrase('messages') }}&lt;/dt&gt;
									&lt;dd&gt;{$extras.message_count|number_short(1)}&lt;/dd&gt;
								&lt;/dl&gt;
							&lt;/div&gt;
						&lt;/xf:if&gt;

						&lt;xf:if is="$depth == 2 AND property('nodeListSubDisplay') == 'menu'"&gt;
							&lt;xf:macro id="forum_list::sub_nodes_menu"
								arg-children="{$children}"
								arg-childExtras="{$childExtras}"
								arg-depth="{{ $depth + 1 }}" /&gt;
						&lt;/xf:if&gt;
					&lt;/div&gt;

					&lt;xf:if is="$depth == 2 AND property('nodeListSubDisplay') == 'flat'"&gt;
						&lt;xf:macro id="forum_list::sub_nodes_flat"
							arg-children="{$children}"
							arg-childExtras="{$childExtras}"
							arg-depth="{{ $depth + 1 }}" /&gt;
					&lt;/xf:if&gt;

					&lt;xf:if is="$bonusInfo is not empty"&gt;
						&lt;div class="node-bonus"&gt;{$bonusInfo}&lt;/div&gt;
					&lt;/xf:if&gt;
				&lt;/div&gt;

				&lt;xf:if is="!{$extras.privateInfo}"&gt;
					&lt;div class="node-stats"&gt;
						&lt;dl class="pairs pairs--rows"&gt;
							&lt;dt&gt;{{ phrase('threads') }}&lt;/dt&gt;
							&lt;dd&gt;{$extras.discussion_count|number_short(1)}&lt;/dd&gt;
						&lt;/dl&gt;
						&lt;dl class="pairs pairs--rows"&gt;
							&lt;dt&gt;{{ phrase('messages') }}&lt;/dt&gt;
							&lt;dd&gt;{$extras.message_count|number_short(1)}&lt;/dd&gt;
						&lt;/dl&gt;
					&lt;/div&gt;
				&lt;/xf:if&gt;

				&lt;div class="node-extra"&gt;
					&lt;xf:if is="{$extras.privateInfo}"&gt;
						&lt;span class="node-extra-placeholder"&gt;{{ phrase('private') }}&lt;/span&gt;
					&lt;xf:elseif is="{$extras.LastThread}" /&gt;
						&lt;div class="node-extra-icon"&gt;
							&lt;xf:if is="$xf.visitor.isIgnoring($extras.last_post_user_id)"&gt;
								&lt;xf:avatar user="{{ null }}" size="xs" /&gt;
							&lt;xf:else /&gt;
								&lt;xf:avatar user="{$extras.LastPostUser}" defaultname="{$extras.last_post_username}" size="xs" /&gt;
							&lt;/xf:if&gt;
						&lt;/div&gt;
						&lt;div class="node-extra-row"&gt;
							&lt;xf:if is="$extras.LastThread.isUnread()"&gt;
								&lt;a href="{{ link('threads/unread', $extras.LastThread) }}" class="node-extra-title" title="{$extras.LastThread.title}"&gt;{{ prefix('thread', $extras.LastThread) }}{$extras.LastThread.title}&lt;/a&gt;
							&lt;xf:else /&gt;
								&lt;a href="{{ link('threads/post', $extras.LastThread, {'post_id': $extras.last_post_id}) }}" class="node-extra-title" title="{$extras.LastThread.title}"&gt;{{ prefix('thread', $extras.LastThread) }}{$extras.LastThread.title}&lt;/a&gt;
							&lt;/xf:if&gt;
						&lt;/div&gt;
						&lt;div class="node-extra-row"&gt;
							&lt;ul class="listInline listInline--bullet"&gt;
								&lt;li&gt;&lt;xf:date time="{$extras.last_post_date}" class="node-extra-date" /&gt;&lt;/li&gt;
								&lt;xf:if is="$xf.visitor.isIgnoring($extras.last_post_user_id)"&gt;
									&lt;li class="node-extra-user"&gt;{{ phrase('ignored_member') }}&lt;/li&gt;
								&lt;xf:else /&gt;
									&lt;li class="node-extra-user"&gt;&lt;xf:username user="{$extras.LastPostUser}" defaultname="{$extras.last_post_username}" /&gt;&lt;/li&gt;
								&lt;/xf:if&gt;
							&lt;/ul&gt;
						&lt;/div&gt;
					&lt;xf:else /&gt;
						&lt;span class="node-extra-placeholder"&gt;{{ phrase('none') }}&lt;/span&gt;
					&lt;/xf:if&gt;
				&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;

	&lt;/xf:if&gt;

	&lt;xf:if is="{$depth} == 1"&gt;
		&lt;xf:macro id="forum_list::node_list"
			arg-children="{$children}"
			arg-extras="{$childExtras}"
			arg-depth="{{ $depth + 1 }}" /&gt;
	&lt;/xf:if&gt;
&lt;/xf:macro&gt;
</code></pre><p><strong>Save</strong>.</p><blockquote class="ipsQuote" cite="" data-ipsquote=""><div class="ipsQuote_contents" data-ipstruncate=""><p><strong>Penjelasan:</strong> Conditional <code>$depth == 1 OR $depth == 2</code> membuat cover style berlaku untuk forum di root maupun di dalam kategori. Sub-forum (depth ≥ 3) tetap pakai layout original supaya rapi.</p></div></blockquote><hr><h2>9. Tambahkan CSS</h2><p>AdminCP → <strong>Appearance → Style properties → Extra LESS template</strong> → tambahkan blok ini:</p><pre spellcheck="" class="ipsCode language-less" data-language="LESS"><code>/* ============================================
   Intutekno Cover Forum Style
   ============================================ */

.intuteknoCover {
    background: @xf-contentBg;
    border: 1px solid @xf-borderColor;
    border-radius: 8px;
    margin-bottom: 12px;
    overflow: hidden;
    border-top: 5px solid #06b6d4; /* default fallback */
}

.intuteknoCover-body {
    display: flex;
    gap: 0;
}

/* === LEFT: Cover + Info === */
.intuteknoCover-side {
    flex: 0 0 30%;
    max-width: 30%;
    padding: 14px;
    border-right: 1px solid @xf-borderColor;
    background: rgba(0,0,0,0.02);
}

.intuteknoCover-img {
    display: block;
    width: 100%;
    aspect-ratio: 3 / 2;
    overflow: hidden;
    border-radius: 6px;
    margin-bottom: 12px;
}

.intuteknoCover-img img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform .3s ease;
}

.intuteknoCover-img:hover img {
    transform: scale(1.05);
}

.intuteknoCover-title {
    margin: 0 0 8px;
    font-size: 17px;
    font-weight: 700;
}

.intuteknoCover-title a {
    color: @xf-linkColor;
}

.intuteknoCover-desc {
    font-size: 13px;
    color: @xf-textColorMuted;
    line-height: 1.5;
    margin-bottom: 12px;
}

.intuteknoCover-stats {
    display: flex;
    gap: 14px;
    padding-top: 10px;
    border-top: 2px solid #e74c3c;
    font-size: 13px;
}

.intuteknoCover-stats b {
    color: @xf-textColor;
}

/* === RIGHT: Daftar Thread === */
.intuteknoCover-threads {
    flex: 1;
    padding: 8px 14px;
}

.intuteknoThread-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.intuteknoThread-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-bottom: 1px solid @xf-borderColorLight;
}

.intuteknoThread-item:last-child {
    border-bottom: 0;
}

.intuteknoThread-item .intuteknoThread-avatar {
    flex: 0 0 32px;
    width: 32px;
    height: 32px;
    margin: 0;
}

.intuteknoThread-item .intuteknoThread-avatar.avatar img,
.intuteknoThread-item .intuteknoThread-avatar img {
    width: 32px !important;
    height: 32px !important;
    border-radius: 50%;
}

.intuteknoThread-item .intuteknoThread-avatar.avatar {
    font-size: 14px !important;
    line-height: 32px !important;
}

.intuteknoThread-main {
    flex: 1;
    min-width: 0;
}

.intuteknoThread-title {
    font-size: 14px;
    margin-bottom: 3px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 500;
}

.intuteknoThread-meta {
    font-size: 12px;
    color: @xf-textColorMuted;
    display: flex;
    gap: 6px;
    align-items: center;
    flex-wrap: wrap;
}

.intuteknoThread-meta .username {
    font-weight: 600;
    color: @xf-linkColor;
}

.intuteknoThread-empty {
    padding: 50px 20px;
    text-align: center;
    color: @xf-textColorMuted;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    height: 100%;
    justify-content: center;
}

.intuteknoThread-empty .fa {
    font-size: 36px;
    opacity: 0.25;
}

/* === Responsive Mobile === */
@media (max-width: 768px) {
    .intuteknoCover-body {
        flex-direction: column;
    }
    .intuteknoCover-side,
    .intuteknoCover-threads {
        flex: 1 1 100%;
        max-width: 100%;
        border-right: 0;
        border-bottom: 1px solid @xf-borderColor;
    }
}
</code></pre><p><strong>Save</strong>.</p><hr><h2>10. Setup Cover Image</h2><h3>10.1. Buat folder cover</h3><p>Via File Manager cPanel atau FTP, buat folder:</p><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>/data/forum_covers/
</code></pre><p>Set permission <code>755</code>.</p><h3>10.2. Cari node_id setiap forum</h3><p>AdminCP → <strong>Forums</strong> → klik nama forum → lihat URL:</p><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>.../admin.php?nodes/nama-forum.5/edit
                              ↑
                          node_id = 5
</code></pre><h3>10.3. Upload cover image</h3><p>Upload file SVG/WebP/JPG ke <code>/data/forum_covers/</code> dengan nama sesuai node_id:</p><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>/data/forum_covers/
├── default.svg     ← fallback wajib
├── 5.svg           ← forum dengan node_id 5
├── 6.svg
├── 7.svg
└── ...
</code></pre><p><strong>Spesifikasi cover ideal:</strong></p><ul><li><p>Aspect ratio <strong>3:2</strong></p></li><li><p>Ukuran <strong>600×400 px</strong></p></li><li><p>Format <strong>SVG</strong> (paling ringan dan scalable) atau <strong>WebP</strong></p></li></ul><blockquote class="ipsQuote" cite="" data-ipsquote=""><div class="ipsQuote_contents" data-ipstruncate=""><p><strong>Catatan:</strong> Template di langkah 8 sudah pakai ekstensi <code>.svg</code>. Kalau Anda pakai <code>.webp</code> atau <code>.jpg</code>, ubah dua tempat di template dari <code>.svg</code> menjadi ekstensi yang Anda pakai.</p></div></blockquote><hr><h2>11. Customize Border per Kategori</h2><p>Border-top tiap card forum bisa diwarnai berbeda berdasarkan <strong>kategori induk</strong>-nya. Berkat class <code>intuteknoCover--cat{X}</code> yang otomatis disisipkan di langkah 8, kita tinggal target via CSS.</p><h3>11.1. Cari node_id kategori</h3><p>Sama seperti forum: AdminCP → Forums → klik kategori → lihat node_id di URL.</p><h3>11.2. Tambahkan rules CSS</h3><p>Tambahkan ke Extra LESS:</p><pre spellcheck="" class="ipsCode language-less" data-language="LESS"><code>/* === Border Color per Kategori === */
.intuteknoCover--cat3 { border-top-color: #3b82f6; } /* Tutorial - biru */
.intuteknoCover--cat4 { border-top-color: #f59e0b; } /* Info - amber */
.intuteknoCover--cat5 { border-top-color: #10b981; } /* hijau */
.intuteknoCover--cat6 { border-top-color: #ec4899; } /* pink */
/* tambahkan sesuai kategori yang dimiliki */
</code></pre><p>Sesuaikan angka di <code>--cat{X}</code> dengan node_id kategori, dan warna sesuai branding masing-masing.</p><h3>Save dan refresh</h3><p>Setelah semua perubahan CSS:</p><ol><li><p>Save Extra LESS</p></li><li><p><strong>Tools → Rebuild caches</strong> → centang semua → Rebuild</p></li><li><p>Buka homepage forum → <strong>Ctrl+F5</strong></p></li></ol><hr><h2>12. Troubleshooting</h2><h3>Error <code>Function "latest_forum_threads" is not callable</code></h3><p>Listener belum aktif atau salah class path. Cek:</p><ol><li><p><strong>Add-ons</strong> — add-on Status harus Active</p></li><li><p><strong>Code event listeners</strong> — entry untuk <code>templater_setup</code> harus enabled, Class dan Method harus persis sama dengan file PHP</p></li><li><p><strong>Tools → Rebuild caches</strong> → centang semua</p></li></ol><h3>Cover tidak muncul, ada celah putih di kiri</h3><p>Berarti gambar gagal dimuat. Cek:</p><ol><li><p>File ada di <code>/data/forum_covers/</code>?</p></li><li><p>Permission folder 755, file 644?</p></li><li><p>Nama file sesuai node_id?</p></li><li><p>Path di template sesuai ekstensi file?</p></li><li><p>Ada file <code>default.svg</code> sebagai fallback?</p></li></ol><h3>Layout tidak berubah, masih style asli</h3><ol><li><p>Pastikan template <code>node_list_forum</code> ter-save dengan kode lengkap di langkah 8</p></li><li><p><strong>Rebuild templates</strong> wajib</p></li><li><p><strong>Ctrl+F5</strong> untuk bypass cache browser</p></li><li><p>Cek dengan inspect element — class <code>intuteknoCover</code> harus muncul di div</p></li></ol><h3>Sub-forum jadi style cover juga (tidak diinginkan)</h3><p>Cek conditional di template:</p><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>&lt;xf:if is="$depth == 1 OR $depth == 2"&gt;
</code></pre><p>Sub-forum biasanya depth ≥ 3, jadi seharusnya tidak ter-cover. Kalau forum Anda strukturnya berbeda, sesuaikan condition.</p><h3>Avatar ada celah ke kiri</h3><p>Pastikan template pakai sintaks ini (avatar inline, bukan dibungkus <code>&lt;a&gt;</code>):</p><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>&lt;xf:avatar user="{$thread.User}" size="xxs" defaultname="{$thread.username}" class="intuteknoThread-avatar" /&gt;
</code></pre><h3>Class <code>intuteknoCover--cat{id}</code> tidak muncul di HTML</h3><p>Berarti template belum ter-update. Edit ulang, save, dan rebuild templates.</p><hr><h2>13. Penutup</h2><p>Setelah semua langkah di atas, halaman utama forum akan menampilkan card cover untuk setiap forum dengan layout 2 kolom dan border atas warna per kategori.</p><h3>Pengembangan Selanjutnya</h3><p>Beberapa peningkatan yang bisa dilakukan kapan-kapan:</p><ul><li><p><strong>Caching</strong> thread terbaru lewat XF registry supaya tidak query 5 thread per forum setiap homepage di-load</p></li><li><p><strong>Custom field</strong> untuk upload cover via AdminCP (tanpa FTP)</p></li><li><p><strong>Hover effect</strong> halus di card dan row thread</p></li><li><p><strong>Lazy load</strong> cover image dengan blur placeholder</p></li></ul><h3>Struktur File Akhir</h3><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>/src/addons/Intutekno/CoverForum/
├── addon.json
├── Listener.php
└── TemplateHelper.php

/data/forum_covers/
├── default.svg
├── 5.svg
├── 6.svg
└── ... (sesuai jumlah forum)

AdminCP → Templates: node_list_forum (modified)
AdminCP → Style properties → Extra LESS (modified)
AdminCP → Code event listeners: templater_setup → Listener::templaterSetup
</code></pre><hr><p><strong>Tutorial dibuat berdasarkan implementasi nyata di forum.intutekno.com</strong></p><p>Lisensi: silakan adaptasi dan bagikan ulang. Kalau bermanfaat, sebutkan sumber sebagai <em>amal jariah</em> — pengetahuan yang terus bermanfaat.</p>]]></description><guid isPermaLink="false">4</guid><pubDate>Sun, 17 May 2026 08:30:50 +0000</pubDate></item><item><title>Cara Membuat PageSpeed Tester Sendiri untuk Blog</title><link>https://lalu.pro/articles/blogger/cara-membuat-pagespeed-tester-sendiri-untuk-blog-r2/</link><description><![CDATA[
<p><img src="https://lalu.pro/uploads/monthly_2026_05/pagespeed_tester_blogger.avif.543ff463e184ac645f0e7c56a4e47223.avif" /></p>
<p>Tutorial lengkap membuat tool PageSpeed Tester yang aman dan gratis untuk blog Blogger Anda. Tool ini menggunakan <strong>Google PageSpeed Insights API</strong> dengan <strong>Cloudflare Worker</strong> sebagai proxy untuk menyembunyikan API key.</p><h2>Apa yang Akan Anda Buat</h2><p>Tool yang bisa:</p><ul><li><p>Cek skor Performance, Accessibility, Best Practices, dan SEO halaman manapun</p></li><li><p>Tampilkan Core Web Vitals (FCP, LCP, TBT, CLS, dll)</p></li><li><p>Berikan rekomendasi perbaikan diurutkan berdasarkan impact</p></li><li><p>Cache hasil 1 jam untuk hemat quota</p></li><li><p>100% gratis (Google API quota gratis cukup untuk blog normal)</p></li></ul><h2>Mengapa Butuh Cloudflare Worker?</h2><p>API Google butuh <strong>API key</strong> untuk akses unlimited. Kalau key disimpan langsung di JavaScript blog Anda:</p><ul><li><p>Key terlihat di <strong>view-source</strong> dan <strong>Network tab</strong> browser</p></li><li><p>Siapa saja bisa copy key Anda</p></li><li><p>Pakai untuk panggil API mereka sendiri</p></li><li><p>Quota Anda habis, atau worse — kena billing kalau key Anda attach ke service berbayar</p></li></ul><p><strong>Solusi:</strong> Simpan key di <strong>Cloudflare Worker</strong> (server). Tool browser hanya panggil Worker, Worker yang panggil Google API.</p><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>Browser (tool Anda) → Cloudflare Worker (key di sini) → Google PageSpeed API
</code></pre><p>Key tidak pernah keluar dari server. Aman.</p><h2>Prasyarat</h2><p>Sebelum mulai, siapkan:</p><ol><li><p><strong>Akun Google</strong> (untuk Google Cloud Console)</p></li><li><p><strong>Akun Cloudflare</strong> (gratis, daftar di <a rel="external nofollow" href="https://cloudflare.com/">cloudflare.com</a>)</p></li><li><p><strong>Blog Blogger</strong> Anda</p></li></ol><p>Tidak perlu credit card untuk setup ini.</p><hr><h2>STEP 1: Dapatkan Google API Key</h2><h3>1.1 Aktifkan PageSpeed Insights API</h3><ol><li><p>Buka <a rel="external nofollow" href="https://console.cloud.google.com/">Google Cloud Console</a></p></li><li><p>Buat project baru (atau pilih yang sudah ada)</p></li><li><p>Di sidebar kiri: <strong>APIs &amp; Services</strong> → <strong>Library</strong></p></li><li><p>Search: <strong>"PageSpeed Insights API"</strong></p></li><li><p>Klik hasil, lalu <strong>Enable</strong></p></li></ol><h3>1.2 Buat API Key</h3><ol><li><p><strong>APIs &amp; Services</strong> → <strong>Credentials</strong></p></li><li><p><strong>Create Credentials</strong> → <strong>API key</strong></p></li><li><p>Key baru muncul. <strong>Copy key tersebut</strong> dan simpan sementara di Notepad/text editor</p></li><li><p>Klik <strong>Edit API key</strong> untuk setup restriction</p></li></ol><h3>1.3 Set Restriction (Sangat Penting!)</h3><p>Tanpa restriction, key bisa dipakai siapa saja yang punya key tersebut.</p><p><strong>Application restrictions:</strong></p><ul><li><p>Pilih <strong>Websites</strong></p></li><li><p>Klik <strong>Add an item</strong></p></li><li><p>Isi domain Anda:</p><ul><li><p><code>*.laluabdrahman.com*</code> (ganti dengan domain Anda)</p></li><li><p><code>https://laluabdrahman.com/*</code></p></li><li><p><code>https://www.laluabdrahman.com/*</code></p></li></ul></li></ul><p><strong>API restrictions:</strong></p><ul><li><p>Pilih <strong>Restrict key</strong></p></li><li><p>Centang <strong>hanya</strong> "PageSpeed Insights API"</p></li><li><p>Klik <strong>Save</strong></p></li></ul><blockquote class="ipsQuote" cite="" data-ipsquote=""><div class="ipsQuote_contents" data-ipstruncate=""><p><strong>Tips keamanan:</strong> Restriction ini berarti key Anda tidak bisa dipakai dari domain lain ATAU untuk API Google lain (Maps, Translate, dll yang berbayar).</p></div></blockquote><hr><h2>STEP 2: Deploy Cloudflare Worker</h2><h3>2.1 Buat Worker Baru</h3><ol><li><p>Login ke <a rel="external nofollow" href="https://dash.cloudflare.com/">Cloudflare dashboard</a></p></li><li><p>Sidebar kiri: <strong>Workers &amp; Pages</strong></p></li><li><p>Klik <strong>Create application</strong> → <strong>Create Worker</strong></p></li><li><p>Beri nama, misal: <code>pagespeed-proxy</code></p></li><li><p>Klik <strong>Deploy</strong> (dengan default code)</p></li><li><p>Setelah deploy, klik <strong>Edit code</strong></p></li></ol><h3>2.2 Paste Kode Worker</h3><p>Hapus semua default code, paste kode berikut:</p><pre spellcheck="" class="ipsCode language-javascript" data-language="JavaScript"><code>export default {
  async fetch(request, env, ctx) {
    // CORS preflight
    if (request.method === 'OPTIONS') {
      return new Response(null, { status: 204, headers: corsHeaders() });
    }
    if (request.method !== 'GET') {
      return errorResponse('Only GET allowed', 405);
    }

    const url = new URL(request.url);
    const targetUrl = url.searchParams.get('url');
    const strategy = (url.searchParams.get('strategy') || 'mobile').toLowerCase();

    if (!targetUrl) {
      return errorResponse('Missing url parameter', 400);
    }
    if (!['mobile', 'desktop'].includes(strategy)) {
      return errorResponse('strategy must be mobile or desktop', 400);
    }

    // Validate target URL
    let target;
    try {
      target = new URL(targetUrl);
    } catch (e) {
      return errorResponse('Invalid URL format', 400);
    }
    if (!['http:', 'https:'].includes(target.protocol)) {
      return errorResponse('Only http(s) protocol allowed', 403);
    }

    // SSRF prevention
    const hostname = target.hostname.toLowerCase();
    if (['localhost', '127.0.0.1', '0.0.0.0', '::1'].includes(hostname) ||
        /^10\./.test(hostname) ||
        /^172\.(1[6-9]|2\d|3[01])\./.test(hostname) ||
        /^192\.168\./.test(hostname) ||
        /^169\.254\./.test(hostname)) {
      return errorResponse('Hostname not allowed', 403);
    }

    if (!env.GOOGLE_API_KEY) {
      return errorResponse('Worker not configured: missing GOOGLE_API_KEY secret', 500);
    }

    // Cache check
    const cacheKey = new Request(
      `https://psi-cache.internal/${strategy}/${encodeURIComponent(target.href)}`,
      { method: 'GET' }
    );
    const cache = caches.default;
    let cached = await cache.match(cacheKey);
    if (cached) {
      const newHeaders = new Headers(cached.headers);
      newHeaders.set('X-PSI-Cache', 'HIT');
      return new Response(cached.body, { status: cached.status, headers: newHeaders });
    }

    // Call Google PageSpeed API
    const apiUrl = new URL('https://www.googleapis.com/pagespeedonline/v5/runPagespeed');
    apiUrl.searchParams.set('url', target.href);
    apiUrl.searchParams.set('strategy', strategy);
    ['performance', 'accessibility', 'best-practices', 'seo'].forEach(cat =&gt; {
      apiUrl.searchParams.append('category', cat);
    });
    apiUrl.searchParams.set('key', env.GOOGLE_API_KEY);

    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() =&gt; controller.abort(), 60000);

      // GANTI Referer di bawah dengan domain blog Anda
      const apiRes = await fetch(apiUrl.toString(), {
        headers: { 'Referer': 'https://www.laluabdrahman.com/' },
        signal: controller.signal
      });
      clearTimeout(timeoutId);

      if (!apiRes.ok) {
        let errMsg = `Google API returned HTTP ${apiRes.status}`;
        try {
          const errBody = await apiRes.json();
          if (errBody.error?.message) errMsg = errBody.error.message;
        } catch (e) {}
        return errorResponse(errMsg, apiRes.status === 429 ? 429 : 502);
      }

      const data = await apiRes.json();
      const response = new Response(JSON.stringify(data), {
        status: 200,
        headers: {
          ...corsHeaders(),
          'Content-Type': 'application/json',
          'Cache-Control': 'public, max-age=3600',
          'X-PSI-Cache': 'MISS'
        }
      });

      ctx.waitUntil(cache.put(cacheKey, response.clone()));
      return response;
    } catch (err) {
      if (err.name === 'AbortError') {
        return errorResponse('PageSpeed API timeout (&gt;60s)', 504);
      }
      return errorResponse('Fetch failed: ' + err.message, 502);
    }
  }
};

function corsHeaders() {
  return {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type'
  };
}

function errorResponse(message, status) {
  return new Response(JSON.stringify({ error: message }), {
    status,
    headers: { ...corsHeaders(), 'Content-Type': 'application/json' }
  });
}
</code></pre><blockquote class="ipsQuote" cite="" data-ipsquote=""><div class="ipsQuote_contents" data-ipstruncate=""><p><strong>PENTING:</strong> Cari baris <code>'Referer': 'https://www.laluabdrahman.com/'</code> dan ganti dengan domain Anda. Ini harus match dengan restriction key di Google Cloud Console.</p></div></blockquote><p>Klik <strong>Save and deploy</strong>.</p><h3>2.3 Tambah API Key sebagai Secret</h3><p>Sekarang masukkan API key Anda ke Worker sebagai secret (encrypted):</p><ol><li><p>Di Worker → tab <strong>Settings</strong></p></li><li><p>Scroll ke section <strong>Variables and Secrets</strong></p></li><li><p>Klik <strong>+ Add</strong></p></li><li><p>Isi:</p><ul><li><p><strong>Type</strong>: <code>Secret</code> (penting! bukan Text)</p></li><li><p><strong>Variable name</strong>: <code>GOOGLE_API_KEY</code> (case-sensitive, persis seperti ini)</p></li><li><p><strong>Value</strong>: paste API key dari Step 1.2</p></li></ul></li><li><p>Klik <strong>Save</strong></p></li></ol><blockquote class="ipsQuote" cite="" data-ipsquote=""><div class="ipsQuote_contents" data-ipstruncate=""><p><strong>Catatan:</strong> Setelah secret ditambahkan, Worker mungkin perlu di-redeploy. Buka tab <strong>Deployments</strong>, kalau tidak ada deployment baru otomatis, edit kode dan save lagi (tambah satu spasi lalu hapus juga cukup).</p></div></blockquote><h3>2.4 Test Worker</h3><p>Buka di browser:</p><pre spellcheck="" class="ipsCode language-plaintext" data-language="Plain Text"><code>https://pagespeed-proxy.YOUR-NAME.workers.dev/?url=https://example.com&amp;strategy=mobile
</code></pre><p>Ganti <code>YOUR-NAME</code> dengan subdomain Worker Anda.</p><p><strong>Expected:</strong></p><ul><li><p>Tunggu 15-40 detik</p></li><li><p>Lihat JSON besar muncul dengan field <code>lighthouseResult</code></p></li><li><p>Jika ada error, baca pesan dan cek troubleshooting di bawah</p></li></ul><p>Catat URL Worker Anda — akan dipakai di Step 3.</p><hr><h2>STEP 3: Setup Tool HTML di Blogger</h2><h3>3.1 Edit Konfigurasi Tool</h3><p>Buka file <code>pagespeed-tester.html</code> Anda. Cari baris ini (sekitar baris 510):</p><pre spellcheck="" class="ipsCode language-javascript" data-language="JavaScript"><code>const WORKER_URL = '';
</code></pre><p>Ganti dengan URL Worker Anda:</p><pre spellcheck="" class="ipsCode language-javascript" data-language="JavaScript"><code>const WORKER_URL = 'https://pagespeed-proxy.YOUR-NAME.workers.dev/';
</code></pre><h3>3.2 Paste ke Blogger</h3><ol><li><p>Blogger dashboard → <strong>Pages</strong> → <strong>New Page</strong></p></li><li><p>Klik mode <strong>HTML view</strong> (bukan Compose)</p></li><li><p>Paste seluruh isi file <code>pagespeed-tester.html</code></p></li><li><p>Beri title halaman: "PageSpeed Tester"</p></li><li><p><strong>Publish</strong></p></li></ol><h3>3.3 Test Tool</h3><p>Buka halaman yang baru dipublish, lalu:</p><ol><li><p>Input URL, contoh: <code>https://example.com</code></p></li><li><p>Pilih Mobile atau Desktop</p></li><li><p>Klik <strong>Mulai Tes</strong></p></li><li><p>Tunggu 15-40 detik</p></li><li><p>Hasil muncul dengan score rings, Core Web Vitals, dan rekomendasi</p></li></ol><p><strong>Cache test:</strong></p><ul><li><p>Klik <strong>Mulai Tes</strong> lagi dengan URL dan strategy sama</p></li><li><p>Harusnya muncul <strong>&lt;1 detik</strong> dari cache</p></li><li><p>Footer berubah jadi: "✓ dari cache"</p></li></ul><hr><h2>Troubleshooting</h2><h3>Error: "Worker not configured: missing GOOGLE_API_KEY secret"</h3><p>Secret belum tersimpan atau Worker belum redeploy. Solusi:</p><ol><li><p>Cek di <strong>Settings → Variables and Secrets</strong>, pastikan ada <code>GOOGLE_API_KEY</code> (case-sensitive)</p></li><li><p>Pastikan <strong>Type-nya Secret</strong>, bukan Text</p></li><li><p>Edit kode Worker, save lagi untuk trigger redeploy</p></li></ol><h3>Error: "Requests from referer &lt;empty&gt; are blocked"</h3><p>API key Anda di-restrict ke domain, tapi Worker tidak kirim Referer header.</p><p><strong>Solusi:</strong> Pastikan di kode Worker ada baris ini di dalam <code>fetch()</code> ke Google API:</p><pre spellcheck="" class="ipsCode language-javascript" data-language="JavaScript"><code>headers: { 'Referer': 'https://www.YOUR-DOMAIN.com/' }
</code></pre><p>Domain di sini harus match dengan salah satu restriction di Google Cloud Console.</p><h3>Tombol "Mulai Tes" tidak respon</h3><p>Buka Developer Console (F12 → Console), lihat error apa yang muncul.</p><p>Cek juga:</p><ul><li><p><code>WORKER_URL</code> sudah diisi dengan URL Worker Anda</p></li><li><p>URL Worker bisa diakses langsung di browser</p></li></ul><h3>Error: "HTTP 429" (Too Many Requests)</h3><p>Rate limit dari Google. Tunggu 1-2 menit, lalu coba lagi. Atau:</p><ul><li><p>Pastikan API key sudah terkonfigurasi dengan benar</p></li><li><p>Caching seharusnya mengurangi request rate</p></li></ul><h3>Hasil PageSpeed berbeda dari pagespeed.web.dev</h3><p>Normal. Score PageSpeed bisa fluktuasi 5-10 point antar test karena:</p><ul><li><p>Variasi network condition di server Google</p></li><li><p>Random sampling dari data CrUX (Chrome User Experience Report)</p></li><li><p>Cache TTL yang berbeda</p></li></ul><p>Untuk benchmark akurat, jalankan test 3-5 kali dan ambil median.</p><hr><h2>Quota dan Biaya</h2><h3>Google PageSpeed Insights API</h3><ul><li><p><strong>25,000 query per hari</strong> gratis</p></li><li><p><strong>Cukup untuk blog normal</strong> — bahkan dengan caching off</p></li><li><p>Tidak perlu billing setup</p></li></ul><h3>Cloudflare Workers</h3><ul><li><p><strong>100,000 request per hari</strong> gratis (free tier)</p></li><li><p><strong>10ms CPU time per request</strong> (kita pakai jauh lebih sedikit)</p></li><li><p>Cache pakai Cloudflare Cache API (built-in, gratis)</p></li></ul><h3>Estimasi penggunaan blog dengan 1,000 visitor/hari</h3><p>Asumsi: 1% visitor pakai tool = 10 test/hari</p><div class="ipsRichText__table-wrapper"><table style="min-width: 60px;"><colgroup><col style="min-width:20px;"><col style="min-width:20px;"><col style="min-width:20px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Resource</p></th><th colspan="1" rowspan="1"><p>Limit</p></th><th colspan="1" rowspan="1"><p>Penggunaan</p></th></tr><tr><td colspan="1" rowspan="1"><p>Cloudflare Workers</p></td><td colspan="1" rowspan="1"><p>100k/hari</p></td><td colspan="1" rowspan="1"><p>10 req</p></td></tr><tr><td colspan="1" rowspan="1"><p>Google API</p></td><td colspan="1" rowspan="1"><p>25k/hari</p></td><td colspan="1" rowspan="1"><p>10 req</p></td></tr><tr><td colspan="1" rowspan="1"><p>Cache hit rate</p></td><td colspan="1" rowspan="1"><p>-</p></td><td colspan="1" rowspan="1"><p>~30% (URL repeat)</p></td></tr></tbody></table></div><p>Penggunaan masih jauh di bawah free tier. Aman untuk blog dengan trafik wajar.</p><hr><h2>Cara Kerja Detail</h2><h3>Arsitektur Defense in Depth</h3><p>Tool ini punya <strong>5 layer keamanan</strong>:</p><ol><li><p><strong>Key disembunyikan di Worker secret</strong> — encrypted di Cloudflare, tidak visible di dashboard setelah disimpan</p></li><li><p><strong>API restriction di Google</strong> — key hanya bisa pakai PageSpeed Insights API, bukan Maps/Translate yang berbayar</p></li><li><p><strong>Website restriction di Google</strong> — referer harus match domain Anda</p></li><li><p><strong>Worker forward referer</strong> — memungkinkan website restriction bekerja meski request datang dari Cloudflare IP</p></li><li><p><strong>SSRF prevention di Worker</strong> — block private IP dan localhost di parameter URL</p></li></ol><p>Kalau salah satu layer compromised, layer lain masih protect:</p><ul><li><p>Key bocor → masih di-restrict ke PageSpeed Insights API only</p></li><li><p>Restriction lemah → key tetap tidak visible di browser</p></li></ul><h3>Cache Strategy</h3><p>Worker pakai <strong>Cloudflare Cache API</strong> (built-in, no setup):</p><ul><li><p><strong>Cache key:</strong> <code>URL + strategy</code></p></li><li><p><strong>TTL:</strong> 1 jam (3600 detik)</p></li><li><p><strong>Storage:</strong> Cloudflare edge, automatic eviction</p></li></ul><p>Mobile dan Desktop terhitung cache terpisah. Cache di-share antar visitor — jadi kalau 100 orang test URL sama, hanya 1 yang fresh fetch, sisanya dari cache.</p><h3>Kenapa Hasil Pertama Lambat?</h3><p>Google PageSpeed API actually menjalankan Lighthouse audit di server mereka — load page, render, simulate user interaction, analyze. Itu butuh 15-40 detik. Tidak bisa diakselerasi.</p><p>Setelah cache, hasil instan karena tidak perlu re-audit.</p><hr><h2>Kesimpulan</h2><p>Anda sekarang punya tool PageSpeed Tester sendiri yang:</p><ul><li><p><strong>Gratis</strong> sampai 25k test/hari (jauh di atas kebutuhan blog normal)</p></li><li><p><strong>Aman</strong> — API key tidak pernah terlihat publik</p></li><li><p><strong>Cepat</strong> untuk repeat test (cache 1 jam)</p></li><li><p><strong>Customizable</strong> — bisa modify UI, tambah fitur, dll</p></li></ul><p>Tool ini juga jadi foundation pattern untuk tool lain yang butuh API key. Misalnya kalau Anda mau buat:</p><ul><li><p><strong>Image Alt Text Generator</strong> (pakai OpenAI/Claude API)</p></li><li><p><strong>SERP Checker</strong> (pakai SerpApi)</p></li><li><p><strong>Backlink Checker</strong> (pakai Ahrefs API)</p></li></ul><p>Polanya sama: Worker proxy + secret env variable.</p><p>Selamat mencoba!</p>]]></description><guid isPermaLink="false">2</guid><pubDate>Sun, 17 May 2026 06:25:00 +0000</pubDate></item><item><title>Tutorial Komprehensif Membuat Homepage Kustom di Invision Community 5</title><link>https://lalu.pro/articles/forum-software/tutorial-komprehensif-membuat-homepage-kustom-di-invision-community-5-r1/</link><description><![CDATA[<p>Panduan lengkap berdasarkan pengalaman membangun <strong>lalu.pro</strong> — dengan Invision Community 5.0.18.</p><p><strong>Target:</strong> Pengguna IPS 5 yang ingin membangun homepage editorial dengan data live dari berbagai aplikasi IPS (Forum, Pages, Downloads).  </p><p><strong>Tingkat:</strong> Menengah (butuh dasar HTML/CSS, tidak butuh PHP advance).</p><h2>Prasyarat</h2><p>Sebelum mulai, pastikan Anda punya:</p><p>Sebelum mulai, pastikan Anda punya:</p><p>Teknis</p><ul><li><p><strong>Invision Community 5.0.x</strong> ter-install (panduan ini di-test di 5.0.18)</p></li><li><p><strong>Aplikasi Pages</strong> aktif (wajib — ini yang memungkinkan custom homepage)</p></li><li><p><strong>AdminCP access</strong> sebagai administrator</p></li><li><p><strong>Akses cPanel atau file manager</strong> (untuk backup tema, opsional tapi sangat direkomendasikan)</p></li></ul><p>Pengetahuan</p><ul><li><p>HTML dasar (tag, attribute, nested element)</p></li><li><p>CSS dasar (selector, properties, flexbox, grid)</p></li><li><p>Konsep template tag (mirip Twig, Liquid, atau Blade — tidak perlu PHP)</p></li><li><p>Mengerti perbedaan client-side (HTML/CSS) vs server-side (PHP) rendering</p></li></ul><p>Aplikasi IPS yang Direkomendasikan untuk Live Data</p><ul><li><p><strong>Forum</strong> (untuk fetch topik diskusi)</p></li><li><p><strong>Pages</strong> dengan custom database (untuk artikel)</p></li><li><p><strong>Downloads</strong> (untuk file/resources)</p></li><li><p><strong>Calendar</strong> (untuk events — opsional)</p></li></ul><p>Persiapan Konten</p><ul><li><p>Minimal <strong>3-5 topik forum</strong> sudah dipost</p></li><li><p>Minimal <strong>3-5 artikel</strong> sudah di-publish di Pages database</p></li><li><p>Minimal <strong>2-3 file</strong> sudah di-upload di Downloads</p></li></ul><p>Tanpa konten ini, section live data akan tampak kosong saat testing.</p><h2>Konsep IPS 5 yang Wajib Dipahami</h2><p>Sebelum nyentuh kode, pahami dulu konsep-konsep ini. Banyak frustrasi muncul karena pengguna tidak tahu <strong>vocabulary</strong> IPS 5.</p><h3>1. Pages — Aplikasi Pembuat Halaman</h3><p><strong>Pages </strong>adalah aplikasi IPS yang memungkinkan Anda membuat halaman custom di luar struktur forum standar. Homepage custom Anda akan jadi sebuah Page.</p><p><strong>Path:</strong> <em>AdminCP → Pages</em></p><h3>2. Page Types</h3><p>Saat membuat Page baru, ada dua tipe utama:</p><div class="ipsRichText__table-wrapper"><table style="width: 600px;"><colgroup><col style="width:148px;"><col style="width:297px;"><col style="width:155px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Tipe</p></th><th colspan="1" rowspan="1"><p>Kapan Dipakai</p></th><th colspan="1" rowspan="1"><p>Tutorial Ini Pakai?</p></th></tr><tr><td colspan="1" rowspan="1"><p>Page Builder</p></td><td colspan="1" rowspan="1"><p>Visual drag-and-drop dengan widget</p></td><td colspan="1" rowspan="1"><p>Tidak</p></td></tr><tr><td colspan="1" rowspan="1"><p>Manual</p></td><td colspan="1" rowspan="1"><p>HTML/template tag custom penuh</p></td><td colspan="1" rowspan="1"><p>Ya</p></td></tr></tbody></table></div><p><strong>Manual</strong> memberi Anda kontrol penuh atas HTML, sementara Page Builder cocok untuk yang tidak mau repot <em>ngoding</em>.</p><h3>3. Use Suite HTML Wrapper — Setting Paling Krusial</h3><p>Ini setting yang sering bikin bingung. Ada dua mode:</p><h4>Mode A: Suite Wrapper ON (Direkomendasikan untuk Production)</h4><p><span class="ipsEmoji" title="">✅</span> Konten Anda di-wrap dalam template global IPS (header, footer, sidebar, scripts)<br><span class="ipsEmoji" title="">✅</span> Security IPS aktif (CSRF, session, auth)<br><span class="ipsEmoji" title="">✅</span> JavaScript dan PHP IPS jalan native<br><span class="ipsEmoji" title="">✅</span> Konsisten dengan halaman lain (forum, artikel detail)<br><span class="ipsEmoji" title="">❌</span> Tidak bisa custom &lt;head&gt;, &lt;body&gt;, atau full HTML</p><p><strong>Kapan pakai:</strong> Saat Anda mau pakai header &amp; footer bawaan IPS, dan hanya custom area konten tengah.</p><h4>Mode B: Suite Wrapper OFF (Full Custom)</h4><p><span class="ipsEmoji" title="">✅</span> Full kontrol dari &lt;!DOCTYPE html&gt; sampai &lt;/html&gt;<br><span class="ipsEmoji" title="">✅</span> Bisa pakai font, CSS, JS dari mana saja<br><span class="ipsEmoji" title="">❌</span> Harus handle security sendiri<br><span class="ipsEmoji" title="">❌</span> Tidak otomatis dapat update template IPS<br><span class="ipsEmoji" title="">❌</span> Header/footer/user dropdown IPS hilang<br><span class="ipsEmoji" title="">❌</span> Login/logout/notifications bawaan IPS tidak otomatis ada</p><p><strong>Kapan pakai:</strong> Saat homepage benar-benar berbeda total dari sisa situs (misal, landing page marketing).</p><blockquote class="ipsQuote" cite="" data-ipsquote=""><div class="ipsQuote_contents" data-ipstruncate=""><p><strong>Rekomendasi panduan ini:</strong> Pakai <strong>Suite Wrapper ON</strong>. Lebih aman, maintenance-friendly, dan transisi antar halaman seamless.</p></div></blockquote><h3>4. Blocks — Mesin Live Data</h3><p>Blocks adalah komponen reusable yang bisa menampilkan data dari aplikasi IPS. Ada <strong>4 tipe block</strong> di IPS 5:</p><div class="ipsRichText__table-wrapper"><table style="width: 636px;"><colgroup><col style="width:119px;"><col style="width:271px;"><col style="width:246px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Tipe</p></th><th colspan="1" rowspan="1"><p>Fungsi</p></th><th colspan="1" rowspan="1"><p>Contoh Penggunaan</p></th></tr><tr><td colspan="1" rowspan="1"><p>Plugin</p></td><td colspan="1" rowspan="1"><p>Feed otomatis dari aplikasi IPS</p></td><td colspan="1" rowspan="1"><p>Topic Feed, Record Feed, File Feed</p></td></tr><tr><td colspan="1" rowspan="1"><p>Manual HTML</p></td><td colspan="1" rowspan="1"><p>HTML + expression tag + PHP inline</p></td><td colspan="1" rowspan="1"><p>Counter, custom widget</p></td></tr><tr><td colspan="1" rowspan="1"><p>Editor</p></td><td colspan="1" rowspan="1"><p>Konten statis pakai WYSIWYG editor</p></td><td colspan="1" rowspan="1"><p>Welcome message, announcement</p></td></tr><tr><td colspan="1" rowspan="1"><p>Template</p></td><td colspan="1" rowspan="1"><p>Reusable dengan input fields</p></td><td colspan="1" rowspan="1"><p>Multi-purpose container</p></td></tr></tbody></table></div><p>Tutorial ini akan beberapa pakai <strong>Plugin</strong> (untuk feed) dan <strong>Manual HTML</strong> (untuk stats counter).</p><h3>5. Block Templates</h3><p>Tampilan default block IPS sering tidak match dengan desain Anda. Solusinya: <strong>Block Template</strong> — custom template yang bisa Anda terapkan ke block tertentu.</p><p><strong>Path:</strong> <em>AdminCP → Pages → Templates → Add → block template</em></p><p>Block Template menggunakan <strong>template tag IPS</strong> (<code>{{foreach}}, {$variable}</code>, dll) — bukan PHP murni.</p><h3>6. Template Tags vs Expression</h3><p>Dua jenis sintaks yang akan sering Anda lihat:</p><p><strong>Template Tag (logic flow):</strong></p><pre spellcheck="" class="ipsCode language-php-template" data-language="PHP Template"><code>{{if $count &gt; 1000}}
{{foreach $items as $item}}
...
{{endforeach}}
{{endif}}</code></pre><p><strong>Expression (eksekusi PHP):</strong></p><pre spellcheck="" class="ipsCode language-php" data-language="PHP"><code>{expression="number_format($count)"}
{expression="$record-&gt;_title"}</code></pre><p>Aturan: gunakan <code>{{...}}</code> untuk logic, <code>{expression="..."}</code> untuk output PHP.</p><h3>7. Default Page Setting</h3><p>Setelah membuat homepage, Anda harus set sebagai default agar muncul saat user buka root domain (<code>https://situs.com/</code>).</p><p><strong>Path:</strong> <em>AdminCP → System → Settings → Site Features → Default Page</em></p><hr><h2>Fase 1 — Setup Halaman Homepage</h2><h3>Step 1.1: Backup Tema Anda (Wajib!)</h3><p>Sebelum modifikasi apapun, backup tema aktif:</p><p>AdminCP → Customization → Appearance → Themes<br>└─ Hover icon download di samping tema → Save .xml file</p><p>Simpan file <code>.xml</code> ini di komputer lokal. Kalau ada yang rusak, Anda bisa <em>restore</em>.</p><h3>Step 1.2: Buat Page Baru</h3><p><strong>Path:</strong> <em>AdminCP → Pages → Add Page</em></p><p>Pilih Content Editor-nya Manual HTML</p><p><img class="ipsImage ipsRichText__align--block" data-fileid="1" src="https://lalu.pro/uploads/monthly_2026_05/Add_Page_Manual_HTML.webp.ae3e9a92f2e7ae6699c8272ed8a0ec16.webp" alt="Add_Page_Manual_HTML.webp" title="Add_Page_Manual_HTML.webp" loading="lazy"></p><p>Lengkapi Page Details dengan isian seperti dalam tabel di bawah.</p><p><img class="ipsImage ipsImage_thumbnailed ipsRichText__align--block" data-fileid="2" src="https://lalu.pro/uploads/monthly_2026_05/Add_Page_Details.thumb.webp.6d5c6d80f73ba377249a5456d9daac67.webp" alt="Add_Page_Details.webp" title="Add_Page_Details.webp" data-full-image="https://lalu.pro/uploads/monthly_2026_05/Add_Page_Details.webp.83b61743c308e52875748f318e9494e3.webp" loading="lazy"></p><p>Isian mencakup:</p><div class="ipsRichText__table-wrapper"><table style="width: 674px;"><colgroup><col style="width:195px;"><col style="width:208px;"><col style="width:271px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Field</p></th><th colspan="1" rowspan="1"><p>Isi dengan</p></th><th colspan="1" rowspan="1"><p>Catatan</p></th></tr><tr><td colspan="1" rowspan="1"><p>Page Name</p></td><td colspan="1" rowspan="1"><p>Homepage</p></td><td colspan="1" rowspan="1"><p>Display name internal</p></td></tr><tr><td colspan="1" rowspan="1"><p>Page Filename</p></td><td colspan="1" rowspan="1"><p>home</p></td><td colspan="1" rowspan="1"><p>URL slug (tanpa ekstensi), jika dikosongkan otomatis namanya mengikuti isian Page Name</p></td></tr><tr><td colspan="1" rowspan="1"><p>Page Folder</p></td><td colspan="1" rowspan="1"><p>Select</p></td><td colspan="1" rowspan="1"><p>Biarkan kosong untuk root, centang No Parent</p></td></tr><tr><td colspan="1" rowspan="1"><p>Use suite HTML wrapper</p></td><td colspan="1" rowspan="1"><p>ON (centang)</p></td><td colspan="1" rowspan="1"><p>Critical setting</p></td></tr><tr><td colspan="1" rowspan="1"><p>Page Title</p></td><td colspan="1" rowspan="1"><p>Komunitas Blogger Indonesia</p></td><td colspan="1" rowspan="1"><p>Untuk SEO <code>&lt;title&gt;</code></p></td></tr><tr><td colspan="1" rowspan="1"><p>Content</p></td><td colspan="1" rowspan="1"><p>Kode HTML</p></td><td colspan="1" rowspan="1"><p>Lihat step 1.3</p></td></tr><tr><td colspan="1" rowspan="1"><p>Meta Description</p></td><td colspan="1" rowspan="1"><p>(deskripsi singkat)</p></td><td colspan="1" rowspan="1"><p>Untuk SEO</p></td></tr></tbody></table></div><p>Klik <strong>Save &amp; Edit</strong>.</p><h3>Step 1.3: Struktur HTML Dasar</h3><p>Karena Suite Wrapper ON, <strong>TIDAK</strong> butuh <code>&lt;!DOCTYPE&gt;</code>, <code>&lt;html&gt;</code>, <code>&lt;head&gt;</code>, atau <code>&lt;body&gt;</code>. IPS akan menambahkannya otomatis. Anda cukup tulis konten yang ada di dalam <code>&lt;body&gt;</code> saja.</p><p>Template dasar yang bisa Anda pakai sebagai starting point:</p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>&lt;link rel="preconnect" href="https://fonts.googleapis.com"&gt;
&lt;link rel="preconnect" href="https://fonts.gstatic.com" crossorigin&gt;
&lt;link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&amp;family=Geist:wght@300;400;500;600;700&amp;family=Geist+Mono:wght@400;500;600&amp;display=swap" rel="stylesheet"&gt;

&lt;style&gt;
/* Reset &amp; base */
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
--bg: #ebe7e0;
--ink: #181818;
--accent: #ff5c2b;
/* ... variabel lainnya ... */
}

/* Style section di sini */
&lt;/style&gt;

&lt;!-- HERO SECTION --&gt;
&lt;section class="hero"&gt;
&lt;h1&gt;Selamat datang di komunitas kami&lt;/h1&gt;
&lt;p&gt;Deskripsi singkat tentang komunitas Anda.&lt;/p&gt;
&lt;/section&gt;

&lt;!-- SECTION LAIN --&gt;
&lt;section class="features"&gt;
&lt;!-- ... --&gt;
&lt;/section&gt;</code></pre><p>Itu saja kerangka dasarnya.</p><h3>Step 1.4: Penting — Cara Paste Kode di Editor IPS</h3><p>Editor IPS Pages punya mode Invision HTML Template dan Plain Text.</p><ol><li><p>Klik tab <strong>Content</strong>, secara default terpilih editor Invision HTML Template.</p></li><li><p>Salin kerangka HTML pada langkah 1.3 di atas.</p></li><li><p>Paste di kolom yang tersedia.</p></li><li><p><strong>Save</strong>.</p></li></ol><p>Setelah Save tentukan <strong>View Page</strong>, centang saja semua agar homepage dapat dilihat oleh semua pengunjung.</p><h3>Step 1.5: Set Homepage Sebagai Default</h3><p>Setelah halaman terbuat, jadikan default, langsung saja klik ikon bintang, lalu geser tombol <strong>Set as default</strong> page ke kanan (ON). Klik Save. Sekarang akses <code>https://situs-anda.com/</code> — homepage Anda muncul.</p><p>Catatan: Jangan lupa atur aplikasi Pages menjadi default juga.</p><h3>Step 1.6: Verifikasi</h3><p>Buka homepage di browser baru (incognito) untuk pastikan tampil benar:</p><ul><li><p>Tampil tanpa error</p></li><li><p>Header &amp; footer IPS muncul (karena Suite Wrapper ON)</p></li><li><p>Konten custom Anda di tengah</p></li><li><p>Font Google Fonts loaded (cek dengan Inspector → Network)</p></li></ul><p>Sampai di sini Anda sudah berhasil membuat homepage. Tapi masih sangat sederhana, hanya berisi teks selamat datang. Selanjutnya kita akan menambahkan beberapa block live.</p><h2>Fase 2 — Live Data Integration</h2><p>Bagian ini yang membuat homepage Anda <strong>hidup</strong> — menampilkan data real dari forum, artikel, dan files.</p><h3>2.1 Strategi Konversi Bertahap</h3><p>Urutan pekerjaan saya saat membuat homepage <a rel="external nofollow" href="https://lalu.pro">lalu.pro</a> adalah per section.<br></p><div class="ipsRichText__table-wrapper"><table style="min-width: 649px;"><colgroup><col style="min-width:20px;"><col style="width:227px;"><col style="width:178px;"><col style="width:224px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Urutan</p></th><th colspan="1" rowspan="1"><p>Section</p></th><th colspan="1" rowspan="1"><p>Tipe Block</p></th><th colspan="1" rowspan="1"><p>Tingkat Kesulitan</p></th></tr><tr><td colspan="1" rowspan="1"><p>1</p></td><td colspan="1" rowspan="1"><p>Stats counter (member, topic, artikel)</p></td><td colspan="1" rowspan="1"><p>Manual HTML</p></td><td colspan="1" rowspan="1"><p>Mudah</p></td></tr><tr><td colspan="1" rowspan="1"><p>2</p></td><td colspan="1" rowspan="1"><p>Latest articles</p></td><td colspan="1" rowspan="1"><p>Plugin (Record Feed)</p></td><td colspan="1" rowspan="1"><p>Sedang</p></td></tr><tr><td colspan="1" rowspan="1"><p>3</p></td><td colspan="1" rowspan="1"><p>Hot topics</p></td><td colspan="1" rowspan="1"><p>Plugin (Topic Feed)</p></td><td colspan="1" rowspan="1"><p>Sedang</p></td></tr><tr><td colspan="1" rowspan="1"><p>4</p></td><td colspan="1" rowspan="1"><p>Latest files (Download)</p></td><td colspan="1" rowspan="1"><p>Plugin (File Feed)</p></td><td colspan="1" rowspan="1"><p>Sedang</p></td></tr></tbody></table></div><p>Tapi saya hanya akan memandu Anda membuat <em>stat counter</em> dulu agar Anda melihat tampilannya dan memahmi alur kerjanya.</p><h3>2.2 Stats Counter — Member Count</h3><p>Kita perlu membuat sebuah block.</p><p><strong>Path:</strong> <em>AdminCP → Pages → Blocks → Create New Block → pilih "Manual HTML</em></p><p><img class="ipsImage ipsRichText__align--block" data-fileid="3" src="https://lalu.pro/uploads/monthly_2026_05/Create_New_Block.webp.4b1923dbbf4c0330c732fb574b626e27.webp" alt="Create_New_Block.webp" title="" loading="lazy"></p><p>Setelah klik tombol Next, isi form <strong>Details</strong> dan <strong>Content</strong>.</p><p><img class="ipsImage ipsRichText__align--block" data-fileid="4" src="https://lalu.pro/uploads/monthly_2026_05/Details_block.webp.01fd7cc122aaaa5a58076cd6c0995afe.webp" alt="Details_block.webp" title="" width="764" height="442" loading="lazy"></p><p><img class="ipsImage ipsRichText__align--block" data-fileid="6" src="https://lalu.pro/uploads/monthly_2026_05/Block_content.webp.5f27c0c45edc6af20a854ffaac5c6b3e.webp" alt="Block_content.webp" title="" loading="lazy"></p><p><strong>Title:</strong> Stat Members Count<br><strong>Template Key:</strong> <em>lalu_stat_members</em>, (tanpa spasi)<br><strong>Description:</strong> (boleh kosong)<br><strong>Content (paste persis):</strong></p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>{expression="number_format( \IPS\Db::i()-&gt;select( 'COUNT(*)', 'core_members', array( 'completed=?', 1 ) )-&gt;first() )"}</code></pre><p>Penjelasan kode:</p><ul><li><p><code>\IPS\Db::i()</code> — akses database IPS</p></li><li><p><code>select('COUNT(*)', 'core_members'...)</code> — hitung jumlah member</p></li><li><p><code>'completed=?', 1</code> — hanya yang sudah verifikasi email</p></li><li><p><code>number_format(...)</code> — format dengan koma (2,847)</p></li></ul><p>Klik <strong>Save</strong>. Klik Save sekali lagi (set view page).</p><h3>2.3 Stats Counter — Topic Count</h3><p>Sekarang buat block baru lagi, langkah-langkahnya seperti block member counter.</p><p>Isiannya:</p><p><strong>Title:</strong> Stat Topics Count<br><strong>Template Key:</strong> <em>lalu_stat_topics</em><br><strong>Content:</strong></p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>{{$count = \IPS\Db::i()-&gt;select( 'COUNT(*)', 'forums_topics', array( 'approved=?', 1 ) )-&gt;first();}}
{{if $count &gt;= 1000}}
{expression="round( $count / 1000, 1 )"}&lt;em&gt;k&lt;/em&gt;
{{else}}
{expression="number_format( $count )"}
{{endif}}</code></pre><p>Penjelasan:</p><ul><li><p>Hitung topic yang approved (approved=1)</p></li><li><p>Kalau ≥1000, tampilkan format 14k, 1.5k</p></li><li><p>Kalau &lt;1000, tampilkan angka penuh</p></li></ul><h3>2.4 Stats Counter — Article Count</h3><p>Cek dulu Database ID Articles:</p><p>AdminCP → Pages → Databases<br>└─ Cari "Articles" → catat angka ID di kolom paling kiri</p><p>Misal ID = <code>1</code>, maka tabel-nya adalah <code>cms_custom_database_1</code>.</p><p>Buat block dengan isian berikut:</p><p><strong>Title:</strong> Stat Articles Count<br><strong>Template Key:</strong> lalu_stat_articles<br><strong>Content (ganti angka 1 dengan ID Anda):</strong></p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>{expression="number_format( \IPS\Db::i()-&gt;select( 'COUNT(*)', 'cms_custom_database_1', array( 'record_approved=?', 1 ) )-&gt;first() )"}</code></pre><h3>2.5 Pasang Stats Block di Homepage</h3><p>Di Page Content homepage, di tempat yang sesuai:</p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>&lt;div class="hero-stats"&gt;
&lt;div class="stat"&gt;
&lt;div class="stat-num"&gt;{block="lalu_stat_members"}&lt;/div&gt;
&lt;div class="stat-label"&gt;Member aktif&lt;/div&gt;
&lt;/div&gt;
&lt;div class="stat"&gt;
&lt;div class="stat-num"&gt;{block="lalu_stat_topics"}&lt;/div&gt;
&lt;div class="stat-label"&gt;Diskusi&lt;/div&gt;
&lt;/div&gt;
&lt;div class="stat"&gt;
&lt;div class="stat-num"&gt;{block="lalu_stat_articles"}&lt;/div&gt;
&lt;div class="stat-label"&gt;Tutorial&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</code></pre><p>Anda bisa mencoba kode yang siap pakai berikut:</p><pre spellcheck="" class="ipsCode language-xml" data-language="HTML/XML"><code>&lt;style&gt;
/* Reset &amp; base */
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
--bg: #ebe7e0;
--ink: #181818;
--accent: #ff5c2b;
--muted: #6b6b6b;
--card: #ffffff;
--radius: 12px;
--gap: 1.5rem;
}

body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.6;
padding: 2rem 1rem;
}

/* HERO */
.hero {
max-width: 960px;
margin: 0 auto 3rem;
text-align: center;
padding: 3rem 1.5rem;
}

.hero h1 {
font-size: clamp(1.75rem, 4vw, 2.75rem);
font-weight: 700;
line-height: 1.2;
margin-bottom: 1rem;
}

.hero h1::after {
content: "";
display: block;
width: 60px;
height: 4px;
background: var(--accent);
margin: 1rem auto 0;
border-radius: 2px;
}

.hero p {
font-size: 1.1rem;
color: var(--muted);
max-width: 600px;
margin: 0 auto 2.5rem;
}

/* Hero stats */
.hero-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--gap);
max-width: 700px;
margin: 0 auto;
}

.stat {
background: var(--card);
padding: 1.5rem 1rem;
border-radius: var(--radius);
border: 1px solid rgba(0, 0, 0, 0.06);
transition: transform 0.2s ease;
}

.stat:hover {
transform: translateY(-3px);
}

.stat-num {
font-size: 2rem;
font-weight: 700;
color: var(--accent);
line-height: 1;
margin-bottom: 0.5rem;
}

.stat-label {
font-size: 0.9rem;
color: var(--muted);
}

/* Features section */
.features {
max-width: 960px;
margin: 0 auto;
padding: 2rem 1rem;
}

/* Responsive */
@media (max-width: 600px) {
.hero-stats {
grid-template-columns: 1fr;
}

.hero {
padding: 2rem 1rem;
}
}
&lt;/style&gt;

&lt;!-- HERO SECTION --&gt;
&lt;section class="hero"&gt;
&lt;h1&gt;Selamat datang di komunitas kami&lt;/h1&gt;
&lt;p&gt;Deskripsi singkat tentang komunitas Anda.&lt;/p&gt;

&lt;div class="hero-stats"&gt;
&lt;div class="stat"&gt;
&lt;div class="stat-num"&gt;{block="lalu_stat_members"}&lt;/div&gt;
&lt;div class="stat-label"&gt;Member aktif&lt;/div&gt;
&lt;/div&gt;
&lt;div class="stat"&gt;
&lt;div class="stat-num"&gt;{block="lalu_stat_topics"}&lt;/div&gt;
&lt;div class="stat-label"&gt;Diskusi&lt;/div&gt;
&lt;/div&gt;
&lt;div class="stat"&gt;
&lt;div class="stat-num"&gt;{block="lalu_stat_articles"}&lt;/div&gt;
&lt;div class="stat-label"&gt;Tutorial&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;</code></pre><p>Save → Clear cache → Refresh.</p><p>Lihat hasilnya, harusnya seperti berikut:</p><p><img class="ipsImage ipsRichText__align--block" data-fileid="5" src="https://lalu.pro/uploads/monthly_2026_05/Membuat_homepage_kustom_ips_v5.webp.9479a1a1d9456c28cc881e25425d0a04.webp" alt="Membuat_homepage_kustom_ips_v5.webp" title="" loading="lazy"></p><p>Saya harap Anda memahami kerjanya dan bisa berkreasi.</p>]]></description><guid isPermaLink="false">1</guid><pubDate>Fri, 15 May 2026 23:37:35 +0000</pubDate></item></channel></rss>
