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.
