Skip to main content

Overview

Visitor recommendations personalize the shopping experience for anonymous users based on their current session behavior. No login required — Refine tracks visitors using automatically generated IDs stored in localStorage.

Basic Usage

import { Refine } from '@refine-ai/sdk';

const refine = new Refine({
  apiKey: process.env.REFINE_API_KEY,
  organizationId: 'org_abc123',
  catalogId: 'cat_xyz789'
});

const recs = await refine.recs.forVisitor({
  configId: 'homepage_personalized',
  topK: 12
});

recs.results.forEach(product => {
  console.log(`${product.title} - $${product.price}`);
});

Parameters

configId
string
required
Recommendation configuration ID from the dashboard. Defines the algorithm and business rules.
topK
number
required
Number of recommendations to return. Recommended: 8-24.
filters
Filter[]
Additional filters to apply. See Filters.

How Visitor Tracking Works

The SDK automatically manages visitor identity:
  1. First Visit: A unique visitorId is generated and stored in localStorage
  2. Subsequent Visits: The same visitorId is reused
  3. Behavior Tracking: Product views, clicks, and searches are associated with the visitor
  4. Recommendations: The algorithm uses this history to personalize results
// Check the current visitor ID
const visitorId = refine.getVisitorId();
console.log('Visitor:', visitorId);  // e.g., "v_a1b2c3d4e5"
Visitor IDs persist across sessions but not across devices or browsers. Use User Recommendations for cross-device personalization.

Creating Configurations

Visitor recommendation configurations are created in the dashboard: Dashboard → Recommendations → Configurations → New Configuration Configuration options include:
SettingDescription
StrategyCollaborative filtering, content-based, or hybrid
FallbackWhat to show for new visitors with no history
DiversityHow much to vary recommended categories
Recency BiasWeight recent behavior more heavily
FiltersDefault filters (e.g., in-stock only)

Tracking Visitor Recommendations

const recs = await refine.recs.forVisitor({
  configId: 'homepage_recs',
  topK: 12
});

const recsContext = refine.events.trackRecommendations(
  recs.serveId,
  recs.results,
  'home_page',
  'visitor-recs'
);

// Track interactions
recsContext.trackClick(productId, position);
recsContext.trackView(productId, position);
recsContext.trackAddToCart(productId);

With Filters

Apply runtime filters on top of configuration defaults:
const recs = await refine.recs.forVisitor({
  configId: 'homepage_recs',
  topK: 12,
  filters: [
    { field: 'price', operator: 'lte', value: 100 },
    { field: 'metadata.category', operator: 'in', value: ['womens', 'accessories'] }
  ]
});

Use Cases

Homepage Hero

Show personalized picks based on browsing history:
async function loadHomepageRecs() {
  const recs = await refine.recs.forVisitor({
    configId: 'homepage_hero',
    topK: 8
  });
  
  return recs.results;
}

Category Page Enhancement

Mix algorithmic recommendations with category products:
async function loadCategoryPageRecs(category: string) {
  const recs = await refine.recs.forVisitor({
    configId: 'category_page_recs',
    topK: 8,
    filters: [
      { field: 'metadata.category', operator: 'eq', value: category }
    ]
  });
  
  return recs.results;
}

Search Fallback

When search returns no results, show personalized alternatives:
async function handleEmptySearch(query: string) {
  const recs = await refine.recs.forVisitor({
    configId: 'search_fallback',
    topK: 12
  });
  
  return {
    message: `No results for "${query}". You might like these:`,
    products: recs.results
  };
}

Exit Intent

Show personalized recommendations in exit-intent popups:
document.addEventListener('mouseleave', async (e) => {
  if (e.clientY < 0) {  // Mouse leaving viewport at top
    const recs = await refine.recs.forVisitor({
      configId: 'exit_intent',
      topK: 4
    });
    
    showExitPopup(recs.results);
  }
});

Complete Implementation

import { Refine } from '@refine-ai/sdk';

const refine = new Refine({
  apiKey: process.env.REFINE_API_KEY,
  organizationId: 'org_abc123',
  catalogId: 'cat_xyz789'
});

class HomepageRecommendations {
  private container: HTMLElement;
  private recsContext: any;

  constructor(containerId: string) {
    this.container = document.getElementById(containerId)!;
    this.load();
  }

  private async load() {
    this.container.innerHTML = '<p>Loading recommendations...</p>';

    try {
      const recs = await refine.recs.forVisitor({
        configId: 'homepage_personalized',
        topK: 12
      });

      this.recsContext = refine.events.trackRecommendations(
        recs.serveId,
        recs.results,
        'home_page',
        'visitor-recs'
      );

      this.render(recs.results);
      this.setupViewabilityTracking();
    } catch (error) {
      this.container.innerHTML = '<p>Unable to load recommendations.</p>';
      console.error('Recommendations error:', error);
    }
  }

  private render(products: any[]) {
    this.container.innerHTML = `
      <h2>Recommended for You</h2>
      <div class="product-grid">
        ${products.map((product, index) => `
          <div class="product-card" 
               data-id="${product.productId}" 
               data-position="${index}">
            <img src="${product.imageUrl}" alt="${product.title}" />
            <h3>${product.title}</h3>
            <p class="price">$${product.price.toFixed(2)}</p>
            <button class="add-to-cart">Add to Cart</button>
          </div>
        `).join('')}
      </div>
    `;

    // Click handlers
    this.container.querySelectorAll('.product-card').forEach(card => {
      const productId = (card as HTMLElement).dataset.id!;
      const position = parseInt((card as HTMLElement).dataset.position!);

      card.addEventListener('click', (e) => {
        if ((e.target as HTMLElement).classList.contains('add-to-cart')) {
          this.recsContext?.trackAddToCart(productId);
        } else {
          this.recsContext?.trackClick(productId, position);
          window.location.href = `/products/${productId}`;
        }
      });
    });
  }

  private setupViewabilityTracking() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const card = entry.target as HTMLElement;
          const productId = card.dataset.id!;
          const position = parseInt(card.dataset.position!);
          this.recsContext?.trackView(productId, position);
          observer.unobserve(card);
        }
      });
    }, { threshold: 0.5 });

    this.container.querySelectorAll('.product-card').forEach(card => {
      observer.observe(card);
    });
  }
}

// Initialize
new HomepageRecommendations('homepage-recs');

New Visitor Handling

For first-time visitors with no history, the configuration’s fallback strategy kicks in:
  • Popular Items: Show best-sellers
  • New Arrivals: Show recently added products
  • Editorial Picks: Show curated selections
Configure fallback behavior in the dashboard.

Next Steps