---
title: "免費開源的A/B測試解決方案 - Featureprobe"
description: "介紹如何用開源免費的 FeatureProbe 在 Next.js App Route 中實作 A/B Test，涵蓋後台設定、SDK 整合、轉換率追蹤與進階條件規則。"
canonical_url: "https://blog.markkulab.net/post/open-souce-ab-test-with-featureprobe"
author: "Mark Ku"
author_url: "https://blog.markkulab.net/author/mark-ku"
site: "Mark Ku's Blog"
date_published: "2024-03-10 01:01:35 +0800"
category: "Frontend"
tags: ["ab test", "featureprobe", "nextjs", "react", "feature flag", "analytics", "frontend"]
language: "zh-TW"
license: "CC BY 4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
attribution: "轉載或引用請註明作者並附上原文連結"
---

# 免費開源的A/B測試解決方案 - Featureprobe

## 時空背景
之前公司將外包給美國的網站優化機構(Conversion rate optimization agency)，因為美國人工及軟體月費非常的貴，且又常常將我們網站改壞，因此我
評估了幾套套A/B Test 當成替代解決方案，但大多都要收費，找了很久才找到一套開源免費，又好用的A/B Test工具 - Featureprobe。

## 了解  AB Test 能幫助我們做些什麼呢?
透過AB 我們可以得知
* 通過測試不同的頁面佈局、按鈕顏色、文案等，找出哪些元素更能激勵用戶採取期望的行動，如購買產品、註冊會員或下載應用，藉此提高轉換率。
* 測試不同的定價策略或促銷活動對銷售的影響。
* 衡量重新設計的頁面或功能，有沒有比以前更好，並識別用戶界面中的問題。

## 運作原理
透過 Featureprobe 的後台配置測試項目的出現比例，當前端在渲染時告訴前端網頁要渲染測試項目A 或是測試項目B，當使用者觸發後台設定的轉換率時，會在後台報表中顯示流量及轉換率。

## 假定測試情境
設計師設計了兩個Banner，想了解Banner A、Banner B，想了解那個Banner 的使用者點擊率比較好。

![ab test case](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/ab-test-case.jpg)

## 參考了 [官方文件](https://docs.featureprobe.com/)

## 首先，我們得先建立 FeatureProbe 容器應用程式

```
git clone https://gitee.com/featureprobe/FeatureProbe.git

cd FeatureProbe
docker compose up
```
## 再接著，訪問先前建立的[Featureprobe應用程式後台](http://localhost:4009)
username: admin  
password: Pass1234

### 新增測試事件
![create ab test case](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/create-ab-test.png)

### 接著我們開始設定預設規則，這裡的百分比，會影響測試項目的出現機率
![set default rule](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/set-default-rule.png)

### 啟用測試，並按下發佈按鈕(Publish)
![start test case](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/start-test.png)

### 定義轉換率事件，並開按下 Start iteration 按鈕，開始蒐集事件分析
![set up conversion event](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/set-up-conversion-event.png)

## 撰寫測試程式 
點選 Connect SDK 按鈕，就會跳出一些程式的範例．但範例漏了一些東西，因此我小改了一下。

### 安裝 SDK 
```
npm install featureprobe-client-sdk-react --save
```
### 前端程式範例 - 採用 Next js app route
#### 將SDK 封裝成一個HOOK - use-featureprobe.ts
```
import { FPUser, FeatureProbe } from 'featureprobe-client-sdk-react';
import { useCallback, useEffect, useRef, useState } from 'react';

/**
 * Custom hook to initialize and use FeatureProbe client, providing feature flag value,
 * loading state, and a method to track events.
 *
 * @param {string} featureKey The key of the feature flag to evaluate
 * @param {any} defaultValue The default value of the feature flag
 * @param {FPUser} user The user object for feature evaluation
 * @returns An object containing the feature flag value, loading state, and a track method
 */
const useFeatureProbe = (featureKey: string, defaultValue: any, user: FPUser) => {
    const [featureValue, setFeatureValue] = useState<any>(defaultValue);
    const [isLoading, setIsLoading] = useState(true);
    const fpClientRef = useRef<FeatureProbe | null>(null);

    useEffect(() => {
        if (!fpClientRef.current) {
            const client = new FeatureProbe({
                remoteUrl: 'http://127.0.0.1:4007',
                user: user,
                clientSdkKey: 'client-xxxxxxxxxxxxx',
                refreshInterval: 5000,
            });

            client.start();
            fpClientRef.current = client;

            // Listener when client is ready
            const handleReady = () => {
                let result: any;

                if (typeof defaultValue === 'boolean') {
                    result = client.boolValue(featureKey, defaultValue);
                } else if (typeof defaultValue === 'string') {
                    result = client.stringValue(featureKey, defaultValue);
                }
                // Add more types as needed

                setFeatureValue(result);
                setIsLoading(false);
            };

            client.on('ready', handleReady);

            // Cleanup
            return () => {
                // client.off('ready', handleReady);
                // client.stop();
            };
        }
    }, []); // This effect depends on user, since user-specific features may require re-initialization

    // Method to track events
    const trackEvent = useCallback((eventName: string) => {
        if (fpClientRef.current) {
            fpClientRef.current.track(eventName);
        }
    }, []);

    return { featureValue, isLoading, trackEvent, client: fpClientRef.current };
};

export default useFeatureProbe;
```

