You may only deploy this edge function using CDN-as-Code due to Node.js dependencies. It will not work when deployed through the Edgio Console.
Optimizely is a popular experimentation platform that allows you to run A/B tests, multivariate tests, and personalization campaigns on your website. By integrating Optimizely with your Edgio application, you can leverage the power of experimentation to optimize your user experience and drive better business outcomes.
If you prefer a simpler workflow that does not require the use of Edge Functions or Optimizely, use Experimentation to serve different experiences to your clients.
In this guide, we’ll show you how to use Edgio Edge Functions to integrate Optimizely experiments into your application. We’ll create an edge function that intercepts incoming requests, checks for an Optimizely experiment cookie, and modifies the request based on the experiment configuration.
The following example demonstrates using an Optimizely experiment to determine the text direction of a webpage and modifies the HTML content accordingly before responding to the client.
Prerequisites
Setup requires:
- An Edgio account. Sign up for free.
- An Edgio property. Learn how to create a property.
- Node.js. View supported versions and installation steps.
- Edgio CLI.
Install the Edgio CLI
If you have not already done so, install the Edgio CLI.
Bash
1npm i -g @edgio/cli@latest
Getting Started
To get started, you’ll need an Optimizely account and an existing experiment that you want to integrate with your Edgio application. If you don’t have an Optimizely account, you can sign up for a free trial.
If you don’t already have an existing Edgio application, you can create one using the Edgio CLI:
Bash
1edgio init --edgioVersion latest
This will create a new Edgio application with the necessary files and configurations to get started.
Next, create the following directories which will be used to store the edge functions and other necessary files:
Bash
1. (project root)2├── edge-functions3├── lib4│ ├── optimizely5│ └── polyfills67# Create the directories8mkdir -p edge-functions lib/optimizely lib/polyfills
Install the Optimizely SDK
To integrate Optimizely with your Edgio application, you’ll need to install the Optimizely SDK and some additional polyfills Optimizely depends on. You can do this by adding the following dependencies to your project:
Bash
1npm install @optimizely/optimizely-sdk crypto-js polyfill-crypto.getrandomvalues uuid
Define Required Polyfills
The Optimizely SDK relies on the
uuid
(has a dependency on crypto
) and other timing functions not available in the Edge Functions runtime. To ensure the SDK works correctly, you’ll need to create the following polyfills. These will be used later in the edge function to ensure the SDK functions correctly.crypto Polyfill
Optimizely requires
uuid
which has a dependency on crypto
. The following polyfill provides the necessary functions for the SDK to work correctly.JavaScript./lib/polyfills/crypto.js
1import CryptoJS from 'crypto-js';2import getRandomValues from 'polyfill-crypto.getrandomvalues';34global.crypto = {5 ...CryptoJS,6 getRandomValues,7};
Timer Polyfill
Various dependencies reference standard JavaScript timing functions that are not available in the Edge Function runtime. The following polyfill provides the necessary functions for the SDK to work correctly.
JavaScript./lib/polyfills/timer.js
1let timers = new Map();2let nextTimerId = 1;34(function (global) {5 var timerQueue = [];6 var nextTimerId = 0;78 function runTimers() {9 var now = Date.now();10 var nextCheck = null;1112 // Run due timers13 for (var i = 0; i < timerQueue.length; i++) {14 var timer = timerQueue[i];15 if (timer.time <= now) {16 timer.callback.apply(null, timer.args);17 if (timer.repeating) {18 timer.time = now + timer.delay; // schedule next run19 nextCheck =20 nextCheck !== null ? Math.min(nextCheck, timer.time) : timer.time;21 } else {22 timerQueue.splice(i--, 1); // remove non-repeating timer23 }24 } else {25 nextCheck =26 nextCheck !== null ? Math.min(nextCheck, timer.time) : timer.time;27 }28 }2930 // Schedule next check31 if (nextCheck !== null) {32 var delay = Math.max(nextCheck - Date.now(), 0);33 setTimeout(runTimers, delay);34 }35 }3637 global.setTimeout = function (callback, delay, ...args) {38 var timerId = ++nextTimerId;39 var timer = {40 id: timerId,41 callback: callback,42 time: Date.now() + delay,43 args: args,44 repeating: false,45 delay: delay,46 };47 timerQueue.push(timer);48 return timerId;49 };5051 global.clearTimeout = function (timerId) {52 for (var i = 0; i < timerQueue.length; i++) {53 if (timerQueue[i].id === timerId) {54 timerQueue.splice(i, 1);55 break;56 }57 }58 };5960 global.queueMicrotask = function (callback) {61 Promise.resolve()62 .then(callback)63 .catch((err) =>64 setTimeout(() => {65 throw err;66 })67 );68 };6970 setTimeout(runTimers, 0);71})(global);
Obtain the Optimizely Datafile
The Optimizely SDK requires a datafile that contains the configuration for your experiments. We recommend that you export the datafile from the Optimizely dashboard and save it as a JSON file in your project’s
lib/optimizely
directory.Create the Edge Function
Next, you’ll need to create an edge function that intercepts incoming requests and modifies the response based on the Optimizely experiment configuration. The edge function will check for an Optimizely experiment cookie in the request and use the Optimizely SDK to determine the appropriate variation to serve.
Optimizely’s SDK Lite is used in this example to reduce the size of the code
bundle. The SDK Lite requires a preloaded datafile, which is referenced in the
guide. You may choose to fetch the datafile dynamically if your experiment
configuration changes frequently.
Create a new file named
optimizely-experiment.js
in your project’s edge-functions
directory and add the following code:JavaScript./edge-functions/optimizely-experiment.js
1// Necessary polyfills for the edge function runtime2import '../lib/polyfills/crypto.js';3import '../lib/polyfills/timer.js';45import {6 createInstance,7 eventDispatcher,8} from '@optimizely/optimizely-sdk/dist/optimizely.lite.min.js';9import optimizelyDatafile from '../lib/optimizely/datafile.json';1011import {v4 as uuidv4} from 'uuid';1213// Constants for Optimizely client configuration14const CLIENT_ENGINE = 'node-sdk';15const COOKIE_NAME = 'experiment-cookie-name';1617/**18 * Handles incoming HTTP requests and applies A/B testing using Optimizely.19 *20 * @param {Request} request - The incoming HTTP request.21 * @param {Object} context - The context for this handler22 * @returns {Response} The HTTP response after applying A/B testing logic.23 */24export async function handleHttpRequest(request, context) {25 // Retrieve or generate a unique user ID from cookies26 const userId =27 request.headers28 .get('Cookie')29 ?.split(';')30 .find((cookie) => cookie.trim().startsWith(`${COOKIE_NAME}=`))31 ?.split('=')[1] || uuidv4();3233 // Create an Optimizely instance with the preloaded datafile and configuration.34 // This edge function uses the Optimizely SDK Lite which requires a preloaded datafile.35 const instance = createInstance({36 datafile: optimizelyDatafile,37 clientEngine: CLIENT_ENGINE,38 eventDispatcher,39 });4041 // Early exit if the Optimizely instance isn't properly created42 if (!instance) {43 return new Response('Optimizely instance unavailable.', {status: 500});44 }4546 // Ensures the Optimizely instance is ready before proceeding47 await instance.onReady();4849 // Create a user context for the retrieved or generated user ID50 const userContext = instance.createUserContext(userId.toString());5152 // Your logic based on the Optimizely experiment variation53 const decision = userContext.decide('your_experiment_flag');54 // ...5556 // Fetch the original response from the origin57 const response = await fetch(request.url, {58 edgio: {origin: 'your-origin'},59 });6061 // Modify the response based on the Optimizely experiment variation62 const updatedResponse = new Response(response.body, response);63 // ...6465 // Add the user ID to the response headers as a cookie to ensure the user experience consistency66 const cookie = `${COOKIE_NAME}=${userId}; Path=/; Max-Age=31536000; SameSite=Lax`;67 updatedResponse.headers.append('Set-Cookie', cookie);6869 // Return the modified response to the client70 return updatedResponse;71}
See our Optimizely example repo for a complete implementation of the edge function.
Routing
With the edge function created, you’ll need to define a route that maps incoming requests to the edge function. You can do this by updating the
routes.[js|ts]
file in your project’s root directory:JavaScriptroutes.js
1// This file was automatically added by edg init.2// You should commit this file to source control.34const {Router} = require('@edgio/core/router');56export default new Router().get('/optimizely-experiment', {7 edge_function: './edge-functions/optimizely-experiment.js',8});9// Additional routes...
With this configuration, any incoming requests to
/optimizely-experiment
will be processed by the optimizely-experiment.js
edge function. Here you may add other features such as cache rules.Running Locally
Test on your local machine by running the following command in your project’s root directory:
Bash
1edgio dev
Once the development server is running, you can access your app at
http://localhost:3000
. Test the Optimizely experiment by navigating to the /optimizely-experiment
route.Deploying
Deploy your app to Edgio by running the following command in your project’s root directory:
Bash
1edgio deploy
Your initial CDN-as-code deployment will generate system-defined origin configurations along with those defined within your
edgio.config.js
. Learn more about system-defined origins.See Deployments for more information.