Here's how you can split traffic into multiple variants, while keeping that user on the same page on revisits. Nothing fancy. How to get stats to analytics isn't covered.

Direct all routes to a single function

Optional. I like my static page traffic handled by a single function.


for path <- PlatformWeb.HomeController.available_paths() do
    get "/#{path}", HomeController, :static_page

Divide the traffic

I'll show you the whole file and comment in between.

defmodule PlatformWeb.HomeController do
    use PlatformWeb, :controller

I don't want a catch-all routing disaster so I've defined available routes manually. We could map the actual templates here, but then the AB testing templates are available too. Manual is safe.

def available_paths do
    ["/", "/contact", "projects", "pricing", "about"]

The "handle any route" function just takes the requested page, asks the ab_test() function to adjust the actual file rendered and then renders it.

def static_page(conn, _params) do
    path =, 0, "index")
    {conn, page} = ab_test(conn, path)
    render(conn, page, %{})

The ab_test() function mainly deals with session memory. If this user has already seen a page, then show the same one again. Lets not get our visitors confused. If this page has not yet been seen, choose a random_variation().

  defp ab_test(conn, requested_path) do
    memory = get_session(conn, :ab_test_memory) || %{}
    page = Map.get(memory, requested_path) || random_variation(requested_path)
    new_memory = Map.put(memory, requested_path, page)
    conn = put_session(conn, :ab_test_memory, new_memory)
    {conn, page}

Choosing a random template was easy after figuring out the View actually holds all of the filenames.

  defp random_variation(page) do
    |> elem(2)
    |> Enum.filter(&String.starts_with?(&1, page))
    |> Enum.random()

And voila. Pretty basic, once you have the template list at hand. If you've also implemented AB Testing, but in some way cooler manner - let me know.