如何在 Astro 中设置 A/B 测试(译)

A/B 测试可以帮助你通过比较变更对关键指标的影响来改进 Astro 应用。为了展示如何设置 A/B 测试,我们将创建一个基本的 Astro 应用,添加 PostHog,创建一个 A/B 测试,并实现其代码。

整体实现思路

  1. 环境准备

    • 安装 Node.js(v18+)并初始化 Astro 项目
    • 创建基础页面结构(标题 + 按钮交互)
  2. 集成分析工具

    • 通过 PostHog Web 片段注入 SDK
    • 创建布局组件统一管理监测代码
    • 验证事件采集功能
  3. 定义实验指标

    • 捕获按钮点击自定义事件
    • 配置事件属性关联用户行为
  4. 创建 A/B 测试实验分组

    • 在 PostHog 配置多版本实验参数
    • 设置核心转化指标(按钮点击率)
  5. 实验逻辑实现

    • 客户端渲染:实时获取功能标志但存在 UI 闪烁
    • 服务端渲染:通过 Node SDK 预取标志,提升首屏稳定性

分组实现

客户端渲染实现

// src/components/posthog.astro
loaded: (posthog) => {
  posthog.onFeatureFlags(() => {
    const button = document.querySelector('.main-button');
    const variant = posthog.getFeatureFlag('my-cool-experiment');
    button.innerText = {
      control: '控制变体',
      test: '测试变体'
    }[variant] || '默认文案';
  });
}

实现步骤:

  1. 在 PostHog 加载完成后注册功能标志监听
  2. 通过 CSS 选择器获取按钮 DOM 元素
  3. 根据返回的 variant 值动态更新按钮文案
  4. 自动处理标志未生效时的默认状态

服务端渲染实现

// src/posthog-node.js
export default function PostHogNode() {
  if (!posthogClient) {
    posthogClient = new PostHog('<ph_key>', {
      host: 'https://us.i.posthog.com',
      fetch: (url, options) => fetch(url, { ...options, next: { revalipubDate: 60 } })
    });
  }
  return posthogClient;
}

// pages/index.astro
const distinctId = ctx.cookies.get('distinct_id') || crypto.randomUUID();
const variant = await PostHogNode().getFeatureFlag('my-cool-experiment', distinctId);

核心配置:

  1. 创建 Node.js 客户端实例并配置缓存策略
  2. 通过 cookies 或随机生成稳定 distinctId
  3. 服务端预取功能标志状态
  4. 同步设置客户端 distinctId 保持一致性

方案对比

维度客户端渲染服务端渲染
首屏加载存在 UI 闪烁无内容抖动
数据时效性实时更新标志状态需要配置缓存刷新策略
实现复杂度仅需前端逻辑需要 Node 环境支持
SEO 友好性客户端状态不影响爬虫服务端直出完整内容
用户 ID 管理依赖浏览器指纹可定制 ID 生成策略

最佳实践建议:

1. 创建 Astro 应用

首先,确保已安装 Node.js(版本 18.0 或更新)。然后,创建一个新的 Astro 应用:

终端

npm create astro@latest

在命令行中提示时,命名你的新项目目录(我们选择 astro-ab-test),选择 Empty 作为新项目,选择 No 使用 TypeScript,安装依赖项,并选择 No 创建 git 仓库。

接下来,将 src/pages/index.astro 中的代码替换为一个简单的标题和按钮:

index.astro

---


---
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
        <meta name="viewport" content="width=device-width" />
        <meta name="generator" content={Astro.generator} />
        <title>Astro</title>
    </head>
    <body>
        <h1>Astro A/B 测试</h1>
        <button class="main-button">点击我!</button>
    </body>
</html>

运行 npm run dev 并导航到 http://localhost:4321 查看你的应用。

基本 Astro 应用

2. 在应用中添加 PostHog

应用设置完成后,是时候安装并设置 PostHog 了。如果你没有 PostHog 实例,可以 免费注册

完成后,回到你的 Astro 项目,在 src 文件夹中创建一个新的 components 文件夹。在这个文件夹中,创建一个 posthog.astro 文件

终端

cd ./src
mkdir components
cd ./components
touch posthog.astro

在这个文件中,添加你的 Web 片段,你可以在 项目设置 中找到。

posthog.astro

---


---
<script>
  !function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys getNextSurveyStep onSessionId".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
  posthog.init(
    '<ph_project_api_key>',
    {
      api_host:'https://us.i.posthog.com',
    }
  )
</script>

下一步是创建一个 布局,我们将在其中使用 posthog.astro。在 src 中创建一个新的 layouts 文件夹,然后创建一个新文件 Layout.astro

终端

cd .. && cd .. # 如果你仍在 src/components/posthog.astro,则返回基础目录
cd ./src
mkdir layouts
cd ./layouts
touch Layout.astro

将以下代码添加到 Layout.astro

Layout.astro

---
import PostHog from '../components/posthog.astro'
---
<head>
    <PostHog />
</head>

最后,更新 index.astro 以使用新的布局:

index.astro

---
import Layout from '../layouts/Layout.astro';
---
<Layout>
    <html lang="en">
        <head>
            <meta charset="utf-8" />
            <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
            <meta name="viewport" content="width=device-width" />
            <meta name="generator" content={Astro.generator} />
            <title>Astro</title>
        </head>
        <body>
            <h1>Astro A/B 测试</h1>
            <button class="main-button">点击我!</button>
        </body>
    </html>
</Layout>

