Back to projects
Aug 2024
3 min read

WordPress AB Testing Block

A fast and simple AB testing WordPress block
  • React
  • WordPress
  • PHP
  • Cloudflare Workers

Regular AB testing tools like Optimizely or the former Google Optimizer are not great for user experience. They rely on third-party JavaScript, which slows down your website. It’s a technological limitation. JavaScript files load when the page is potentially already visible to users.

That’s a problem because users would notice the page change or the redirect. That’s why Google recommends hiding your content until the experiment is ready. This is not an acceptable solution to me.

Therefore, I worked on a custom solution that leverages Cloudflare Workers to deliver a frictionless user experience. That way, users don’t know they are viewing an experiment.

WordPress AB Testing Block

The AB Testing Block is a simple block wrapper that contains two child blocks made of InnerBlocks components.

AB Testing WordPress Block

WordPress Editor interface of the AB Testing Block

The block-rendered HTML should look like this 👇

<!-- wp:my-plugin/ab-testing {"experimentId":"30d74c399e10"} -->
<div class="wp-block-workleap-ab-testing">
	<!-- wp:my-plugin/ab-testing-block-a {"experimentId":"30d74c399e10"} -->
	<div class="wp-block-ab-testing-block-a">...</div>
	<!-- /wp:my-plugin/ab-testing-block-a -->

	<!-- wp:my-plugin/ab-testing-block-b {"experimentId":"30d74c399e10"} -->
	<div class="wp-block-ab-testing-block-b">...</div>
	<!-- /wp:my-plugin/ab-testing-block-b -->
</div>
<!-- /wp:my-plugin/ab-testing -->

Once we have that, we can hide or display a child block based on a query parameter. We can do that with the render_callback of the register_block_type function.

// Register Main Block
register_block_type( __DIR__ );

// Register Child Block A
register_block_type(
    trailingslashit( __DIR__  ) . '/a-block.json',
    array(
        'render_callback' => array( $this, 'render_block_experiment_a' ),
    )
);

// Register Child Block B
register_block_type(
    trailingslashit( __DIR__  ) . '/b-block.json',
    array(
        'render_callback' => array( $this, 'render_block_experiment_b' ),
    )
);
// Render block A
function render_block_experiment_a( $attributes, $content ) {
    if ( isset( $_GET['experiment'] ) && 'b' === $_GET['experiment'] ) {
        return false;
    }

    return $content;
}

// Render block B
function render_block_experiment_b( $attributes, $content ) {
    if ( ! isset( $_GET['experiment'] ) || 'b' !== $_GET['experiment'] ) {
        return false;
    }

    return $content;
}

Analytics

Performing an AB experiment is only worthwhile if we can determine a winner. For that purpose, we can send an event from the WordPress block. In my case, the monitoring stack was Cloudflare Zaraz and Mixpanel and I did as follow.

Cloudflare Zaraz way 👇

zaraz.track("Experiment Started", { experiment_name: "homepage hero", experiment_group: "B" });

Or if you use Mixpanel client-side SDK 👇

mixpanel.track("$experiment_started", { "Experiment name": "homepage hero", "Variant name": "B" });

AB Testing Cloudflare Worker

Cloudflare has a documented example of A/B testing with same-URL direct access that I used as a starting point.

The Worker should be enabled only when an experiment is active. You can manually set up a Cloudflare Route or, as I did, do it programmatically using the Cloudflare API.

Finally, we can modify the Worker to simply return the page if the user is categorized in group A. Alternatively, it can return a fetch of the same URL with ?experiment=b added to it if the user is categorized in group B.