mcpField
R
recently-viewed-products

recently-viewed-products

github.com/finsilabs设计与 UI6077 次安装5 天前更新
代码仓库

关于 recently-viewed-products

这项技能帮助网店记录商品浏览并展示最近浏览组件,介绍各平台内置方案和自定义店面实现;展示商品时重新获取最新数据。

主要功能

  1. 01Shopify、WooCommerce 和 BigCommerce 方案
  2. 02使用浏览器存储浏览记录
  3. 03通过 localStorage 自定义跟踪
  4. 04从服务器刷新商品信息
  5. 05隐私同意与记录过期指导

适用场景

  1. 01在商品详情页添加最近浏览区块
  2. 02在购物车或首页显示浏览记录
  3. 03为无头店面构建浏览历史组件
  4. 04向个性化引擎提供浏览数据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill recently-viewed-products

安装后在 Claude Code 中输入/recently-viewed-products确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/ssr-hydration-and-privacy-fallbacks/
criteria.json
task.md
evals/storage-utility-implementation/
criteria.json
task.md
evals/widget-component-and-batch-api/
criteria.json
task.md

详细介绍

最近浏览的产品

概述

跟踪购物者浏览过的产品,并在产品页面、购物车和主页上通过“最近浏览”小组件展示这些产品。使用浏览器存储保留当前会话和跨会话的浏览记录。始终从服务器重新获取产品数据,避免显示过时价格或缺货商品。

何时使用此技能

  • 为回访访客打造“接着上次继续浏览”的体验时
  • 在产品详情页或购物车抽屉中添加“最近浏览”小组件时
  • 与需要客户端行为数据的个性化引擎集成时
  • 构建无头店面,并且需要不依赖后端的轻量级浏览记录时

核心说明

步骤 1:确定商家的平台并选择合适的方案
平台推荐方案原因
Shopify在 OS2.0 主题中启用内置的 Recently Viewed 分区,或安装 Also Bought • Related products / LimeSpot PersonalizerDawn 和大多数 OS2.0 主题都内置了原生的 Recently Viewed 分区;LimeSpot($15/月)提供 AI 驱动的个性化功能,包括跨会话的最近浏览记录
WooCommerce使用 WooCommerce 内置的 Recently Viewed Products 小组件或简码,或安装 YITH WooCommerce Frequently Bought TogetherWooCommerce 开箱即带有最近浏览小组件;可通过 外观 → 小组件 或 [recent_products] 简码放置
BigCommerce使用 BigCommerce Page Builder 中的 Recently Viewed 小组件,或在 Cornerstone 主题设置中启用Cornerstone 和 BigCommerce Page Builder 都内置了原生 Recently Viewed 小组件,可自动使用浏览器 Cookie
Custom / Headless使用 localStorage 跟踪浏览记录,并通过服务端批量接口获取最新产品数据客户端存储意味着无需服务器状态;展示时重新获取数据可确保价格和库存信息为最新
步骤 2:在平台上启用 Recently Viewed

Shopify

使用内置分区(OS2.0 主题 — Dawn、Sense、Craft):

  1. 前往 在线商店 → 主题 → 自定义
  2. 导航到产品页面模板
  3. 点击 添加分区 并搜索“Recently Viewed”——它会在分区列表中显示为 Recently viewed products
  4. 配置以下选项:
    • 设置 要显示的产品数量上限(建议为 4–8 个)
    • 设置分区标题文本(“Recently Viewed”“Your Browsing History”等)
    • 设置产品卡片样式:是否显示价格、评分等
  5. 该分区会自动使用 JavaScript 和浏览器存储(localStorage),无需额外配置

在主页上显示:

  1. 前往主页模板
  2. 按照相同方式添加 Recently viewed products 分区
  3. 该分区只会为曾浏览过产品的回访访客呈现内容

LimeSpot Personalizer(跨设备、AI 驱动):

  1. 从 Shopify App Store 安装
  2. LimeSpot 在服务端跟踪浏览记录(而不仅是在浏览器中),因此最近浏览记录可跨设备和浏览器保留
  3. 在 LimeSpot 控制面板中配置 Recently Viewed 小组件的位置——通过应用分区可将其放在任何页面

WooCommerce

内置的最近浏览小工具:

  1. 前往 外观 → 小工具
  2. 找到 WooCommerce Recently Viewed Products 小工具
  3. 将其拖到 Shop Sidebar 或 Footer 小工具区域
  4. 配置:
    • 要显示的商品数量:建议设为 4–6
    • 显示标题:是/否
  5. 或者,直接在商品页面模板、侧边栏或 Gutenberg 区块中放置短代码 [woocommerce_recently_viewed_products per_page="4"]

适用于 Classic Editor / Gutenberg:

  • 在 Gutenberg 中,添加一个 Shortcode 区块并粘贴 [woocommerce_recently_viewed_products per_page="4"]
  • 对于 Elementor:使用 Shortcode 小工具嵌入相同代码

WooCommerce 会将最近浏览的商品 ID 存储在访客的会话中(PHP 会话 / Cookie),无需安装插件。小工具会自动读取此会话。


