가이드

Voluum, Redtrack, ClickFlare에 A/B 테스트를 연동하는 방법

소개 디지털 마케팅에서 A/B 테스트는 캠페인을 성공시키는 데 필수입니다. LanderLab은 이제 같은 URL로 여러 변형을 번갈아 노출하는 서버 사이드 A/B 테스트를 설정할 수 있는 훌륭한 도구를 제공합니다.

Bruno Agalliu2분 분량

소개

디지털 마케팅에서 A/B 테스트는 캠페인을 성공시키는 데 필수입니다. LanderLab은 이제 같은 URL로 여러 변형을 번갈아 노출하는 서버 사이드 A/B 테스트를 설정할 수 있는 훌륭한 도구를 제공합니다. 이번 업데이트로 사용자는 랜딩 페이지를 A/B 테스트하고, 선호하는 트래커에서 결과를 교차 확인할 수 있습니다.

LanderLab으로 A/B 테스트를 해야 하는 이유

대부분의 마케터는 ClickFlare, Voluum, Redtrack 같은 자체 트래킹 소프트웨어로 랜딩 페이지를 스플릿 테스트하며, 대부분의 경우 그것만으로 충분합니다. 하지만 트래커로는 스플릿 테스트를 할 수 없는 경우도 있습니다. 프리미엄 트래픽 소스는 리디렉션을 허용하지 않아, 최종 도착 URL이 랜딩 페이지 URL과 같아야 합니다.

이럴 때 LanderLab의 서버 사이드 A/B 테스트가 유용합니다. 흐름에서 리디렉션을 하나 없앨 수 있기 때문입니다. LanderLab에서 A/B 테스트가 어떻게 작동하는지 자세히 알고 싶다면 이 글을 확인해 보세요. 무엇보다 좋은 점은 LanderLab을 대부분의 최신 트래커와 연동할 수 있다는 것입니다.

A/B 테스트

개념은 간단합니다. LanderLab의 A/B 테스트를 모든 트래커가 제공하는 다이렉트 트래킹 방식과 결합하는 것입니다. 기본기를 다루기 위해, 이 글에서는 ClickFlare, Voluum, Redtrack으로 이 구성을 구현하는 방법을 안내합니다.

ClickFlare로 A/B 테스트 설정하기

ClickFlare의 다이렉트 트래킹 방식은 이 구성에 완벽한 조합이 되어 줍니다. 두 플랫폼은 같은 회사가 만들고 개발했으니, 둘을 함께 써서 더 긴밀하게 연동하는 것이 당연히 합리적입니다. ClickFlare가 제공하는 다이렉트 트래킹에 대해 더 알고 싶다면 이 글을 읽어 보세요.

이 구성을 구현할 때 따라야 할 기본 단계는 2가지이며, 같은 단계를 Voluum과 Redtrack에도 적용할 수 있습니다. 첫 번째 단계는 ClickFlare에서 variantID를 리포트하기 위한 새 트래킹 파라미터를 정의하는 방법입니다.

ClickFlare의 VariantID

트래커에서 variantID를 추적하려면 다이렉트 트래킹 스크립트를 일부 수정해야 합니다. 아래 스크립트에는 교체해야 하는 변수가 2개 들어 있습니다. “REPLACE_ME_CAMPAIGN_ID”와 “REPLACE_ME_CUSTOM_DOMAIN”입니다. 준비가 끝나면 전체 코드를 LanderLab의 Settings > Custom Code > Header Section에 넣어야 합니다.

