
I’ve seen how often shoppers lose momentum simply because they can’t easily return to products they were just viewing. A Recently Viewed Products section in Shopify solves this exact friction point by helping customers instantly rediscover items they already showed interest in.
This guide explains how recently viewed products work, why they impact conversions, and how to implement them cleanly in Shopify using a performance-first approach. You’ll learn the core components, technical logic, and step-by-step setup required to deliver a more intuitive and personalised shopping experience.
You'll learn about the key benefits of this feature, the technical components behind it, and a step-by-step implementation guide complete with code snippets. By the end, you'll understand how this simple addition can enhance user experience, reduce cart abandonment, and create natural opportunities for cross-selling. Ready to give your customers a more intuitive shopping experience? Let's dive in!
What Are Recently Viewed Products?
The Shopify recently viewed products feature offers numerous advantages for store owners looking to optimize their customer experience. By implementing this solution, you'll create multiple touchpoints that guide customers back to products they've shown interest in.
Using browser storage mechanisms such as localStorage, this feature records products a customer has interacted with and makes them easily accessible later, removing friction from the buying journey and improving product rediscovery.
Benefits of Recently Viewed Products for Your Shopify Store

1. Enhanced User Experience
Imagine walking into a physical store where an assistant remembers exactly what caught your eye. That's precisely what Recently Viewed Products does online. Recently viewed products reduce cognitive load by giving shoppers a fast way to return to items they already evaluated, creating a smoother and more confident browsing experience.
2. Increased Conversion Rates
Customers are more likely to purchase products they've already shown interest in. Through strategic conversion optimization and repeated presentations of these items, you create multiple touchpoints that can nudge a hesitant shopper toward making a purchase.
3. Personalisation at Its Best
In an era of generic online shopping, recently viewed products add a layer of individualised experience, making customers feel understood and valued.
4. Reduced Cart Abandonment
Sometimes customers get distracted or need time to make a decision. This feature acts as a gentle reminder, bringing those potential purchases back into focus.
5. Cross-Selling and Upselling Opportunities
By showcasing previously viewed items, you create natural opportunities to suggest complementary or upgraded products, potentially increasing the average order value.
The Core Components of this Implementation
1. Product Tracking
The implementation focuses on data freshness, performance efficiency, and conditional rendering, ensuring the section appears only when relevant and never impacts page speed or user experience.
- 7-Day Product Expiration: Products automatically expire after a week, keeping the recommendations fresh and relevant
- Performance-Optimized: Lightweight JavaScript implementation
- Conditional Rendering: Section hidden when no products are available
Your Store, Open for Business - Fast
Custom Shopify builds that convert browsers into buyers.
2. Key Technical Highlights
LocalStorage Management
class RecentlyViewedProducts {
constructor() {
this.storageKey = 'oss_recently_viewed';
this.maxProducts = 4; // Configurable product limit
this.expirationDays = 7; // Automatic data expiration
}
}
This constructor sets up the core parameters for tracking recently viewed products, demonstrating a clean, modular approach to managing product history.
3. Product Cleanup
cleanExpiredProducts() {
const products = this.getStoredProducts();
const validProducts = products.filter(product =>
!this.isProductExpired(product) &&
product.published_at
);
}
The cleanExpiredProducts() method ensures that:
- Only current, published products are stored
- Old products are automatically removed
- The product list remains fresh and relevant
4. Performance and User Experience
renderProducts() {
// Clean expired products before rendering
this.cleanExpiredProducts();
const products = this.getStoredProducts();
// Hide section if no products
if (!products || products.length === 0) {
this.container.style.display = 'none';
return;
}
}
Key user experience features:
- Automatic section visibility management
- Efficient product rendering
- Graceful handling of empty product lists
5. Customization Options
- Adjust maximum number of products
- Modify expiration period
- Customize styling to match store design
How to Add a Recently Viewed Products Section in Shopify: Step-by-Step Guide
Step 1: Create the Liquid Section
{% comment %}
Recently Viewed Products Section
- Only shows published/active products
- Data expires after 7 days
- Hidden by default until products are available
{% endcomment %}
<style>
.oss-recently-viewed {
margin-bottom: 2rem;
display: none;
}
.oss-recently-viewed.has-products {
display: block;
}
.oss-recently-title h2 {
font-size: 35pt !important;
margin-bottom: 1rem;
padding-top: 1rem;
}
.oss-product-title{
font-family: "Moniqa-Condensed";
font-size: 17pt;
color: black;
text-transform: uppercase;
}
.oss-product-price{
color: black;
font-family: "Gill Sans";
font-size: 11pt;
font-weight: normal;
}
.oss-recent-products-block {
display: grid;
gap: var(--grid-desktop-horizontal-spacing, 20px);
}
@media only screen and (min-width: 750px) {
.oss-recent-products-block {
grid-template-columns: repeat(4, 1fr);
}
}
@media only screen and (max-width: 749px) {
.oss-recent-products-block {
grid-template-columns: repeat(2, 1fr);
}
.oss-product-title{
font-size: 11.5pt;
}
.oss-product-price{
font-size: 9.5pt;
}
}
.oss-product {
cursor: pointer;
}
.oss-product-img img {
aspect-ratio: 3.2/5;
object-fit: cover;
}
.oss-product-title {
margin: 10px 0 5px;
}
.oss-product-title a {
color: black;
text-decoration: none;
}
.oss-product-price {
margin: 0;
color: #000;
}
</style>
{% assign current_time = 'now' | date: '%s' | times: 1000 %}
{% if template contains 'product' and product.published_at != nil %}
<script type="application/json" id="CurrentProductData">
{
"id": {{ product.id | json }},
"title": {{ product.title | json }},
"handle": {{ product.handle | json }},
"url": {{ product.url | json }},
"featured_image": "{{ product.featured_image | img_url: 'master' }}",
"price": {{ product.price | money | json }},
"timestamp": {{ current_time | json }},
"published_at": {{ product.published_at | date: '%s' | times: 1000 | json }}
}
</script>
{% endif %}
<section class="oss-recently-viewed page-width{% if section.settings.full_width %} page-width-desktop{% endif %}" id="RecentlyViewed-{{ section.id }}">
{% if section.settings.title != blank %}
<div class="oss-recently-title">
<h2 class="title inline-richtext h2 scroll-trigger animate--slide-in">{{ section.settings.title }}</h2>
</div>
{% endif %}
<div class="oss-recent-products-block">
</div>
</section>
<script>
class RecentlyViewedProducts {
constructor() {
this.storageKey = 'oss_recently_viewed';
this.maxProducts = {{ section.settings.products_to_show }};
this.expirationDays = 7; // Data expires after 7 days
this.container = document.querySelector('#RecentlyViewed-{{ section.id }}');
this.productsContainer = this.container.querySelector('.oss-recent-products-block');
}
init() {
this.cleanExpiredProducts();
this.renderProducts();
this.updateCurrentProduct();
}
getCurrentProduct() {
const dataElement = document.getElementById('CurrentProductData');
if (!dataElement) return null;
try {
const productData = JSON.parse(dataElement.textContent);
if (!productData || !productData.url || !productData.published_at) return null;
return productData;
} catch (error) {
console.error('Error parsing current product data:', error);
return null;
}
}
isProductExpired(product) {
if (!product.timestamp) return true;
const now = new Date().getTime();
const age = now - product.timestamp;
const expirationTime = this.expirationDays * 24 * 60 * 60 * 1000; // Convert days to milliseconds
return age > expirationTime;
}
cleanExpiredProducts() {
const products = this.getStoredProducts();
const validProducts = products.filter(product =>
!this.isProductExpired(product) &&
product.published_at
);
if (validProducts.length !== products.length) {
this.setStoredProducts(validProducts);
}
}
getStoredProducts() {
try {
const stored = localStorage.getItem(this.storageKey);
return stored ? JSON.parse(stored) : [];
} catch (error) {
console.error('Error reading from localStorage:', error);
return [];
}
}
setStoredProducts(products) {
try {
// Filter out any products that are expired or unpublished
const validProducts = products.filter(product =>
!this.isProductExpired(product) &&
product.published_at
);
localStorage.setItem(this.storageKey, JSON.stringify(validProducts));
} catch (error) {
console.error('Error writing to localStorage:', error);
}
}
updateCurrentProduct() {
const currentProduct = this.getCurrentProduct();
if (!currentProduct) return;
let products = this.getStoredProducts();
// Remove if already exists
products = products.filter(product => product.url !== currentProduct.url);
// Add to start of array
products.unshift(currentProduct);
// Limit to max products
products = products.slice(0, this.maxProducts);
this.setStoredProducts(products);
this.renderProducts();
}
renderProducts() {
if (!this.productsContainer) return;
// Clean expired products before rendering
this.cleanExpiredProducts();
const products = this.getStoredProducts();
// If no products, hide the entire section and return early
if (!products || products.length === 0) {
this.container.style.display = 'none';
return;
}
// Show section if we have products
this.container.style.display = 'block';
// Toggle section visibility
this.container.classList.toggle('has-products', products.length > 0);
const html = products.map(product => {
// Validate required fields
if (!product.url || !product.title || !product.featured_image || !product.published_at) return '';
return `
<div class="oss-product">
<div class="oss-product-img">
<a href="${product.url}">
<img src="${product.featured_image}" alt="${product.title}" loading="lazy"/>
</a>
</div>
<p class="oss-product-title">
<a href="${product.url}">${product.title}</a>
</p>
<p class="oss-product-price">${product.price}</p>
</div>
`;
}).join('');
this.productsContainer.innerHTML = html;
}
}
// Initialize on DOM content loaded
document.addEventListener('DOMContentLoaded', () => {
const recentlyViewed = new RecentlyViewedProducts();
recentlyViewed.init();
});
</script>
{% schema %}
{
"name": "Recently Viewed Products",
"tag": "section",
"class": "section",
"settings": [
{
"type": "text",
"id": "title",
"default": "Recently Viewed",
"label": "Title"
},
{
"type": "range",
"id": "products_to_show",
"min": 2,
"max": 8,
"step": 1,
"default": 4,
"label": "Maximum products to show"
},
{
"type": "checkbox",
"id": "full_width",
"default": false,
"label": "Full width"
}
],
"presets": [
{
"name": "Recently Viewed Products"
}
]
}
{% endschema %}
Step 2: Add Section to Theme
FAQs
1. How does Shopify track recently viewed products?
Shopify typically uses browser storage like localStorage to save product data during a user’s session without affecting server performance.
Your Store, Open for Business - Fast
Custom Shopify builds that convert browsers into buyers.
2. Do recently viewed products affect Shopify store speed?
When implemented correctly with lightweight JavaScript and conditional rendering, it has a performance impact.
3. How many recently viewed products should be displayed?
Most stores perform best with 3–6 products, balancing visibility without overwhelming the shopper.
4. Do recently viewed products work across devices?
By default, they are device-specific since data is stored locally unless synced with customer accounts.
5. Can recently viewed products increase conversion rates?
Yes. Re-exposing evaluated products reduces friction and improves purchase confidence, often lifting conversions.
6. Is custom coding better than Shopify apps for this feature?
Custom implementation gives better control, performance, and styling, especially for theme-specific designs.
Conclusion
A Recently Viewed Products section improves product rediscovery, reduces drop-offs, and supports more confident purchase decisions. When implemented with clean logic and performance safeguards, it becomes a subtle but powerful conversion lever for Shopify stores.
The code keeps track of what customers view and shows these products in a neat section. This helps customers remember products they liked but forgot to buy.
Store owners can easily change how many products show up and how the section looks.
Adding this feature is a simple way to improve your store and help customers find what they want, which can lead to more sales.



