[TUTORIAL] Recently Viewed Products — AJAX + localStorage + Bricks Template (works with cached pages)
Hey Bricks friends ![]()
Here’s a complete and fully working way to create a Recently Viewed Products section in Bricks Builder —
that works perfectly even on cached pages (no cookies or plugins needed).
This approach uses localStorage (on the client side) to track product visits and then loads a Bricks template via AJAX, so it works with LiteSpeed, Cloudflare, WP Rocket, etc.
No PHP needs to run on the product pages!
Overview
Features:
- Works on cached or static pages (no cookie or session needed).
- Tracks views locally via JS (localStorage).
- Loads a Bricks Template dynamically via admin-ajax.php.
- Fully compatible with Bricks Query Loop.
- Keeps product order (latest first).
- Easy to adapt for a “Favorites” system too!
Step 1 — Add this JavaScript to your Single Product template
(In Bricks: Page Settings → Custom Code → “Body (footer) scripts”)
This script saves the currently viewed postid set on body class into localStorage.
It runs once per product page, even if the page is cached.
<script id="recently-viewed-script">
document.addEventListener('DOMContentLoaded', function() {
var key = 'recently_viewed';
var body = (window.top && window.top.document && window.top.document.body) ? window.top.document.body : document.body;
if (!body) return;
var cls = body.className || '';
var clsArray = cls.split(' ');
var productId = null;
for (var i = 0; i < clsArray.length; i++) {
var c = clsArray[i];
if (c.indexOf('postid-') === 0) {
productId = parseInt(c.substring(7), 10);
break;
}
}
if (!productId) {
console.warn('⚠️ Could not extract postid from classes:', clsArray);
return;
}
console.log('🆔 Detected product ID:', productId);
// Guardar en localStorage (sin duplicados)
var viewed = [];
try {
var stored = localStorage.getItem(key);
if (stored) viewed = JSON.parse(stored) || [];
} catch (e) {}
var newList = [productId];
for (var j = 0; j < viewed.length; j++) {
if (viewed[j] !== productId) newList.push(viewed[j]);
}
// Optional: limit to 6
// newList = newList.slice(0, 6);
try {
localStorage.setItem(key, JSON.stringify(newList));
console.log('✅ recently_viewed stored:', newList);
} catch (e) {
console.error('❌ Error storing recently_viewed:', e);
}
});
</script>
Step 2 — Add PHP code in your theme (or Code Snippets plugin)
You can paste this code either:
- in your child theme’s functions.php , or
- in a Code Snippets block (recommended if you don’t want to edit theme files).
This PHP part:
- Registers a shortcode [bricks_recently_viewed].
- Enqueues a small JS file that loads your Bricks template via AJAX.
- Handles the AJAX call that renders your Bricks section template that you will create in step 4, with the product IDs from localStorage.
/* ===========================================================
Recently Viewed Products (AJAX + Bricks Template)
=========================================================== */
/**
* 1️⃣ Shortcode: placeholder container where AJAX content will be injected
*/
add_shortcode('bricks_recently_viewed', function() {
return '<div id="recently-viewed-products"></div>';
});
/**
* 2️⃣ Enqueue JavaScript responsible for reading localStorage and requesting AJAX
*/
add_action('wp_enqueue_scripts', function() {
wp_enqueue_script(
'recently-viewed-js',
get_stylesheet_directory_uri() . '/js/recently-viewed.js',
['jquery'],
filemtime(get_stylesheet_directory() . '/js/recently-viewed.js'),
true
);
// Pass data to JS
wp_localize_script('recently-viewed-js', 'recentlyViewedData', [
'ajaxurl' => admin_url('admin-ajax.php'),
'template_id' => 245499, // 🔧 Replace with your Bricks template ID
]);
});
/**
* 3️⃣ AJAX handler: renders Bricks template with proper CSS (supports all Bricks versions)
*/
add_action('wp_ajax_load_recently_viewed', 'load_recently_viewed_ajax');
add_action('wp_ajax_nopriv_load_recently_viewed', 'load_recently_viewed_ajax');
function load_recently_viewed_ajax() {
// 💾 Retrieve product IDs sent via GET or POST
$raw_ids = $_REQUEST['ids'] ?? '';
$ids = array_filter(array_map('intval', explode(',', $raw_ids)));
if (empty($ids)) {
wp_send_json_success('<p>No recently viewed products.</p>');
}
// 🧩 Bricks template ID (must be provided)
$template_id = intval($_REQUEST['template_id'] ?? 0);
if (!$template_id) {
wp_send_json_error('Missing template ID');
}
// ⚡ Pass product IDs to Bricks Query Loop via $_GET
$_GET['ids'] = implode(',', $ids);
// 🧱 Render the Bricks template (HTML only)
$html = do_shortcode('[bricks_template id="' . $template_id . '"]');
// 📁 Locate Bricks CSS directory inside uploads
$upload_dir = wp_upload_dir();
$base_dir = trailingslashit($upload_dir['basedir']) . 'bricks/css/';
$base_url = trailingslashit($upload_dir['baseurl']) . 'bricks/css/';
// 🧩 Check possible Bricks CSS file naming formats (old/new)
$candidates = [
"bricks-template-{$template_id}.css", // older Bricks versions
"post-{$template_id}.min.css", // modern minified format
"post-{$template_id}.css" // non-minified fallback
];
$css_link = '';
// 🔍 Look for the first existing CSS file
foreach ($candidates as $file) {
$path = $base_dir . $file;
if (file_exists($path)) {
$css_link = '<link rel="stylesheet" href="' . esc_url($base_url . $file) . '" />';
break;
}
}
// 🪄 If no physical file exists, fall back to inline CSS stored in DB
if (!$css_link) {
$css = get_post_meta($template_id, '_bricks_css', true);
if ($css) {
$css_link = '<style id="bricks-template-' . esc_attr($template_id) . '-css">' . $css . '</style>';
}
}
// 🚀 Return combined CSS + HTML in AJAX response
wp_send_json_success($css_link . $html);
}
IMPORTANT: On step 4, replace ‘template_id’ on the code above (around line 25) with your Bricks template ID
Step 3 — Create the JS file in your child theme
Create this file:
/wp-content/themes/your-child-theme/js/recently-viewed.js
It loads the Bricks section template on step 4 dynamically once the page is ready.
(function($) {
$(function() {
const key = 'recently_viewed';
const stored = localStorage.getItem(key);
if (!stored) return;
let ids;
try {
ids = JSON.parse(stored);
} catch {
return;
}
if (!Array.isArray(ids) || !ids.length) return;
// 🚫 Exclude current product
const body = (window.top && window.top.document && window.top.document.body)
? window.top.document.body
: document.body;
const cls = body ? body.className : '';
const match = cls.match(/postid-(\d+)/);
const currentId = match && match[1] ? parseInt(match[1], 10) : null;
if (currentId) {
ids = ids.filter(id => id !== currentId);
}
if (!ids.length) return;
// 🔧 AJAX request to admin-ajax.php
$.post(recentlyViewedData.ajaxurl, {
action: 'load_recently_viewed',
ids: ids.join(','),
template_id: recentlyViewedData.template_id
})
.done(function(res) {
if (res.success && res.data) {
$('#recently-viewed-products').html(res.data);
} else {
console.warn('No recently viewed data received', res);
}
})
.fail(function(err) {
console.error('AJAX error loading recently viewed', err);
});
});
})(jQuery);
Step 4 — Create the Bricks Section Template
1. Create a new Bricks Template of type Section (for example, name it “Recently Viewed Products”).
2. Inside it, Create a Query Loop.
3. On the Query editor (PHP) paste this code:
$ids = [];
if (isset($_GET[‘ids’])) {
$ids = array_map(‘intval’, explode(’,’, sanitize_text_field($_GET[‘ids’])));
}
return [
‘post_type’ => ‘product’,
‘posts_per_page’ => 6,
‘no_found_rows’ => true,
‘post__in’ => $ids,
‘orderby’ => ‘post__in’,
];
-
Change ‘posts_per_page’ value to the number of results you wish.
-
Save the template and copy the id of your new created template. Then paste the id to ‘template_id’ on the code on Step 2. -
Now design the loop visually (image, title, price, add-to-cart, etc.) —
exactly as you would with any Bricks product grid.
⸻
Step 5 — Add the shortcode anywhere
Place this shortcode anywhere in Bricks using the shortcode element, or Gutenberg:
[bricks_recently_viewed]
This will render the section Template “Recently Viewed” wherever you want.
When the page loads:
- The script reads product IDs from localStorage.
- It fetches the template HTML from admin-ajax.php.
- The “Recently Viewed Products” section appears dynamically, even on cached pages.
Tip
You can easily extend this same system to create a “Favorites” feature:
just replace the key name (recently_viewed) with something like user_favorites,
and trigger the save logic from a “heart” button click instead of DOMContentLoaded.