Everything posted by Lalu
-
htaccess WordPress Konfigurasi Terbaik
- 7 downloads
- Version 1.0.0
Berikut .htaccess WordPress lengkap dengan 9 bagian konfigurasi: WordPress Core — Rewrite rules standar agar permalink bekerja. Wajib ada. Keamanan – Blokir File Sensitif — Melindungi wp-config.php, xmlrpc.php, file log/backup, dan memblokir eksekusi PHP di folder uploads (mencegah shell upload). Keamanan – Proteksi Serangan — Blokir bot/scanner berbahaya, SQL Injection, XSS, dan akses langsung ke wp-includes. Juga menambahkan security headers seperti X-Frame-Options, X-Content-Type-Options, dan Permissions-Policy. Kompresi Gzip — Mengecilkan ukuran transfer HTML, CSS, JS, font, dan SVG sebelum dikirim ke browser. Browser Caching — Aset statis (gambar, CSS, JS, font) di-cache hingga 1 tahun. HTML tidak di-cache agar selalu fresh. ETags & Keep-Alive — Menonaktifkan ETag (tidak efisien di shared hosting) dan mengaktifkan koneksi persistent. Redirect HTTPS & www — Template siap pakai, tinggal hapus tanda # sesuai kebutuhan (HTTP→HTTPS, www atau non-www). Batas Upload PHP — Konfigurasi opsional jika hosting mengizinkan override via .htaccess. Error Page Kustom — Template redirect ke halaman error buatan sendiri. Hal yang perlu disesuaikan: HTTPS redirect → hapus tanda # di bagian [7] jika sudah pasang SSL Blokir wp-login.php → isi IP Anda di bagian [2] untuk keamanan ekstra xmlrpc.php → aktifkan blokir jika tidak pakai Jetpack atau aplikasi mobile WordPress PHP limits → aktifkan bagian [8] jika perlu upload file besarFree -
Apakah forum ini menggunakan chat bot AI?
Ya. Kami mengintegrasikan bot AI untuk merespon diskusi atau menjawab pertanyaan. Akun bot (atau yang diberi mandat sebagai bot) akan diebrikan lencana (badge) bot sebagai bentuk transparansi. Menyadari kenyataan bahwa AI bisa keliru maka moderator diwajibkan memeriksa setiap jawaban yang diberikan oleh AI. Moderator dapat mengedit atau menghapus jawaban yang diberikan oleh bot. Saat ini permintaan respon bot hanya tersedia untuk akun terbatas. Di masa depan mungkin akan tersedia untuk umum.
-
Aturan Umum di Forum
Selamat datang di Forum Komunitas Lalu Pro! Forum ini merupakan wadah interaktif yang dihadirkan untuk melengkapi ekosistem Lalu Pro. Di sini, para pegiat web, blogger, dan kreator konten dapat saling berdiskusi, berbagi pengalaman, serta berkolaborasi secara sehat. Untuk memastikan kenyamanan bersama dan menjaga kualitas diskusi di dalam forum, kami mewajibkan seluruh anggota untuk mematuhi beberapa regulasi dasar berikut: Saling Menghormati: Menjaga etika berkomunikasi, menghargai perbedaan pendapat, dan menghindari segala bentuk provokasi, penghinaan, atau SARA. Gunakan Kategori yang Tepat: Saat membuat topik baru, pastikan Anda meletakkannya di sub-forum yang relevan agar diskusi lebih terstruktur dan mudah ditemukan. Hindari Spam & Iklan Liar: Kami sangat mendukung pembagian portofolio atau studi kasus, namun dilarang keras melakukan spamming link afiliasi, promosi massal, atau jualan tanpa izin moderator. Anggota yang melanggar ketentuan di atas akan diberikan peringatan hingga pemblokiran akun demi kenyamanan bersama. Terima kasih atas kontribusi aktif Anda dalam membangun komunitas digital yang bersih, sehat, dan edukatif bersama Lalu Pro. Selamat berdiskusi! Salam, Tim Moderator Lalu Pro Catatan: Aturan lebih rinci dapat Anda baca di: Guidelines kami.
- Bagaimana cara mengamankan halaman login WordPress?
- Bagaimana cara mengamankan halaman login WordPress?
- Bagaimana cara mengamankan halaman login WordPress?
-
Apakah Blog sudah mati?
Saya sempat baca artikel bilang begitu.
- Apa bedanya WordPress.com dan WordPres.org?
-
Apa bedanya blogger dengan blogspot?
Apa bedanya blogger dengan blogspot?
- Apa bedanya WordPress.com dan WordPres.org?
- Apa kabar?
- Apa kabar?
- Cara memperbarui versi PHP di hosting
- Penyebab Internal Server Error (Error 500)
- Bagaimana cara mengamankan halaman login WordPress?
- Error Establishing a Database Connection
- Bagaimana mengatasi White Screen of Death (WSoD)?
- Apakah Blog sudah mati?
-
Nitro Blogger Template
- Version 1.0
Nitro adalah tema Blogger bergaya reader / dokumentasi dengan tata letak tiga kolom: sidebar navigasi gelap di kiri, daftar artikel (Daftar Isi) di tengah, dan area baca yang lapang di kanan. Dirancang untuk blog informasi dan panduan yang mengutamakan keterbacaan, kecepatan, dan navigasi yang mulus. Dikembangkan oleh Lalu Abd. Rahman. Fitur dan kelebihannya: Tata letak 3 kolom adaptif — sidebar, daftar isi, dan konten yang menyusut rapi menjadi satu kolom di perangkat seluler, lengkap dengan menu hamburger dan overlay. Mode Terang & Gelap — sakelar tema di topbar dengan preferensi tersimpan otomatis di perangkat pengunjung. Panel sticky — sidebar, daftar artikel, dan blok widget kanan tetap diam saat konten digulir, sehingga navigasi selalu terjangkau. Daftar Isi otomatis — TOC di dalam pos dibuat dari heading H2/H3 dengan smooth scroll, ditambah estimasi waktu baca dan penanda tanggal terbit/diperbarui. Pencarian instan — filter artikel sisi-klien yang langsung menyaring daftar tanpa memuat ulang halaman. Widget kaya — kartu profil penulis, postingan populer bernomor, dan statistik blog (total pos, komentar, tayangan) yang ditarik dari feed Blogger. Typografi premium — keluarga IBM Plex (Sans, Serif, Mono, Arabic) untuk antarmuka, badan tulisan, kode, dan teks RTL. Performa & efisiensi — daftar artikel pada halaman pos dibatasi 30 entri terbaru, komentar dimuat lazy lewat IntersectionObserver, dan thumbnail rusak disembunyikan otomatis. SEO terintegrasi — Open Graph, Twitter Card, kanonik, serta data terstruktur JSON-LD (WebSite + BlogPosting) agar tampilan berbagi dan hasil pencarian lebih kaya. Sentuhan detail — breadcrumb dinamis, scrollbar tipis seragam di semua peramban, halaman 404 dan kontak khusus, serta gaya lengkap untuk tabel, kutipan, blok kode, kotak info/peringatan, dan tombol. Demo$10 -
Kuis Berhadiah Ke-1
until
Tersedia hadiah uang tunai 50 ribu rupiah bagi anggota yang menyelesaikan kuis dengan skor dan waktu terbaik. Topik kuis adalah 'SEO untuk Pemula'. Hadiah akan dikirimkan via transfer bank. Syarat dan ketentuan: Belum pernah mendapatkan hadiah dari kami. Kuis hanya dapat diikuti oleh anggota terdaftar. Hadiah tidak dapat ditukar dengan apapun. Komplain tidak diterima jika status transfer dari kami telah berhasil, termasuk komplain salah nomor rekening, gangguan jaringan, dlsb. Catatan: Insyaallah link kuis akan tersedia 25 Mei 2026. Semoga berhasil! -
Tutorial: Membuat Cover Forum Style Invision untuk XenForo
Panduan lengkap membuat cover forum XenForo yang tampialannya mirip dengan IPS. Konsep tampilan akhir: cover di kiri, daftar thread terbaru di kanan, border atas warna berbeda per kategori. Mau coba? View full article
-
Tutorial: Membuat Cover Forum Style IPS untuk XenForo
Panduan lengkap membangun tampilan cover forum 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. Estimasi waktu: 30–45 menit Tingkat: Menengah (perlu nyaman edit file PHP via FTP dan template XenForo) Konsep tampilan akhir: cover di kiri, daftar thread terbaru di kanan, border atas warna berbeda per kategori. Lihat demo: https://forum.intutekno.com/ 1. Yang Akan DibuatSetiap forum di halaman utama akan menampilkan: Kolom kiri (30% lebar): Cover image 3:2 (SVG/WebP/JPG) Judul forum (link ke forum) Deskripsi forum Statistik (jumlah threads & messages) Kolom kanan (70% lebar): 5 thread terbaru dengan avatar, judul, username, tanggal, jumlah replies Atau ikon + pesan "Belum ada thread" kalau forum masih kosong Bonus: Border-top tiap card forum bisa diwarnai berbeda berdasarkan kategori induk-nya. 2. PrasyaratXenForo 2.3.x (panduan ini diuji di 2.3.10) Akses AdminCP Akses FTP/File Manager ke server (untuk upload PHP files) Akses cPanel atau setara untuk backup database (opsional tapi disarankan) Pemahaman dasar HTML/CSS akan membantu 3. Backup DuluSebelum apa pun, lakukan dua backup: Backup database: cPanel → phpMyAdmin → pilih database forum → Export → Quick → Go Backup file: File Manager cPanel → kompres folder forum jadi .zip → download Kalau ada yang salah saat ngoprek, kita bisa balik ke kondisi awal. 4. Aktifkan Development ModeBuka /src/config.php lewat File Manager atau FTP. Tambahkan dua baris ini di paling bawah: $config['development']['enabled'] = true; $config['development']['defaultAddOn'] = 'Intutekno/CoverForum'; Ganti Intutekno/CoverForum sesuai nama yang Anda inginkan (format: VendorName/AddOnName). Setelah save, refresh AdminCP — menu Development dan Add-ons akan muncul di sidebar. 5. Bikin Add-on PHPAdd-on ini bertugas menjalankan satu fungsi: mengambil 5 thread terbaru dari sebuah forum dan menyediakannya untuk dipakai di template. 5.1. Buat folder add-onVia FTP, buat struktur folder: /src/addons/Intutekno/CoverForum/ 5.2. Buat file addon.jsonIsi: { "title": "Intutekno Cover Forum", "version_string": "1.0.0", "version_id": 1000070, "dev": "Intutekno", "supported": true } 5.3. Buat file Listener.php<?php namespace Intutekno\CoverForum; class Listener { public static function templaterSetup(\XF\Container $container, \XF\Template\Templater &$templater) { $templater->addFunction('latest_forum_threads', ['Intutekno\CoverForum\TemplateHelper', 'templaterFunction'] ); } } 5.4. Buat file TemplateHelper.php<?php namespace Intutekno\CoverForum; class TemplateHelper { public static function templaterFunction($templater, &$escape, $nodeId, $limit = 5) { $escape = false; return self::getLatestThreads($nodeId, $limit); } public static function getLatestThreads($nodeId, $limit = 5) { $finder = \XF::finder('XF:Thread') ->where('node_id', $nodeId) ->where('discussion_state', 'visible') ->where('discussion_type', '<>', 'redirect') ->with('User') ->with('Prefix') ->order('last_post_date', 'DESC') ->limit($limit); return $finder->fetch(); } } Struktur akhir folder: /src/addons/Intutekno/CoverForum/ ├── addon.json ├── Listener.php └── TemplateHelper.php 6. Install Add-onDi AdminCP: Klik menu Add-ons di sidebar Cari "Intutekno Cover Forum" di list Klik tombol Install di sampingnya Confirm Status add-on harus jadi Active (toggle hijau). 7. Register Code Event ListenerIni langkah yang menghubungkan add-on PHP kita ke template engine XenForo. AdminCP → Development → Code event listeners Klik + Add code event listener di pojok kanan atas Isi form: Field Isi Listen to event templater_setup Event hint (kosongkan) Execute callback: Class Intutekno\CoverForum\Listener Execute callback: Method templaterSetup Callback execution order 10 (default) Enable callback execution ✅ centang Description Register latest_forum_threads function Add-on Intutekno Cover Forum 1.0.0 Klik Save 8. Edit Template node_list_forumAdminCP → Appearance → Templates → cari template node_list_forum → Edit. Ganti seluruh isi template dengan kode berikut: <xf:macro id="depth1" arg-node="!" arg-extras="!" arg-children="!" arg-childExtras="!" arg-depth="1"> <div class="block"> <div class="block-container"> <div class="block-body"> <xf:macro id="forum" arg-node="{$node}" arg-extras="{$extras}" arg-children="{$children}" arg-childExtras="{$childExtras}" arg-depth="{$depth}" /> </div> </div> </div> </xf:macro> <xf:macro id="depth2" arg-node="!" arg-extras="!" arg-children="!" arg-childExtras="!" arg-depth="1"> <xf:macro id="forum" arg-node="{$node}" arg-extras="{$extras}" arg-children="{$children}" arg-childExtras="{$childExtras}" arg-depth="{$depth}" /> </xf:macro> <xf:macro id="depthN" arg-node="!" arg-extras="!" arg-children="!" arg-childExtras="!" arg-depth="1"> <li> <a href="{{ link('forums', $node) }}" class="subNodeLink subNodeLink--forum {{ $extras.hasNew ? 'subNodeLink--unread' : '' }}"> <xf:fa icon="{{ $node.Data.TypeHandler.getTypeIconClass() ?: 'fa-comments' }}" class="subNodeLink-icon" />{$node.title} </a> <xf:macro id="forum_list::sub_node_list" arg-children="{$children}" arg-childExtras="{$childExtras}" arg-depth="{{ $depth + 1 }}" /> </li> </xf:macro> <xf:macro id="forum" arg-node="!" arg-extras="!" arg-children="!" arg-childExtras="!" arg-depth="!" arg-chooseName="" arg-bonusInfo=""> <xf:if is="$depth == 1 OR $depth == 2"> <!-- ===== COVER STYLE (forum utama) ===== --> <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' }}"> <div class="intuteknoCover-body"> <div class="intuteknoCover-side"> <a href="{{ link('forums', $node) }}" class="intuteknoCover-img"> <img src="/data/forum_covers/{$node.node_id}.svg" onerror="this.src='/data/forum_covers/default.svg'" alt="{$node.title}" loading="lazy" /> </a> <h3 class="intuteknoCover-title"> <a href="{{ link('forums', $node) }}">{$node.title}</a> </h3> <xf:if is="$node.description"> <div class="intuteknoCover-desc">{$node.description|raw}</div> </xf:if> <div class="intuteknoCover-stats"> <span><b>{{ phrase('threads') }}:</b> {$extras.discussion_count|number_short(1)}</span> <span><b>{{ phrase('messages') }}:</b> {$extras.message_count|number_short(1)}</span> </div> </div> <div class="intuteknoCover-threads"> <xf:set var="$latest" value="{{ latest_forum_threads($node.node_id, 5) }}" /> <xf:if is="$latest is not empty"> <ul class="intuteknoThread-list"> <xf:foreach loop="$latest" value="$thread"> <li class="intuteknoThread-item"> <xf:avatar user="{$thread.User}" size="xxs" defaultname="{$thread.username}" class="intuteknoThread-avatar" /> <div class="intuteknoThread-main"> <div class="intuteknoThread-title"> <xf:if is="$thread.prefix_id">{{ prefix('thread', $thread) }}</xf:if> <a href="{{ link('threads', $thread) }}">{$thread.title}</a> </div> <div class="intuteknoThread-meta"> <a href="{{ link('members', $thread.User) }}" class="username">{$thread.username}</a> <span>·</span> <xf:date time="{$thread.last_post_date}" /> <span>·</span> <span><xf:fa icon="fa-comment-alt" /> {$thread.reply_count|number_short(1)}</span> </div> </div> </li> </xf:foreach> </ul> <xf:else /> <div class="intuteknoThread-empty"> <xf:fa icon="fa-comments" /> <span>Belum ada thread di forum ini</span> </div> </xf:if> </div> </div> </div> <xf:else /> <!-- ===== ORIGINAL STYLE (sub-forum, depth >= 3) ===== --> <div class="node node--id{$node.node_id} node--depth{$depth} node--forum {{ $extras.hasNew ? 'node--unread' : 'node--read' }}"> <div class="node-body"> <span class="node-icon" aria-hidden="true"> <xf:fa icon="{{ $node.Data.TypeHandler.getTypeIconClass() ?: 'fa-comments' }}" /> </span> <div class="node-main js-nodeMain"> <xf:if is="$chooseName"> <xf:checkbox standalone="true"> <xf:option labelclass="u-pullRight" class="js-chooseItem" name="{$chooseName}[]" value="{$node.node_id}" /> </xf:checkbox> </xf:if> <xf:set var="$descriptionDisplay" value="{{ property('nodeListDescriptionDisplay') }}" /> <h3 class="node-title"> <a href="{{ link('forums', $node) }}" data-xf-init="{{ $descriptionDisplay == 'tooltip' ? 'element-tooltip' : '' }}" data-shortcut="node-description">{$node.title}</a> </h3> <xf:if is="$descriptionDisplay != 'none' && $node.description"> <div class="node-description {{ $descriptionDisplay == 'tooltip' ? 'node-description--tooltip js-nodeDescTooltip' : '' }}">{$node.description|raw}</div> </xf:if> <div class="node-meta"> <xf:if is="!{$extras.privateInfo}"> <div class="node-statsMeta"> <dl class="pairs pairs--inline"> <dt>{{ phrase('threads') }}</dt> <dd>{$extras.discussion_count|number_short(1)}</dd> </dl> <dl class="pairs pairs--inline"> <dt>{{ phrase('messages') }}</dt> <dd>{$extras.message_count|number_short(1)}</dd> </dl> </div> </xf:if> <xf:if is="$depth == 2 AND property('nodeListSubDisplay') == 'menu'"> <xf:macro id="forum_list::sub_nodes_menu" arg-children="{$children}" arg-childExtras="{$childExtras}" arg-depth="{{ $depth + 1 }}" /> </xf:if> </div> <xf:if is="$depth == 2 AND property('nodeListSubDisplay') == 'flat'"> <xf:macro id="forum_list::sub_nodes_flat" arg-children="{$children}" arg-childExtras="{$childExtras}" arg-depth="{{ $depth + 1 }}" /> </xf:if> <xf:if is="$bonusInfo is not empty"> <div class="node-bonus">{$bonusInfo}</div> </xf:if> </div> <xf:if is="!{$extras.privateInfo}"> <div class="node-stats"> <dl class="pairs pairs--rows"> <dt>{{ phrase('threads') }}</dt> <dd>{$extras.discussion_count|number_short(1)}</dd> </dl> <dl class="pairs pairs--rows"> <dt>{{ phrase('messages') }}</dt> <dd>{$extras.message_count|number_short(1)}</dd> </dl> </div> </xf:if> <div class="node-extra"> <xf:if is="{$extras.privateInfo}"> <span class="node-extra-placeholder">{{ phrase('private') }}</span> <xf:elseif is="{$extras.LastThread}" /> <div class="node-extra-icon"> <xf:if is="$xf.visitor.isIgnoring($extras.last_post_user_id)"> <xf:avatar user="{{ null }}" size="xs" /> <xf:else /> <xf:avatar user="{$extras.LastPostUser}" defaultname="{$extras.last_post_username}" size="xs" /> </xf:if> </div> <div class="node-extra-row"> <xf:if is="$extras.LastThread.isUnread()"> <a href="{{ link('threads/unread', $extras.LastThread) }}" class="node-extra-title" title="{$extras.LastThread.title}">{{ prefix('thread', $extras.LastThread) }}{$extras.LastThread.title}</a> <xf:else /> <a href="{{ link('threads/post', $extras.LastThread, {'post_id': $extras.last_post_id}) }}" class="node-extra-title" title="{$extras.LastThread.title}">{{ prefix('thread', $extras.LastThread) }}{$extras.LastThread.title}</a> </xf:if> </div> <div class="node-extra-row"> <ul class="listInline listInline--bullet"> <li><xf:date time="{$extras.last_post_date}" class="node-extra-date" /></li> <xf:if is="$xf.visitor.isIgnoring($extras.last_post_user_id)"> <li class="node-extra-user">{{ phrase('ignored_member') }}</li> <xf:else /> <li class="node-extra-user"><xf:username user="{$extras.LastPostUser}" defaultname="{$extras.last_post_username}" /></li> </xf:if> </ul> </div> <xf:else /> <span class="node-extra-placeholder">{{ phrase('none') }}</span> </xf:if> </div> </div> </div> </xf:if> <xf:if is="{$depth} == 1"> <xf:macro id="forum_list::node_list" arg-children="{$children}" arg-extras="{$childExtras}" arg-depth="{{ $depth + 1 }}" /> </xf:if> </xf:macro> Save. 9. Tambahkan CSSAdminCP → Appearance → Style properties → Extra LESS template → tambahkan blok ini: /* ============================================ 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; } } Save. 10. Setup Cover Image10.1. Buat folder coverVia File Manager cPanel atau FTP, buat folder: /data/forum_covers/ Set permission 755. 10.2. Cari node_id setiap forumAdminCP → Forums → klik nama forum → lihat URL: .../admin.php?nodes/nama-forum.5/edit ↑ node_id = 5 10.3. Upload cover imageUpload file SVG/WebP/JPG ke /data/forum_covers/ dengan nama sesuai node_id: /data/forum_covers/ ├── default.svg ← fallback wajib ├── 5.svg ← forum dengan node_id 5 ├── 6.svg ├── 7.svg └── ... Spesifikasi cover ideal: Aspect ratio 3:2 Ukuran 600×400 px Format SVG (paling ringan dan scalable) atau WebP 11. Customize Border per KategoriBorder-top tiap card forum bisa diwarnai berbeda berdasarkan kategori induk-nya. Berkat class intuteknoCover--cat{X} yang otomatis disisipkan di langkah 8, kita tinggal target via CSS. 11.1. Cari node_id kategoriSama seperti forum: AdminCP → Forums → klik kategori → lihat node_id di URL. 11.2. Tambahkan rules CSSTambahkan ke Extra LESS: /* === 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 */ Sesuaikan angka di --cat{X} dengan node_id kategori, dan warna sesuai branding masing-masing. Save dan refreshSetelah semua perubahan CSS: Save Extra LESS Tools → Rebuild caches → centang semua → Rebuild Buka homepage forum → Ctrl+F5 12. TroubleshootingError Function "latest_forum_threads" is not callableListener belum aktif atau salah class path. Cek: Add-ons — add-on Status harus Active Code event listeners — entry untuk templater_setup harus enabled, Class dan Method harus persis sama dengan file PHP Tools → Rebuild caches → centang semua Cover tidak muncul, ada celah putih di kiriBerarti gambar gagal dimuat. Cek: File ada di /data/forum_covers/? Permission folder 755, file 644? Nama file sesuai node_id? Path di template sesuai ekstensi file? Ada file default.svg sebagai fallback? Layout tidak berubah, masih style asliPastikan template node_list_forum ter-save dengan kode lengkap di langkah 8 Rebuild templates wajib Ctrl+F5 untuk bypass cache browser Cek dengan inspect element — class intuteknoCover harus muncul di div Sub-forum jadi style cover juga (tidak diinginkan)Cek conditional di template: <xf:if is="$depth == 1 OR $depth == 2"> Sub-forum biasanya depth ≥ 3, jadi seharusnya tidak ter-cover. Kalau forum Anda strukturnya berbeda, sesuaikan condition. Avatar ada celah ke kiriPastikan template pakai sintaks ini (avatar inline, bukan dibungkus <a>): <xf:avatar user="{$thread.User}" size="xxs" defaultname="{$thread.username}" class="intuteknoThread-avatar" /> Class intuteknoCover--cat{id} tidak muncul di HTMLBerarti template belum ter-update. Edit ulang, save, dan rebuild templates. 13. PenutupSetelah semua langkah di atas, halaman utama forum akan menampilkan card cover untuk setiap forum dengan layout 2 kolom dan border atas warna per kategori. Pengembangan SelanjutnyaBeberapa peningkatan yang bisa dilakukan kapan-kapan: Caching thread terbaru lewat XF registry supaya tidak query 5 thread per forum setiap homepage di-load Custom field untuk upload cover via AdminCP (tanpa FTP) Hover effect halus di card dan row thread Lazy load cover image dengan blur placeholder Struktur File Akhir/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 Tutorial dibuat berdasarkan implementasi nyata di forum.intutekno.com Lisensi: silakan adaptasi dan bagikan ulang. Kalau bermanfaat, sebutkan sumber sebagai amal jariah — pengetahuan yang terus bermanfaat.
-
Cara Membuat PageSpeed Tester Sendiri untuk Blog
Tutorial lengkap membuat PageSpeed Tester di Blogger, bisa Cek skor performa, SEO, accessibility, dan Core Web Vitals. Powered by Google PageSpeed Insights. View full article
-
Cara Membuat PageSpeed Tester Sendiri untuk Blog
Tutorial lengkap membuat tool PageSpeed Tester yang aman dan gratis untuk blog Blogger Anda. Tool ini menggunakan Google PageSpeed Insights API dengan Cloudflare Worker sebagai proxy untuk menyembunyikan API key. Apa yang Akan Anda BuatTool yang bisa: Cek skor Performance, Accessibility, Best Practices, dan SEO halaman manapun Tampilkan Core Web Vitals (FCP, LCP, TBT, CLS, dll) Berikan rekomendasi perbaikan diurutkan berdasarkan impact Cache hasil 1 jam untuk hemat quota 100% gratis (Google API quota gratis cukup untuk blog normal) Mengapa Butuh Cloudflare Worker?API Google butuh API key untuk akses unlimited. Kalau key disimpan langsung di JavaScript blog Anda: Key terlihat di view-source dan Network tab browser Siapa saja bisa copy key Anda Pakai untuk panggil API mereka sendiri Quota Anda habis, atau worse — kena billing kalau key Anda attach ke service berbayar Solusi: Simpan key di Cloudflare Worker (server). Tool browser hanya panggil Worker, Worker yang panggil Google API. Browser (tool Anda) → Cloudflare Worker (key di sini) → Google PageSpeed API Key tidak pernah keluar dari server. Aman. PrasyaratSebelum mulai, siapkan: Akun Google (untuk Google Cloud Console) Akun Cloudflare (gratis, daftar di cloudflare.com) Blog Blogger Anda Tidak perlu credit card untuk setup ini. STEP 1: Dapatkan Google API Key1.1 Aktifkan PageSpeed Insights APIBuka Google Cloud Console Buat project baru (atau pilih yang sudah ada) Di sidebar kiri: APIs & Services → Library Search: "PageSpeed Insights API" Klik hasil, lalu Enable 1.2 Buat API KeyAPIs & Services → Credentials Create Credentials → API key Key baru muncul. Copy key tersebut dan simpan sementara di Notepad/text editor Klik Edit API key untuk setup restriction 1.3 Set Restriction (Sangat Penting!)Tanpa restriction, key bisa dipakai siapa saja yang punya key tersebut. Application restrictions: Pilih Websites Klik Add an item Isi domain Anda: *.laluabdrahman.com* (ganti dengan domain Anda) https://laluabdrahman.com/* https://www.laluabdrahman.com/* API restrictions: Pilih Restrict key Centang hanya "PageSpeed Insights API" Klik Save STEP 2: Deploy Cloudflare Worker2.1 Buat Worker BaruLogin ke Cloudflare dashboard Sidebar kiri: Workers & Pages Klik Create application → Create Worker Beri nama, misal: pagespeed-proxy Klik Deploy (dengan default code) Setelah deploy, klik Edit code 2.2 Paste Kode WorkerHapus semua default code, paste kode berikut: 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 => { apiUrl.searchParams.append('category', cat); }); apiUrl.searchParams.set('key', env.GOOGLE_API_KEY); try { const controller = new AbortController(); const timeoutId = setTimeout(() => 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 (>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' } }); } Klik Save and deploy. 2.3 Tambah API Key sebagai SecretSekarang masukkan API key Anda ke Worker sebagai secret (encrypted): Di Worker → tab Settings Scroll ke section Variables and Secrets Klik + Add Isi: Type: Secret (penting! bukan Text) Variable name: GOOGLE_API_KEY (case-sensitive, persis seperti ini) Value: paste API key dari Step 1.2 Klik Save 2.4 Test WorkerBuka di browser: https://pagespeed-proxy.YOUR-NAME.workers.dev/?url=https://example.com&strategy=mobile Ganti YOUR-NAME dengan subdomain Worker Anda. Expected: Tunggu 15-40 detik Lihat JSON besar muncul dengan field lighthouseResult Jika ada error, baca pesan dan cek troubleshooting di bawah Catat URL Worker Anda — akan dipakai di Step 3. STEP 3: Setup Tool HTML di Blogger3.1 Edit Konfigurasi ToolBuka file pagespeed-tester.html Anda. Cari baris ini (sekitar baris 510): const WORKER_URL = ''; Ganti dengan URL Worker Anda: const WORKER_URL = 'https://pagespeed-proxy.YOUR-NAME.workers.dev/'; 3.2 Paste ke BloggerBlogger dashboard → Pages → New Page Klik mode HTML view (bukan Compose) Paste seluruh isi file pagespeed-tester.html Beri title halaman: "PageSpeed Tester" Publish 3.3 Test ToolBuka halaman yang baru dipublish, lalu: Input URL, contoh: https://example.com Pilih Mobile atau Desktop Klik Mulai Tes Tunggu 15-40 detik Hasil muncul dengan score rings, Core Web Vitals, dan rekomendasi Cache test: Klik Mulai Tes lagi dengan URL dan strategy sama Harusnya muncul <1 detik dari cache Footer berubah jadi: "✓ dari cache" TroubleshootingError: "Worker not configured: missing GOOGLE_API_KEY secret"Secret belum tersimpan atau Worker belum redeploy. Solusi: Cek di Settings → Variables and Secrets, pastikan ada GOOGLE_API_KEY (case-sensitive) Pastikan Type-nya Secret, bukan Text Edit kode Worker, save lagi untuk trigger redeploy Error: "Requests from referer <empty> are blocked"API key Anda di-restrict ke domain, tapi Worker tidak kirim Referer header. Solusi: Pastikan di kode Worker ada baris ini di dalam fetch() ke Google API: headers: { 'Referer': 'https://www.YOUR-DOMAIN.com/' } Domain di sini harus match dengan salah satu restriction di Google Cloud Console. Tombol "Mulai Tes" tidak responBuka Developer Console (F12 → Console), lihat error apa yang muncul. Cek juga: WORKER_URL sudah diisi dengan URL Worker Anda URL Worker bisa diakses langsung di browser Error: "HTTP 429" (Too Many Requests)Rate limit dari Google. Tunggu 1-2 menit, lalu coba lagi. Atau: Pastikan API key sudah terkonfigurasi dengan benar Caching seharusnya mengurangi request rate Hasil PageSpeed berbeda dari pagespeed.web.devNormal. Score PageSpeed bisa fluktuasi 5-10 point antar test karena: Variasi network condition di server Google Random sampling dari data CrUX (Chrome User Experience Report) Cache TTL yang berbeda Untuk benchmark akurat, jalankan test 3-5 kali dan ambil median. Quota dan BiayaGoogle PageSpeed Insights API25,000 query per hari gratis Cukup untuk blog normal — bahkan dengan caching off Tidak perlu billing setup Cloudflare Workers100,000 request per hari gratis (free tier) 10ms CPU time per request (kita pakai jauh lebih sedikit) Cache pakai Cloudflare Cache API (built-in, gratis) Estimasi penggunaan blog dengan 1,000 visitor/hariAsumsi: 1% visitor pakai tool = 10 test/hari Resource Limit Penggunaan Cloudflare Workers 100k/hari 10 req Google API 25k/hari 10 req Cache hit rate - ~30% (URL repeat) Penggunaan masih jauh di bawah free tier. Aman untuk blog dengan trafik wajar. Cara Kerja DetailArsitektur Defense in DepthTool ini punya 5 layer keamanan: Key disembunyikan di Worker secret — encrypted di Cloudflare, tidak visible di dashboard setelah disimpan API restriction di Google — key hanya bisa pakai PageSpeed Insights API, bukan Maps/Translate yang berbayar Website restriction di Google — referer harus match domain Anda Worker forward referer — memungkinkan website restriction bekerja meski request datang dari Cloudflare IP SSRF prevention di Worker — block private IP dan localhost di parameter URL Kalau salah satu layer compromised, layer lain masih protect: Key bocor → masih di-restrict ke PageSpeed Insights API only Restriction lemah → key tetap tidak visible di browser Cache StrategyWorker pakai Cloudflare Cache API (built-in, no setup): Cache key: URL + strategy TTL: 1 jam (3600 detik) Storage: Cloudflare edge, automatic eviction 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. Kenapa Hasil Pertama Lambat?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. Setelah cache, hasil instan karena tidak perlu re-audit. KesimpulanAnda sekarang punya tool PageSpeed Tester sendiri yang: Gratis sampai 25k test/hari (jauh di atas kebutuhan blog normal) Aman — API key tidak pernah terlihat publik Cepat untuk repeat test (cache 1 jam) Customizable — bisa modify UI, tambah fitur, dll Tool ini juga jadi foundation pattern untuk tool lain yang butuh API key. Misalnya kalau Anda mau buat: Image Alt Text Generator (pakai OpenAI/Claude API) SERP Checker (pakai SerpApi) Backlink Checker (pakai Ahrefs API) Polanya sama: Worker proxy + secret env variable. Selamat mencoba!
-
Review Hawk Host
Secara keseluruhan, Hawk Host adalah pilihan yang sangat seimbang bagi blogger, pengusaha digital, dan agensi kecil yang mencari performa tinggi dengan harga yang tetap masuk akal untuk jangka panjang. Apakah Anda tertarik menyewa hosting di Hawk Host? Apakah ada yang ditanyakan sebelum membeli? Jangan sungkan bertanya. Kalau Anda punya pengalaman dengan Hawk Host bagikan di sini mungkin berguna untuk anggota lainnya. View full hosting review