<?xml version="1.0"?>
<rss version="2.0"><channel><title>Articles: Blogger</title><link>https://lalu.pro/articles/blogger/?d=1</link><description>Articles: Blogger</description><language>en</language><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>Thu, 01 Jan 1970 00:00:00 +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>Thu, 01 Jan 1970 00:00:00 +0000</pubDate></item></channel></rss>
