[TUTORIAL] Recently Viewed Products — AJAX + localStorage + Bricks Template (works with cached pages)

:brick: [TUTORIAL] Recently Viewed Products — AJAX + localStorage + Bricks Template (works with cached pages)

Hey Bricks friends :wave:

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!


:gear: Overview

:white_check_mark: 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!

:jigsaw: 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>

:jigsaw: 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:

  1. Registers a shortcode [bricks_recently_viewed].
  2. Enqueues a small JS file that loads your Bricks template via AJAX.
  3. 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);
}

:wrench: IMPORTANT: On step 4, replace ‘template_id’ on the code above (around line 25) with your Bricks template ID

:jigsaw: 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);

:jigsaw: 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.

  • :warning: 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.

:jigsaw: 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.

:bulb: 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.