<script> ! function() {
    "use strict";
    var t = "lp_ref",
        n = "cpid",
        e = "lpurl",
        r = "REPLACE_ME_CUSTOM_DOMAIN",
        c = "(?<domain>http(?:s?)://[^/]*)".concat("/cf/click"),
        a = "(?:(?:/(?<cta>[1-9][0-9]*)/?)|(?:/))?",
        i = "^".concat(c).concat(a).concat("(?:$|(\\?.*))"),
        o = 'javascript:window.clickflare.l="(?<original_link>'.concat(c).concat(a, '("|(\\?[^"]*"))).*'),
        s = function() {
            return new RegExp(i, "")
        },
        u = function() {
            return new RegExp(o, "")
        };
    function l(t) {
        var n = function(t) {
            return t.replace(s(), (function(t) {
                for (var n = [], e = 1; e < arguments.length; e++) n[e - 1] = arguments[e];
                var c = n[n.length - 1].domain;
                return t.replace(c, r)
            }))
        }(t);
        return 'javascript:window.clickflare.l="'.concat(n, '"; void 0;')
    }
    function f(t, n) {
        if (n && t && n.apply(document, [t]), /loaded|interactive|complete/.test(document.readyState))
            for (var e = 0, r = document.links.length; e < r; e++)
                if (s().test(document.links[e].href)) {
                    var c = document.links[e];
                    window.clickflare.links_replaced.has(c) || (c.href = l(c.href), window.clickflare.links_replaced.add(c))
                }
    }! function(c, a) {
        var i = document.onreadystatechange;
        window.clickflare || (window.clickflare = {
            listeners: {},
            customParams: {},
            links_replaced: new Set,
            addEventListener: function(t, n) {
                var e = this.listeners[t] || [];
                e.includes(n) || e.push(n), this.listeners[t] = e
            },
            dispatchEvent: function(t, n) {
                n && (this.customParams[t] = n), (this.listeners[t] || []).forEach((function(t) {
                    return t(n)
                }))
            },
            push: function(t, n) {
                n && (this.customParams[t] = n), (this.listeners[t] || []).forEach((function(t) {
                    return t(n)
                }))
            }
        }, document.onreadystatechange = function(t) {
            return f(t, i)
        }, f(null, i), setTimeout((function() {
            ! function(c, a) {
                var i, o = function(c, a) {
                        var i = new URL("".concat(r).concat(c)),
                            o = "{",
                            s = o + o;
                        a.startsWith(s) || i.searchParams.set(n, a);
                        return i.searchParams.append(t, document.referrer), i.searchParams.append(e, location.href), i.searchParams.append("variantId", window.LL_VARIANT_ID), i.searchParams.append("lpt", document.title), i.searchParams.append("t", (new Date).getTime().toString()), i.toString()
                    }(c, a),
                    s = document.createElement("script"),
                    l = document.scripts[0];
                s.async = 1, s.src = o, s.onerror = function() {
                    ! function() {
                        for (var t = function(t, n) {
                                var e = document.links[t];
                                u().test(e.href) && setTimeout((function() {
                                    e && e.setAttribute("href", function(t) {
                                        var n = t.match(u());
                                        if (n) {
                                            var e = (n.groups || {}).original_link;
                                            return e ? e.slice(0, -1) : t
                                        }
                                        return t
                                    }(e.href))
                                }))
                            }, n = 0, e = document.links.length; n < e; n++) t(n)
                    }()
                }, null === (i = l.parentNode) || void 0 === i || i.insertBefore(s, l)
            }(c, a)
        })))
    }("".concat("/cf/tags", "/").concat(new URL(window.location.href).searchParams.get("cftmid") || "{{__CONTAINER_ID__}}"), new URL(window.location.href).searchParams.get(n) || "REPLACE_ME_CAMPAIGN_ID")
}(); </script>

Voluum으로 A/B 테스트 설정하기

Voluum은 제휴 마케팅에서 가장 오래된 트래커 중 하나로, 지금도 시장에서 큰 비중을 차지하고 있습니다. 이 트래커에 variantID를 리포트할 때도 앞에서와 같은 방식으로 다이렉트 트래킹을 활용합니다.

Voluum의 VariantID

VariantID는 트래픽 소스 템플릿의 맨 아래에 추가하지만, 어떤 순서로 넣어도 크게 상관없습니다. 용어를 통일하기 위해 교체해야 하는 변수 2개도 같습니다. “REPLACE_ME_CAMPAIGN_ID”와 “REPLACE_ME_CUSTOM_DOMAIN”입니다. 다음 코드를 복사해 LanderLab의 커스텀 코드 영역에 붙여 넣으면 설정이 완료됩니다.