#### 再來撰寫 Banner A Component
```
'use client';
const BannerA = ({ featureValue, trackEvent }: IFeatureProbe) => {
    const clickHandler = () => {
        alert(featureValue + ' clicked');
        trackEvent('banner_click');
    };

    return (
        <div id="boolean-result" onClick={clickHandler}>
            {featureValue.toString()}
        </div>
    );
};

export default BannerA;

interface IFeatureProbe {
    featureValue: any;
    trackEvent: (eventName: string) => void;
}

```
#### 再來撰寫 Banner B Component
```
'use client';
const BannerB = ({ featureValue, trackEvent }: IFeatureProbe) => {
    const clickHandler = () => {
        alert(featureValue + ' clicked');
        trackEvent('banner_click');
    };

    return (
        <div id="boolean-result" onClick={clickHandler}>
            {featureValue.toString()}
        </div>
    );
};

export default BannerB;

interface IFeatureProbe {
    featureValue: any;
    trackEvent: (eventName: string) => void;
}
```

#### 新增 AB Test 的頁面
```
'use client';

import useFeatureProbe from '@/hooks/use-featureprobe';
import dynamic from 'next/dynamic';
const BannerA = dynamic(() => import('@components/ab-test/banner-a'));
const BannerB = dynamic(() => import('@components/ab-test/banner-b'));

import { FPUser } from 'featureprobe-client-sdk-react';

export default function Page() {
    const user = new FPUser();
    const { featureValue, isLoading, trackEvent } = useFeatureProbe('Banner_test', '', user);

    return (
        <main className="flex flex-col items-center justify-between min-h-screen p-24">
            {isLoading && <div>Loading...</div>}

            {!isLoading && <BannerA featureValue={featureValue} trackEvent={trackEvent}></BannerA>}
            {!isLoading && <BannerB featureValue={featureValue} trackEvent={trackEvent}></BannerB>}
        </main>
    );
}
```

## P.S.因為我們使用 Next js 因此，在做 A/B Test 的頁面不能把頁面cache 起來(ISR)，A/B Test 頁面必須是SSR 或是CSR
## 此時只要在畫面重新整理，就會依據先前設定的比例出現相對應的測試情境
![final-test](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/final-test.png)

## 如何除錯 (畫面上按下 Open 才開啟除錯模式)
![debug tools](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/debug-tracker.png)

## 接著，我們來看數據報表
### 流量報表
![traffic report](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/traffic-report.png)

### 轉換率報表
![conversion report](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/conversion-report.png)

## 進階應用 - 依據傳入條件及分組規則獲取相對應的測試情境

當我建立規則中指定 City 等於台北則，顯示 Banner A，City 等於高雄，就顯示Banner B ，在程式中指定使用者為台北的用戶，就可以彈性依據不同的資料，而給不同的測試。  

P.S. Featureprobe 本身沒有 sticky cookie 的機制，但它的程式蠻靈活的，可以透過自己寫入讀取cookie及後台設定條件，讓使用者在測試階段顯示一樣的畫面。

```
const user = new FPUser().with('City', '台北');
```
![set up rules](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/set-up-rules.png)