完成后,重新加载你的应用并点击按钮几次。你应该会在 PostHog 事件探索器 中看到事件。

3. 捕获自定义事件

在 PostHog 中设置 A/B 测试的第一步是设置目标指标。我们将使用按钮的点击次数作为目标。

为了测量这一点,我们在按钮被点击时 捕获自定义事件 home_button_clicked。为此,更新 posthog.astro 中的代码,添加一个 <script> 并在按钮被点击时调用 posthog.capture()

index.astro

---
import Layout from '../layouts/Layout.astro';
---
<Layout>
    <html lang="en">
        <head>
            <meta charset="utf-8" />
            <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
            <meta name="viewport" content="width=device-width" />
            <meta name="generator" content={Astro.generator} />
            <title>Astro</title>
        </head>
        <body>
            <h1>Astro A/B 测试</h1>
            <button class="main-button">点击我!</button>


            <script>
                const button = document.querySelector('.main-button');
                button.addEventListener('click', () => {
                    window.posthog.capture('home_button_clicked')
                });
            </script>   
        </body>
    </html>
</Layout>

设置完成后,刷新你的应用并点击按钮几次,以在 PostHog 中看到捕获的事件。

PostHog 中捕获的事件PostHog 中捕获的事件

4. 在 PostHog 中创建 A/B 测试

接下来,转到 A/B 测试选项卡 并通过点击 新建实验 按钮创建一个 A/B 测试。为你的实验添加以下详细信息:

  1. 命名为 “我的酷实验”。
  2. 设置 “功能标志键” 为 my-cool-experiment
  3. 对所有其他字段使用默认值。
  4. 点击 保存为草稿

PostHog 中的实验设置PostHog 中的实验设置

创建完成后,将主要指标设置为 home_button_clicked 的趋势,然后点击 启动

5. 实现 A/B 测试代码

在实现我们的实验代码时,有两个选项:

  1. 客户端渲染
  2. 服务器端渲染

我们将展示如何实现这两种方式。

客户端渲染

为了实现 A/B 测试,我们使用 posthog.onFeatureFlags 回调来根据用户是否在实验的 controltest 变体中更新按钮文本。

更新 /components/posthog.astro 中的代码,在 PostHog 的 loaded 回调中实现 posthog.onFeatureFlags 代码:

posthog.astro

---


---
<script>
  !function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys getNextSurveyStep onSessionId".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
  posthog.init(
    '<ph_project_api_key>',
    {
      api_host:'https://us.i.posthog.com',
      loaded: (posthog) => {
        posthog.onFeatureFlags(() => {
          const button = document.querySelector('.main-button');
          if (posthog.getFeatureFlag('my-cool-experiment') === 'control') {
            button.innerText = '控制变体';
          } else if (posthog.getFeatureFlag('my-cool-experiment') === 'test') {
            button.innerText = '测试变体';
          }
        });
      }
    }
  )
</script>

现在如果你刷新你的应用,你应该会看到按钮文本更新为 控制变体测试变体。用户会自动分配到两者之一,PostHog 继续跟踪按钮点击,你可以在 PostHog 中查看 A/B 测试的结果。

服务器端渲染

注意,当你刷新页面时,按钮文本会在 点击我!控制/测试变体 之间闪烁。这是因为 PostHog 加载并发出功能标志请求需要时间。

服务器端渲染是一种避免这种情况的方法。这种方式在客户端页面加载之前获取功能标志。

要设置这个,我们必须安装并使用 PostHog 的 Node 库(因为我们正在发出服务器端请求)。

终端

npm install posthog-node

src 文件夹中,创建一个 posthog-node.js 文件。这是我们设置代码以创建 PostHog Node 客户端的地方。你可以在 项目设置 中找到你的 API 密钥和实例地址。

src/posthog-node.js

import { PostHog } from 'posthog-node';


let posthogClient = null;


export default function PostHogNode() {
  if (!posthogClient) {
    posthogClient = new PostHog('<ph_project_api_key>', {
      host: 'https://us.i.posthog.com',
    });
  }
  return posthogClient;
}

接下来,我们将 posthog-node.js 导入 pages/index.astro。然后我们使用它来获取功能标志并更新按钮文本:

index.astro

---
import Layout from '../layouts/Layout.astro';
import PostHogNode from '../posthog-node.js';


let buttonText = '无变体'
try {
  const distinctId = 'placeholder-user-id'
  const enabledVariant = await PostHogNode().getFeatureFlag('my-cool-experiment', distinctId);
  if (enabledVariant === 'control') {
        buttonText = '控制变体';
    } else if (enabledVariant === 'test') {
        buttonText = '测试变体';
    }
} catch (error) {
  buttonText = '错误';
}
---
<Layout>
    <html lang="en">
        <head>
            <meta charset="utf-8" />
            <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
            <meta name="viewport" content="width=device-width" />
            <meta name="generator" content={Astro.generator} />
            <title>Astro</title>
        </head>
        <body>
            <h1>Astro A/B 测试</h1>
            <button class="main-button">{buttonText}</button>


            <script>
                const button = document.querySelector('.main-button');
                button.addEventListener('click', () => {
                    window.posthog.capture('home_button_clicked')
                });
        </script>   
        </body>
    </html>
</Layout>

最后,你可以移除我们在 posthog.astro 中添加的用于客户端渲染功能标志的代码:

posthog.astro

---


---
<script>
  !function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys getNextSurveyStep onSessionId".split(" "),n