How I Added a Related Posts Section in Blogger (With Randomized Results)
Important Blogger Settings
- Enable your blog feed: Go to Settings > Site feed and set Allow blog feed to Full (or at least Until Jump Break). If the feed is off or set to "None," the script can't fetch related posts.
- Labels must be visible: Make sure your post template includes the labels block, so the script can read the current post's labels.
1. The HTML Block
I placed this right after the post content in my theme. It only appears on single post pages.
<b:if cond='data:blog.pageType == "item"'> <div class='related-posts-container'> <h3 class='related-posts-title'>Related Posts</h3> <div class='related-posts-grid' id='related-posts-grid'> <!-- Posts will be loaded here --> </div> </div> </b:if>
2. The JavaScript (Randomized Related Posts)
This is the script I used. It grabs up to 2 labels from the current post, fetches posts with those labels, and displays up to 4 results (excluding the current post). The results are sorted by best label match, but randomized within each match group. I put this just before </body>
in the theme.
<script> //<![CDATA[ (function() { // Only run on individual post pages if (!/\/\d{4}\/\d{2}\//.test(window.location.pathname)) return; var maxPosts = 4; // Change as needed var container = document.getElementById('related-posts-grid'); if (!container) return; // Get up to 2 labels from the current post var labelLinks = document.querySelectorAll('.labels a[rel="tag"]'); var labels = []; labelLinks.forEach(function(link) { var label = link.textContent.trim(); if (label && labels.indexOf(label) === -1) labels.push(label); }); var labelsToUse = labels.slice(0, 2); if (!labelsToUse.length) return; // Fetch posts for each label (2 fetches only) var fetches = labelsToUse.map(function(label) { var url = '/feeds/posts/default/-/' + encodeURIComponent(label) + '?alt=json&max-results=8'; return fetch(url) .then(function(res) { return res.json(); }) .then(function(data) { return data.feed && data.feed.entry ? data.feed.entry : []; }); }); Promise.all(fetches).then(function(results) { var postsMap = {}; var currentUrl = window.location.href.replace(/#.*$/, ''); // Flatten and score posts results.flat().forEach(function(entry) { var postUrl = ''; for (var i = 0; i < entry.link.length; i++) { if (entry.link[i].rel === 'alternate') { postUrl = entry.link[i].href; break; } } if (postUrl === currentUrl) return; // Get post labels var postLabels = (entry.category || []).map(function(cat) { return cat.term; }); // Score: number of matching labels (max 2) var score = labelsToUse.filter(function(l) { return postLabels.includes(l); }).length; // Use highest score if duplicate if (!postsMap[postUrl] || postsMap[postUrl].score < score) { postsMap[postUrl] = { entry: entry, score: score, published: entry.published.$t }; } }); // Convert map to array, sort by score then randomize within same score var sorted = Object.values(postsMap) .sort(function(a, b) { if (b.score !== a.score) return b.score - a.score; // Score first return 0.5 - Math.random(); // Random within same score }) .slice(0, maxPosts); // Render var html = ''; sorted.forEach(function(item) { var entry = item.entry; var postUrl = entry.link.find(function(l) { return l.rel === 'alternate'; }).href; var title = entry.title.$t || 'Untitled'; var snippet = ''; if (entry.summary && entry.summary.$t) { snippet = entry.summary.$t.replace(/<[^>]+>/g, ''); } else if (entry.content && entry.content.$t) { snippet = entry.content.$t.replace(/<[^>]+>/g, '').substring(0, 150) + '...'; } // Get date var date = new Date(entry.published.$t).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); html += '<div class="related-post-card">'; html += ' <div class="related-post-content">'; html += ' <a href="' + postUrl + '" class="related-post-title">' + title + '</a>'; html += ' <div class="related-post-snippet">' + snippet + '</div>'; html += ' <div class="related-post-meta">'; html += ' <div class="related-post-date">'; html += ' <i class="fa fa-calendar" aria-hidden="true"></i>'; html += ' <span>' + date + '</span>'; html += ' </div>'; html += ' </div>'; html += ' </div>'; html += '</div>'; }); container.innerHTML = html; }).catch(function() { document.querySelector('.related-posts-container').style.display = 'none'; }); })(); //]]> </script>
3. The CSS
This is the CSS I used for a clean, responsive look. Add it to your theme’s <b:skin><![CDATA[ ... ]]></b:skin>
section.
.related-posts-container { margin: 2.5rem 0; padding: 0 1.25rem; } .related-posts-title { font-size: 1.5rem; font-weight: 600; margin-bottom: 1.5rem; color: #2a2a2a; text-align: left; } .related-posts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; } .related-post-card { background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; border: 1px solid #f0f0f0; } .related-post-card:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0,0,0,0.1); } .related-post-content { padding: 1.25rem; } .related-post-title { font-size: 1.1rem; font-weight: 600; line-height: 1.4; margin-bottom: 0.75rem; color: #2a2a2a; text-decoration: none; display: block; } .related-post-title:hover { color: #D2691E; } .related-post-snippet { font-size: 0.95rem; line-height: 1.6; color: #666; margin-bottom: 0.75rem; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } .related-post-meta { font-size: 0.85rem; color: #888; display: flex; align-items: center; gap: 0.5rem; } .related-post-date { display: flex; align-items: center; gap: 0.25rem; } @media (max-width: 768px) { .related-posts-grid { grid-template-columns: 1fr; gap: 1rem; } .related-posts-container { padding: 0 1rem; } .related-post-content { padding: 1rem; } }
How It Works
- Runs only on single post pages.
- Finds up to 2 labels from the current post.
- Fetches posts with those labels using Blogger’s JSON feed.
- Shows up to 4 related posts (excluding the current post), sorted by best label match and randomized within each group.
- Displays each related post’s title, snippet, and date in a card layout.
- Hides the section if nothing is found or there’s an error.