當然不僅有 true 和false ，在建立時可以選擇其他種型態，像string ，這樣就能一次測試多種的測試情境
![set up return trype](https://blog.markkulab.net/content/markku/posts/open-souce-ab-test-with-featureprobe/images/set-up-return-trype.png)

## 最後
做A/B Test 的成本蠻高的，應該是拿重要功能，小範圍的去做比較，透過使用Featureprobe進行A/B測試，我們可以快速得到了實用的洞察，Featureprobe以其直觀的用戶界面和強大的分析功能脫穎而出，使測試設計和結果評估變得簡單高效，我們確定了哪些改動最能提高用戶參與度和轉化率，從而協助我們的產品迭代，Featureprobe的靈活性和易用性對於支持多樣化測試非常有幫助，確保我們能夠基於數據做出精確的產品決策。



## 參考資料
* [如何提供一个可信的AB测试解决方案](https://tech.meituan.com/2023/08/24/ab-test-practice-in-meituan.html)
* [提升产品功能发布效率的5个开源项目](https://zhuanlan.zhihu.com/p/628587038)

---

## 關於本文與作者

本文出自 [Mark Ku's Blog](https://blog.markkulab.net/post/open-souce-ab-test-with-featureprobe)

授權條款： [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — 轉載或引用請註明作者並附上原文連結

### 關於作者

**[Mark Ku](https://blog.markkulab.net/author/mark-ku)** — Software Solution Provider

- 10+ 年資深軟體工程師，現為 AI 應用 Builder
- 專注大型平台架構設計，從北美電商到AI SaaS訂閱收費系統
- 結合 AI Agent 與自動化，打造高效可演進的產品技術基礎

### 作者開發的免費工具

以下工具皆可免費使用：

- [免費 PDF 簽名工具](https://blog.markkulab.net/tools/pdf-sign): 線上 PDF 簽名工具，瀏覽器內完成手繪、打字、上傳簽名，可拖曳放置、縮放、下載。所有處理都在你的裝置完成，檔案不會上傳。
- [VS Code Refactory](https://blog.markkulab.net/tools/refactory): Refactory 是一款 VS Code 重構擴充套件：34 個重構動作、37 條 code smell 檢查、Code Health 儀表板、18 種語言、534 支測試。懂你的專案慣例：介面放哪、DI 註冊寫在哪、'use client' 該不該加；還會用 git 修改頻率 × 複雜度排出「該先修哪個檔案」，並一鍵把壞味道交給你自己電腦上的 Claude Code 修。免費使用，原始碼不離開你的機器。
- [DB-Kit 資料庫管理工具](https://blog.markkulab.net/tools/db-kit): DB-Kit 是一個用 Tauri + Rust + React 打造的輕量跨平台資料庫管理工具，用單一一致的介面同時管理 MySQL、MariaDB、PostgreSQL、SQL Server、Oracle、SQLite、MongoDB、Redis、Kafka、Elasticsearch 與 RabbitMQ 十一種資料來源：連線密碼以 OS keychain 加密、SSH Tunnel、完整 CRUD、視覺化查詢建構器、多結果集同時顯示、跨連線資料傳輸與比對同步、Excel / CSV 匯入匯出、執行計畫視覺化、ER 圖、排程備份、SQL 壓力測試（p50～p99 延遲百分位）、15 條規則的 SQL 審查、Kafka 訊息瀏覽與監控告警；繁中 / 英文雙語介面，內建 AI 助手（自然語言生成 SQL、AI 審查與調校建議）與命令列工具 dbk。免費開源（MIT），提供 Windows / macOS / Linux 安裝檔。
- [VS Code Super Mermaid](https://blog.markkulab.net/tools/super-mermaid): Super Mermaid 是一款 VS Code 擴充套件：開箱即用的漂亮 Mermaid 圖表，自動上色、即時預覽、滑鼠平移縮放、PNG / SVG 高解析匯出，內建 21 種範本與多種主題。免費開源（MIT）。
- [React Super Mermaid](https://blog.markkulab.net/tools/react-super-mermaid): react-super-mermaid 是一個開源 React 元件庫：一行 <MermaidViewer> 即可渲染漂亮的 Mermaid 圖表，內建 colorful / sketch 主題、平移縮放、圖內搜尋、SVG / PNG 高解析匯出。輕量、SSR 安全、完整 TypeScript 型別。免費開源（MIT）。
- [Jira / Confluence Super Mermaid](https://blog.markkulab.net/tools/jira-super-mermaid): Atlassian Forge app：在 Jira issue 與 Confluence 內文直接寫 Mermaid 語法，畫流程圖、時序圖、狀態機與甘特圖。11 種圖表、SVG / PNG 匯出、明暗主題、完整中日韓文字支援。取得 Runs on Atlassian 資格：圖表存在你自己的站台，app 不呼叫任何第三方服務。免費，即將上架 Atlassian Marketplace。
- [Mermaid 線上預覽](https://blog.markkulab.net/tools/mermaid-preview): 在瀏覽器裡寫 Mermaid、即時看圖，整張圖表壓進網址就能分享。免註冊、不上傳伺服器，相容 mermaid.live 的分享連結。
- [React Intl Phone Number](https://blog.markkulab.net/tools/react-intl-phone-number): react-intl-phone-number 是一個開源 React 元件：framework-agnostic、不依賴 antd，提供 E.164 進出、可搜尋國旗 / 國碼下拉、可配置驗證等級（strict / mobile-strict / loose）、可主題化 CSS 與 i18n，電話邏輯由 google-libphonenumber 驅動。輕量、完整 TypeScript 型別。免費開源（MIT）。
- [Uptime Kuma Cluster](https://blog.markkulab.net/tools/uptime-kuma-cluster): 把單機版 Uptime Kuma 改造成高可用叢集：OpenResty + Lua 智慧負載平衡、MariaDB 共享狀態、健康檢查與自動 Failover，附叢集管理 REST API，一行 Docker Compose 啟動。免費開源（MIT）。
- [特教專案](https://blog.markkulab.net/education): 為特殊教育學生製作的學習教材

### 每日 Podcast

- [科技新鮮事](https://blog.markkulab.net/category/tech-news): 每日精選 AI 與科技趨勢，透過語音摘要快速掌握最新技術動態，涵蓋 AI 應用、軟體架構、DevOps 與工程實戰。 — RSS: https://blog.markkulab.net/feed.xml
- [AI股市蝦聊](https://blog.markkulab.net/category/ai-stock-chat): 每個交易日用 AI 分析台股盤勢，以雙人對話聊當天的盤中觀察與隔日預測。 — RSS: https://blog.markkulab.net/ai-stock-chat/feed.xml

### 電子報

[訂閱電子報](https://blog.markkulab.net/subscribe) — 第一時間收到新文章通知，無垃圾信、隨時可取消訂閱。
