What Is a Reading Dashboard?
A reading dashboard offers users a personalized overview of their reading history, progress through post series, and suggestions on what to read next. This feature is common in large content platforms but rarely seen on static blogs — especially those built with Jekyll. In this guide, you'll learn how to build one from scratch using only client-side tools.
Why It Matters for Static Blogs
Since Jekyll is a static site generator, implementing user-specific features like dashboards can seem out of scope. But with creative use of localStorage
, JavaScript, and well-structured front matter, we can simulate dynamic, personalized behavior entirely in the browser.
Design Goals for the Dashboard
- No backend or server-side database
- Lightweight and privacy-respecting
- Client-only logic using
localStorage
- Data pulled from existing Jekyll collections or posts
- Easy to integrate into any Jekyll theme (especially Mediumish)
Step 1: Recording Read Posts in localStorage
We begin by modifying your post layout to register each time a user views a post. Add this to your post.html
layout:
<script>
(function() {
const key = 'jekyll_read_posts';
const slug = '{{ page.url | replace:'/','-' | escape }}';
let data = JSON.parse(localStorage.getItem(key) || '[]');
if (!data.includes(slug)) {
data.push(slug);
localStorage.setItem(key, JSON.stringify(data));
}
})();
</script>
This stores an array of post URLs (or slugs) that the user has visited. It requires no backend or cookies.
Step 2: Setting Up the Dashboard Page
Create a new page called dashboard.md
or dashboard.html
in your root folder:
---
layout: page
title: "Your Reading Dashboard"
permalink: /dashboard/
---
<h2>Welcome Back</h2>
<div id="dashboard-container"></div>
<script>
(async function() {
const readPosts = JSON.parse(localStorage.getItem('jekyll_read_posts') || '[]');
const postMap = {
{% for post in site.posts %}
'{{ post.url | replace:'/','-' }}': {
title: '{{ post.title | escape }}',
url: '{{ post.url }}',
date: '{{ post.date | date: "%Y-%m-%d" }}'
},
{% endfor %}
};
const container = document.getElementById('dashboard-container');
if (readPosts.length === 0) {
container.innerHTML = '<p>No posts read yet. Start exploring the blog!</p>';
return;
}
container.innerHTML = '<h3>Posts You’ve Read:</h3><ul>' +
readPosts.map(slug => {
const post = postMap[slug];
return post ? `<li><a href="${post.url}">${post.title}</a> <small>(${post.date})</small></li>` : '';
}).join('') +
'</ul>';
})();
</script>
This creates a client-side dashboard that lists the posts the user has already read, pulled dynamically from site.posts
.
Step 3: Suggesting Next Posts
You can extend the dashboard with suggestions using the related_posts
logic, your series
data, or simply the most recent unread posts.
<script>
const suggestions = Object.keys(postMap)
.filter(slug => !readPosts.includes(slug))
.slice(0, 5)
.map(slug => {
const post = postMap[slug];
return `<li><a href="${post.url}">${post.title}</a></li>`;
});
container.innerHTML += '<h3>Suggested Reads:</h3><ul>' + suggestions.join('') + '</ul>';
</script>
Step 4: Enhancing UX
- Show progress bars or completion indicators for post series
- Add filters like “Read this week” or “Unread only”
- Style with theme-consistent cards or badges
Benefits of This Approach
- No personal data stored or sent
- Fully offline-capable
- No third-party trackers or cookies
- Data persists across sessions (unless browser is cleared)
Limitations
- localStorage is per-device and per-browser
- Cannot sync across multiple devices
- No analytics unless explicitly logged via opt-in (e.g., GitHub Issues)
Conclusion
Even in a purely static Jekyll site, you can offer personalized reading experiences with zero backend. A personal reading dashboard is a low-effort, high-impact enhancement for your blog, especially if you publish content in series or categories.
What’s Next?
In the next part of this series, we’ll explore how to combine this dashboard with client-side search to help users find their most relevant next article — boosting engagement without leaving the JAMstack philosophy.