BigCommerce

内置的最近浏览小工具(Cornerstone 主题):

  1. 前往 Storefront → My Themes → Customize
  2. 导航到商品页面模板
  3. 在侧边栏中,找到 Recently Viewed 区域并启用它(或在页面布局中调整其位置)
  4. 设置要显示的商品数量
  5. BigCommerce 使用浏览器 Cookie 跟踪最近浏览的商品,无需安装应用

使用 Page Builder:

  1. 前往 Storefront → Page Builder
  2. 打开要放置小工具的任意页面
  3. 将小工具面板中的 Recently Viewed Products 小工具拖到页面上
  4. 在右侧的小工具面板中配置显示设置

自定义 / Headless

基于 localStorage 的浏览记录跟踪:

// lib/recentlyViewed.js
const STORAGE_KEY = 'rv_products';
const MAX_ITEMS = 12;
const TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days

export function recordView(productId) {
  const items = getStoredItems();
  const filtered = items.filter(i => i.id !== productId); // remove existing entry
  const updated = [{ id: productId, viewedAt: Date.now() }, ...filtered].slice(0, MAX_ITEMS);
  try { localStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); } catch {}
}

export function getRecentlyViewedIds(excludeId = null) {
  const now = Date.now();
  return getStoredItems()
    .filter(i => now - i.viewedAt < TTL_MS && i.id !== excludeId)
    .map(i => i.id);
}

function getStoredItems() {
  try { return JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '[]'); }
  catch { return []; }
}

在商品详情页记录浏览:

// In ProductDetailPage.jsx — read localStorage only in useEffect to avoid SSR mismatch
import { useEffect } from 'react';
import { recordView } from '../lib/recentlyViewed';

export function ProductDetailPage({ product }) {
  useEffect(() => { recordView(product.id); }, [product.id]);
  return (
    <div>
      {/* product content */}
      <RecentlyViewedWidget excludeId={product.id} maxItems={4} />
    </div>
  );
}

小工具组件——从服务端重新获取最新商品数据:

// RecentlyViewedWidget.jsx
export function RecentlyViewedWidget({ excludeId, maxItems = 4 }) {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const ids = getRecentlyViewedIds(excludeId).slice(0, maxItems);
    if (ids.length === 0) return;

    fetch('/api/products/by-ids', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ids }),
    })
      .then(r => r.json())
      .then(data => {
        // Preserve the viewed order (API may return in any order)
        const map = new Map(data.products.map(p => [p.id, p]));
        setProducts(ids.map(id => map.get(id)).filter(Boolean));
      })
      .catch(() => {}); // Non-critical — fail silently
  }, [excludeId, maxItems]);

  if (products.length === 0) return null;

  return (
    <section aria-label="Recently viewed">
      <h2>Recently Viewed</h2>
      <div className="recently-viewed-grid">
        {products.map(product => (
          <article key={product.id}>
            <a href={product.url}>
              <img src={product.image} alt={product.name} loading="lazy" width="150" height="150" />
              <p>{product.name}</p>
              <p>${product.price}</p>
            </a>
          </article>
        ))}
      </div>
    </section>
  );
}

批量商品端点(Node.js):

// api/products/by-ids.js
export async function getProductsByIds(req, res) {
  const { ids } = req.body;
  if (!Array.isArray(ids) || ids.length === 0 || ids.length > 20)
    return res.status(400).json({ error: 'Invalid ids' });

  const products = await db.products.findMany({
    where: { id: { in: ids }, published: true },
    select: { id: true, name: true, price: true, image: true, url: true, inStock: true },
  });
  res.json({ products });
}

最佳实践

  • 只存储商品 ID,不存储完整商品对象——价格和库存状态会变化;渲染小工具时始终从服务端重新获取数据
  • 排除当前商品——传入 excludeId,避免显示购物者正在浏览的商品
  • 静默失败——最近浏览小工具并非关键功能;捕获所有错误并不渲染任何内容,而不是导致页面出错
  • 挂载后再填充数据——在 SSR 框架中,在 useEffect 内读取 localStorage,以避免服务端与客户端不匹配
  • 尊重隐私同意——如果 Cookie 政策要求,只有在用户接受分析 Cookie 后才写入存储
  • 考虑 TTL 过期——读取时清除超过 30 天的条目,避免显示已停售的商品

常见问题

问题解决方案
Next.js / SSR 中出现水合不匹配只在 useEffect 内读取 localStorage,不要在渲染期间读取;将状态初始化为空数组
隐私浏览模式下 localStorage 抛出异常使用 try/catch 包裹所有读写操作;当前会话改用内存数组
小工具显示缺货或已删除的商品过滤服务端响应,仅包含 published: true 的商品;不要认为已存储的 ID 能反映当前商品目录状态
同一商品在多个位置重复出现添加到列表前,先筛除该商品 ID 对应的现有条目
小工具加载时导致布局偏移数据加载期间使用具有最小高度的骨架屏预留小工具高度,或将其放在首屏以下

相关技能

  • @wishlist-save-for-later
  • @product-page-design
  • @product-comparison
  • @storefront-theming

评论

支持 Markdown