<<b>style</b>>.dtpcnt{opacity: 0;}</<b>style</b>>
    <<b>script</b>>
    (function(d, c, k, l, r, t, g, u, A, e, m, v, B, a, n, p, h, q, w, D, x) {
        function y() { for (var f = c.querySelectorAll(".dtpcnt"), b = 0, a = f.length; b < a; b++) f[b][u] = f[b][u].replace(/(^|\s+)dtpcnt($|\s+)/g, "") }
        function C(a, b, d, e) { var f = new Date;
            f.setTime(f.getTime() + (e || 864E5));
            c.cookie = a + "=" + b + "; " + d + "samesite=Strict; expires=" + f.toGMTString() + "; path=/";
            k.setItem(a, b);
            k.setItem(a + "-expires", f.getTime()) }
        function z(a) {
            var b = c.cookie.match(new RegExp("(^| )" + a + "=([^;]+)"));
            return b ? b.pop() : k.getItem(a + "-expires") && +k.getItem(a +
                "-expires") > (new Date).getTime() ? k.getItem(a) : null
        }
        x = "https:" === d.location.protocol ? "secure; " : "";
        d[e] || (d[e] = function() {
            (d[e].q = d[e].q || []).push(arguments) }, p = c[r], c[r] = function() { p && p.apply(this, arguments); if (d[e] && !d[e].hasOwnProperty("params") && /loaded|interactive|complete/.test(c.readyState))
                for (; a = c[t][m++];) /\/?click\/?($|(\/[0-9]+)?$)/.test(a.pathname) && (a[g] = "javascrip" + d.postMessage.toString().slice(4, 5) + ":" + e + '.l="' + a[g] + '",void 0') }, setTimeout(function() {
            (q = /[?&]cpid(=([^&#]*)|&|#|$)/.exec(d.location.href)) &&
            q[2] && (h = q[2], w = z("vl-" + h));
            var f = z("vl-cep"),
                b = location[g];
            if ("savedCep" === B && f && (!h || "undefined" === typeof h) && 0 > b.indexOf("cep=")) { var e = -1 < b.indexOf("?") ? "&" : "?";
                b += e + f } a = c.createElement("script");
            n = c.scripts[0];
            a.defer = 1;
            a.src = v + (-1 === v.indexOf("?") ? "?" : "&") + "lpref=" + l(c.referrer) + "&lpurl=" + l(b) + "&lpt=" + l(c.title) + "&vtm=" + (new Date).getTime() + (w ? "&uw=no" : "") + "&variantId=" + window.LL_VARIANT_ID ;
            a[A] = function() { for (m = 0; a = c[t][m++];) /dtpCallback\.l/.test(a[g]) && (a[g] = decodeURIComponent(a[g]).match(/dtpCallback\.l="([^"]+)/)[1]);
                y() };
            n.parentNode.insertBefore(a, n);
            h && C("vl-" + h, "1", x)
        }, 0), setTimeout(y, 7E3))
    })(window, document, localStorage, encodeURIComponent, "onreadystatechange", "links", "href", "className", "onerror", "dtpCallback", 0, "REPLACE_ME_CUSTOM_DOMAIN/d/REPLACE_ME_CAMPAIGN_ID.js", "savedCep");
    </<b>script</b>>
    <<b>noscript</b>>
        <<b>link</b> href="REPLACE_ME_CUSTOM_DOMAIN/d/REPLACE_ME_CAMPAIGN_ID.js?noscript=true&lpurl=" rel="stylesheet" /></<b>noscript</b>>

Redtrack으로 A/B 테스트 설정하기

마지막으로 같은 구성을 Redtrack에도 구현해 보겠습니다. 이 트래커는 시장에서 상당한 비중을 차지하며, ClickFlare나 Voluum과 같은 기능을 제공합니다.

다만 나머지 두 트래커와 달리, 일부 토큰은 시스템 토큰 전용이기 때문에 variantID를 특정 토큰에 할당해야 합니다. 이 예시에서는 variantID를 sub18에 할당했고, 다이렉트 트래킹 스크립트에도 똑같이 반영됩니다.

Redtrack의 VariantID

 

“REPLACE_ME_CAMPAIGN_ID”와 “REPLACE_ME_CUSTOM_DOMAIN”을 교체하면 코드를 LanderLab에서 바로 사용할 수 있습니다.

<script>
function getCookie(name) {
    var value = "; " + document.cookie;
    var parts = value.split("; " + name + "=");
    if (parts.length === 2) {
        return parts.pop().split(";").shift();
    }
}
var trackerURL = "REPLACE_ME_CUSTOM_DOMAIN";
var campaignID = "REPLACE_ME_CAMPAIGN_ID";
var cachebuster = Math.round(new Date().getTime() / 1000);
var rtkClickID;
var rtkfbp = getCookie('_fbp') || '';
var rtkfbc = getCookie('_fbc') || '';
var LL_VARIANT_ID = window.LL_VARIANT_ID;
var locSearch = window.location.search;
var urlParams = new URLSearchParams(locSearch);
var pixelParams = "&" + locSearch.substr(1) + "&sub18=" + LL_VARIANT_ID + "&sub19=" + rtkfbp + "&sub20=" + rtkfbc
if (campaignID == "") {
    campaignID = urlParams.get('rtkcmpid')
}
var initialSrc = "https://"+ trackerURL + "/" + campaignID + "?format=json";

function stripTrailingSlash(str) {
    return str.replace(/\/$/, "");
}

var rawData;

function fixHrefWithClick(
    _rawData,
    _cachebuster,
    _rtkClickID
) {
    document.querySelectorAll('a').forEach(function(el) {
        if (el.href.indexOf(trackerURL + "/click") > -1) {
            if (el.href.indexOf('?') > -1) {
                el.href = stripTrailingSlash(el.href) + "&clickid=" + (_rtkClickID || _rawData.clickid) + "&rtkck=" + _cachebuster
            } else {
                el.href = stripTrailingSlash(el.href) + "?clickid=" + (_rtkClickID || _rawData.clickid) + "&rtkck=" + _cachebuster
            }
        }
        if (el.href.indexOf("clickid={clickid}") > -1) {
            el.href = el.href.replace(/{clickid}/, _rawData.clickid) + "&rtkck=" + _cachebuster;
        }
    });
}

setTimeout(function() {
    if (!urlParams.get('rtkcid')) {
        xhr = new XMLHttpRequest;
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {
                rawData = JSON.parse(xhr.responseText);
                rtkClickID = rawData.clickid
                setCookie();
                // fixHrefWithClick(rawData, cachebuster)
                document.querySelectorAll('a').forEach(function(el) {
                    if (el.href.indexOf(trackerURL + "/click") > -1) {
                        if (el.href.indexOf('?') > -1) {
                            el.href = stripTrailingSlash(el.href) + "&clickid=" + rawData.clickid + "&rtkck=" + cachebuster
                        } else {
                            el.href = stripTrailingSlash(el.href) + "?clickid=" + rawData.clickid + "&rtkck=" + cachebuster
                        }
                    }
                    if (el.href.indexOf("clickid={clickid}") > -1) {
                        el.href = el.href.replace(/{clickid}/, rawData.clickid) + "&rtkck=" + cachebuster;
                    }
                });
                xhrr = new XMLHttpRequest;
                xhrr.open("GET", "https://"+ trackerURL +"/view?clickid=" + rawData.clickid)
                xhrr.send();
            }
        }
        xhr.open("GET", initialSrc + pixelParams)
        xhr.send();
    } else {
        rtkClickID = urlParams.get('rtkcid')
        setCookie();
        xhrTrack = new XMLHttpRequest;
        xhrTrack.open("GET", "https://"+trackerURL+"/view?clickid=" + rtkClickID)
        xhrTrack.send();
        // fixHrefWithClick(rawData, cachebuster, rtkClickID)
        document.querySelectorAll('a').forEach(function(el) {
            if (el.href.indexOf(trackerURL+ "/click") > -1) {
                if (el.href.indexOf('?') > -1) {
                    el.href = stripTrailingSlash(el.href) + "&clickid=" + rtkClickID + "&rtkck=" + cachebuster
                } else {
                    el.href = stripTrailingSlash(el.href) + "?clickid=" + rtkClickID + "&rtkck=" + cachebuster
                }
            }
            if (el.href.indexOf("clickid={clickid}") > -1) {
                el.href = el.href.replace(/{clickid}/, rawData.clickid) + "&rtkck=" + cachebuster;
            }
        });
    }
}, 5e1)

function setCookie() {
    var cookieName = "rtkclickid-store",
        cookieValue = rtkClickID,
        expirationTime = 86400 * 30 * 1000,
        date = new Date(),
        dateTimeNow = date.getTime();
    date.setTime(dateTimeNow + expirationTime);
    var date = date.toUTCString();
    document.cookie = cookieName + "=" + cookieValue + "; expires=" + date + "; path=/;"
}
    </script>

결론

이제 데이터가 트래커에 리포트되므로 특정 변형이 이벤트를 얼마나 가져오는지 실제로 측정할 수 있습니다. 승리한 변형을 알게 되면 A/B 테스트를 종료하고 그 변형을 메인 변형으로 설정하면 됩니다. 차단당할 위험 없이 오늘부터 LanderLab으로 스플릿 테스트를 시작해 보세요.

트래커와 함께 A/B 테스트를 최적화하고 싶으신가요?
지금 무료 체험을 시작하고 ClickFlare, Voluum, Redtrack과 함께 LanderLab의 고급 A/B 테스트를 사용해 보세요!

이어서 읽기

가이드의 다른 글.

주제

주제별로 둘러보기

모든 글은 하나의 주제에 속합니다. 지금 운영 중인 캠페인과 가장 가까운 주제부터 읽어 보세요.

플레이북을 읽으세요.그다음 페이지를 만드세요.

무료로 시작하세요. 랜딩 페이지를 만들고, 내 도메인에 게시하고, 모든 리드를 구매자에게 라우팅하세요. 다음 캠페인이 시작되기 전에 모두 끝낼 수 있습니다.

프롬프트 하나에서 검증된 리드까지

프롬프트 하나가 라이브 태양광 견적 퍼널이 되고, 모든 리드가 구매자에게 라우팅되며, 예산을 갉아먹기 전에 새는 곳이 표시되는 과정을 보세요.

0:00
0:59