Back to Blog
Blogger Februari 17, 2026

Membuat Pagination pada Blogger Menampilkan 6 Post Terbaru dan Exclude Label Tertentu

Mats Inakri

Mats Inakri

Published: | Updated: Februari 17, 2026

Tantangan yang menarik karena Blogger secara bawaan tidak memiliki filter "exclude label" yang kuat pada API standarnya. Kita akan menggunakan Blogger JSON Feed API untuk menarik data, memfilternya secara manual di sisi klien (JavaScript), dan menangani navigasi (Next/Prev) berdasarkan timestamp atau index.

Berikut adalah skrip HTML/JavaScript lengkap yang bisa Anda pasang di halaman statis atau bagian tata letak Blogger Anda.


<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blogger Pagination - inakri.my.id</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        .post-card {
            transition: all 0.3s ease;
        }
        .post-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 20px rgba(0,0,0,0.1);
        }
        .loader {
            border: 3px solid #f3f3f3;
            border-radius: 50%;
            border-top: 3px solid #2563eb;
            width: 30px;
            height: 30px;
            animation: spin 1s linear infinite;
            display: none;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body class="bg-gray-50 font-sans">

<div class="container mx-auto px-4 py-8">
    <div class="flex items-center justify-between mb-8">
        <h2 class="text-2xl font-bold text-gray-800 border-l-4 border-blue-600 pl-4">Postingan Terbaru</h2>
    </div>
    
    <!-- Container Postingan -->
    <div id="post-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
        <!-- Konten dimuat di sini -->
    </div>

    <!-- Loading Indicator -->
    <div class="flex justify-center my-12">
        <div id="loader" class="loader"></div>
    </div>

    <!-- Navigasi -->
    <div class="flex justify-between items-center mt-12 border-t pt-8">
        <button id="prev-btn" class="flex items-center gap-2 bg-white border border-gray-300 px-5 py-2.5 rounded-full text-gray-700 font-medium hover:bg-gray-50 disabled:opacity-30 disabled:cursor-not-allowed transition shadow-sm" disabled>
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
            Sebelumnya
        </button>
        
        <div id="page-info" class="bg-blue-50 text-blue-700 px-4 py-1 rounded-full text-sm font-bold shadow-inner">
            Halaman 1
        </div>

        <button id="next-btn" class="flex items-center gap-2 bg-blue-600 text-white px-5 py-2.5 rounded-full font-medium hover:bg-blue-700 disabled:opacity-30 disabled:cursor-not-allowed transition shadow-md shadow-blue-200">
            Berikutnya
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
        </button>
    </div>
</div>

<script>
    /**
     * KONFIGURASI SPESIFIK INAKRI.MY.ID
     */
    const CONFIG = {
        postsPerPage: 6,
        excludedLabels: ['Portfolio', 'Services', 'Product'],
        blogUrl: 'https://www.inakri.my.id',
        containerId: 'post-container',
        loaderId: 'loader',
        nextBtnId: 'next-btn',
        prevBtnId: 'prev-btn',
        pageInfoId: 'page-info'
    };

    let currentPage = 1;
    let currentStartIndex = 1;
    let indexHistory = [1]; 

    /**
     * Menggunakan JSONP untuk menghindari masalah CORS
     */
    function loadContent(startIndex) {
        const loader = document.getElementById(CONFIG.loaderId);
        const container = document.getElementById(CONFIG.containerId);
        
        loader.style.display = 'block';
        container.style.opacity = '0.4';

        // Menghapus callback lama jika ada
        const oldScript = document.getElementById('blogger-api-script');
        if (oldScript) oldScript.remove();

        // Membuat script element untuk JSONP
        const script = document.createElement('script');
        script.id = 'blogger-api-script';
        // Menggunakan ?alt=json-in-script&callback=handleBloggerResponse
        script.src = `${CONFIG.blogUrl}/feeds/posts/default?alt=json-in-script&start-index=${startIndex}&max-results=50&callback=handleBloggerResponse`;
        
        script.onerror = () => {
            loader.style.display = 'none';
            container.style.opacity = '1';
            container.innerHTML = `<div class="col-span-full text-center py-10">
                <p class="text-red-500 font-medium">Gagal memuat data (CORS/Network Error). Pastikan URL blog benar.</p>
            </div>`;
        };

        document.body.appendChild(script);
    }

    /**
     * Callback handler untuk data dari Blogger
     */
    window.handleBloggerResponse = function(data) {
        const container = document.getElementById(CONFIG.containerId);
        const loader = document.getElementById(CONFIG.loaderId);
        const entries = data.feed.entry || [];
        
        // Filter postingan yang TIDAK memiliki label di daftar pengecualian
        const filteredPosts = entries.filter(post => {
            if (!post.category) return true;
            const labels = post.category.map(c => c.term);
            return !labels.some(l => CONFIG.excludedLabels.includes(l));
        });

        // Potong hanya 6 post untuk ditampilkan
        const postsToShow = filteredPosts.slice(0, CONFIG.postsPerPage);
        
        displayPosts(postsToShow);
        updateUI(filteredPosts.length > CONFIG.postsPerPage);

        // Hitung index untuk halaman berikutnya
        if (filteredPosts.length > CONFIG.postsPerPage) {
            const sixthPostIndexInEntries = entries.indexOf(postsToShow[CONFIG.postsPerPage - 1]);
            window.nextPageStartIndex = currentStartIndex + sixthPostIndexInEntries + 1;
        }

        loader.style.display = 'none';
        container.style.opacity = '1';
    };

    function displayPosts(posts) {
        const container = document.getElementById(CONFIG.containerId);
        container.innerHTML = '';

        if (posts.length === 0) {
            container.innerHTML = '<p class="col-span-full text-center text-gray-500 py-12">Tidak ada postingan untuk ditampilkan.</p>';
            return;
        }

        posts.forEach(post => {
            const title = post.title.$t;
            const link = post.link.find(l => l.rel === 'alternate').href;
            const date = new Date(post.published.$t).toLocaleDateString('id-ID', {
                day: 'numeric', month: 'short', year: 'numeric'
            });

            let imgUrl = 'https://via.placeholder.com/600x400?text=No+Image';
            if (post.media$thumbnail) {
                imgUrl = post.media$thumbnail.url.replace('s72-c', 'w600-h400-p-k-no-nu');
            }

            let snippet = "";
            if (post.summary) snippet = post.summary.$t;
            else if (post.content) snippet = post.content.$t.replace(/<[^>]*>?/gm, '');
            snippet = snippet.substring(0, 90) + "...";

            const card = `
                <article class="post-card bg-white rounded-2xl border border-gray-100 overflow-hidden flex flex-col shadow-sm">
                    <a href="${link}" class="block overflow-hidden h-48">
                        <img src="${imgUrl}" alt="${title}" class="w-full h-full object-cover hover:scale-110 transition duration-500">
                    </a>
                    <div class="p-6 flex flex-col flex-grow">
                        <div class="flex items-center gap-2 text-xs text-blue-600 font-bold uppercase mb-3">
                            <span class="bg-blue-50 px-2 py-0.5 rounded">${date}</span>
                        </div>
                        <h3 class="text-xl font-bold text-gray-800 leading-tight mb-3">
                            <a href="${link}" class="hover:text-blue-600 transition-colors">${title}</a>
                        </h3>
                        <p class="text-gray-600 text-sm leading-relaxed mb-4 flex-grow">
                            ${snippet}
                        </p>
                        <a href="${link}" class="inline-flex items-center text-sm font-bold text-gray-900 group">
                            Baca Selengkapnya 
                            <svg class="ml-1 w-4 h-4 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path></svg>
                        </a>
                    </div>
                </article>
            `;
            container.innerHTML += card;
        });
    }

    function updateUI(hasNext) {
        document.getElementById(CONFIG.pageInfoId).innerText = `Halaman ${currentPage}`;
        document.getElementById(CONFIG.prevBtnId).disabled = currentPage === 1;
        document.getElementById(CONFIG.nextBtnId).disabled = !hasNext;
    }

    document.getElementById(CONFIG.nextBtnId).addEventListener('click', () => {
        currentStartIndex = window.nextPageStartIndex;
        indexHistory.push(currentStartIndex);
        currentPage++;
        loadContent(currentStartIndex);
        window.scrollTo({ top: 0, behavior: 'smooth' });
    });

    document.getElementById(CONFIG.prevBtnId).addEventListener('click', () => {
        if (currentPage > 1) {
            indexHistory.pop();
            currentStartIndex = indexHistory[indexHistory.length - 1];
            currentPage--;
            loadContent(currentStartIndex);
            window.scrollTo({ top: 0, behavior: 'smooth' });
        }
    });

    window.addEventListener('DOMContentLoaded', () => loadContent(currentStartIndex));
</script>

</body>
</html>