Mark Ku's Blog

Background

Our company used to outsource website optimization to a Conversion Rate Optimization (CRO) agency in the United States. Since US labor and software monthly fees are very expensive, and they often broke our site in the process, I evaluated several A/B testing tools as a replacement. Most of them charged fees, and after a long search I finally found one that is open source, free, and easy to use - FeatureProbe.

What can A/B testing actually help us with?

Through A/B testing we can learn:

  • By testing different page layouts, button colors, and copy, we can identify which elements better motivate users to take desired actions, such as buying products, registering, or downloading apps, thereby improving conversion rates.
  • The impact of different pricing strategies or promotions on sales.
  • Whether a redesigned page or feature is actually better than the previous version, and identify problems in the user interface.

How it works

You configure the rollout percentage of test variants in the FeatureProbe admin panel. When the front-end renders, FeatureProbe tells the page which variant (A or B) to render. When users trigger the conversion event configured in the admin panel, traffic and conversion rate appear in the dashboard reports.

Hypothetical test scenario

A designer created two banners, Banner A and Banner B, and wants to know which banner has a better click-through rate.

ab test case
ab test case

Following the official docs

First, set up the FeatureProbe container application

git clone https://gitee.com/featureprobe/FeatureProbe.git

cd FeatureProbe
docker compose up

Next, visit the FeatureProbe admin panel you just deployed

username: admin
password: Pass1234

Create a test event

create ab test case
create ab test case

Set the default rule. The percentage here controls the probability of each variant appearing

set default rule
set default rule

Enable the test and click Publish

start test case
start test case

Define the conversion event and click Start iteration to begin collecting analytics

set up conversion event
set up conversion event

Writing the test code

Click the Connect SDK button and FeatureProbe will pop up some sample code. The samples are missing a few things, so I made small adjustments.

Install the SDK

npm install featureprobe-client-sdk-react --save

Front-end code example - using Next.js App Router

Wrap the SDK as a 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;

Then write the 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;
}

And the 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;
}

Add the A/B test page

'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. Because we use Next.js, the A/B test page must not be cached (no ISR). It must be SSR or CSR.

Refresh the page and the corresponding variant will appear according to the configured ratio

final-test
final-test

How to debug (click Open in the UI to enter debug mode)

debug tools
debug tools

Now let's look at the data reports

Traffic report

traffic report
traffic report

Conversion report

conversion report
conversion report

Advanced - retrieve variants based on input conditions and segmentation rules

When I create a rule that says "if City equals Taipei show Banner A, if City equals Kaohsiung show Banner B", I can specify the user's city in code, which lets me flexibly serve different tests based on different data.

P.S. FeatureProbe itself has no sticky cookie mechanism, but its programming model is quite flexible. You can implement cookie read/write yourself and combine it with admin-side rules so the same user sees the same variant throughout the test.

const user = new FPUser().with('City', '台北');
set up rules
set up rules

Of course, variants are not limited to true/false. When creating a flag you can choose other types like string, allowing you to test multiple variants at once. set up return trype

Wrap-up

A/B testing has a high cost, so it's best applied to important features in a small, targeted scope. Through FeatureProbe, we got useful insights quickly. FeatureProbe stands out with its intuitive UI and powerful analytics, making test design and result evaluation simple and efficient. We identified which changes most improved user engagement and conversion, helping our product iteration. FeatureProbe's flexibility and ease of use are very helpful for supporting diverse tests, ensuring we can make precise product decisions based on data.

References

Author

Mark Ku

擁有 10+ 年經驗的資深軟體工程師,現為 AI 應用 Builder,專注於大型平台架構與簡化複雜系統設計,從電商系統到訂閱與收費平台,結合 AI Agent、AI 整合與自動化開發,打造高效率且可持續演進的產品技術基礎。Read More

Found this useful?

The author's free tools, daily podcasts and newsletter are all here.

Mark Ku · This article is licensed under CC BY 4.0. Credit the author and link back to the original when reusing it.

Comments

Subscribe to Newsletter

Subscribe to get new posts delivered instantly — never miss a tech share.

By submitting, you agree to receive emails. You can anytime.

Popular Posts

View all
Mark Ku
··602

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution
Mark Ku
··490

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.
Mark Ku
··333

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki
Mark Ku
··264

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning
Mark Ku
··221

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1
Mark Ku
··215

Setting Up Samba on Ubuntu to Share Folders with Windows 11

Setting Up Samba on Ubuntu to Share Folders with Windows 11