Conversation
Add @convertcom/js-sdk-cloudflare with edge-specific helpers for running Convert experiments inside Cloudflare Workers: KV-backed DataStore adapter, edge config cache, cookie helpers, and variation-aware cache utilities. Includes a demo Worker (demo/cloudflare-workers/) demonstrating page-level A/B testing with HTMLRewriter, asset swaps, split URL redirects, and SPA injection patterns. Updates release-please config, publish workflows, and root build scripts to support the new package.
Summary of ChangesHello @abbaseya, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a significant enhancement by enabling the Convert JavaScript SDK to run experiments directly on the Cloudflare edge. This integration allows for A/B testing, feature flagging, and personalization with superior performance, eliminating visual flicker and reducing latency by executing all decision-making and content modification server-side within Cloudflare Workers. The changes include a new utility package, a detailed demo, and robust mechanisms for configuration caching and visitor data persistence using Cloudflare KV. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Ignored Files
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a new package @convertcom/js-sdk-cloudflare and a demo to enable running Convert experiments on Cloudflare's edge. The implementation is well-structured and thoroughly documented. My review focuses on improving robustness and type safety. I've pointed out potential race conditions in singleton initialization and areas where JSON parsing could fail without error handling. I've also suggested improvements to TypeScript typings for better code clarity and safety. Overall, this is a great addition.
Fix SDK singleton race condition by caching the initialization promise so concurrent cold-start requests share one init. Guard JSON.parse calls in KVDataStore and EdgeConfigCache against corrupted KV data. Type the applyVariation parameter as BucketedVariation instead of any.
clllaur
left a comment
There was a problem hiding this comment.
@abbaseya I took a quick look and some comments:
- for caching fetch response (our project data) is no need to use KW, that's more like a database, more expensive and It might be that not all plans have it. Instead the basic caching setting for fetch can be used:
let response = await fetch(url, { cf: { cacheTtl: 500000, cacheEverything: true, } })https://developers.cloudflare.com/workers/examples/cache-using-fetch/ - Persistence store is not really required as usual, maybe make that clear?
- you mentioned that tracking events are released on a schedule base, how will that work here? You need to make sure the script finishes fast and does not wait on the queue of events to be released X seconds later. IF thats the case, a setting for this would be needed and than have tracking queue release right before finishing worker's job
…tional persistence
Address code review on Cloudflare Workers integration:
- Replace KV-based EdgeConfigCache with Cloudflare built-in fetch cache
(cf: { cacheTtl, cacheEverything }) — simpler, free on all plans, no KV
dependency needed for basic setup
- Mark KVDataStore as optional — deterministic MurmurHash bucketing means
the same visitor ID always gets the same variation without persistence
- Document that releaseQueues() must be called in waitUntil() before the
Worker finishes, since the SDK setTimeout-based event timer will not fire
in stateless Workers
- Update demo, README, and wrangler.toml to reflect KV-free default setup
65a4e88 to
a24523c
Compare
|



Edge Experimentation with Cloudflare Workers
Run Convert A/B tests, feature flags, and personalisation at the Cloudflare edge. Visitors receive a fully modified page with zero flicker and sub-millisecond bucketing latency because all decisions happen server-side inside a Cloudflare Worker, before the response reaches the browser.
sequenceDiagram participant V as Visitor participant W as Cloudflare Worker participant O as Origin Server participant C as Convert CDN V->>W: GET /page W->>C: Fetch config (edge-cached) C-->>W: SDK config data W->>W: SDK: createContext → runExperience W->>O: Fetch origin page O-->>W: HTML response W->>W: HTMLRewriter: apply variation W-->>V: Modified page (zero flicker) W--)C: Send tracking event (waitUntil)Table of Contents
1. Why Edge Experimentation?
Traditional client-side A/B testing has three fundamental problems:
The Convert FullStack SDK already runs in Cloudflare Workers without modification. Its HTTP client auto-detects the Workers runtime (
server-with-fetch) and uses the nativefetchAPI. What this guide provides is the Cloudflare-specific integration layer: edge-cached config, cookie management,HTMLRewriterpatterns, and tracking event handling.2. Architecture Overview
Key principles:
cf.cacheTtlfetch option, which is free and works on all plans.setTimeoutto batch and release tracking events. In Workers, the isolate may finish before that timer fires. Always callcontext.releaseQueues()insidectx.waitUntil()to flush events before the Worker completes.3. Prerequisites
npm install -g wrangler)ACCOUNT_ID/PROJECT_ID(found in Convert app settings)4. Setup
4.1 Create the Worker Project
4.2 Configure wrangler.toml
That's it — no KV namespace needed for the basic setup. The SDK config is cached automatically at the Cloudflare edge using the native
fetchcache.4.3 Development
5. Utility Package Reference
Install:
npm install @convertcom/js-sdk-cloudflare(oryarn add)EdgeConfigCache
Caches Convert SDK configuration using Cloudflare's built-in
cf.cacheTtlfetch option. No KV namespace required — the response is cached at the edge automatically.KVDataStore (Optional)
Bridges the SDK's synchronous DataStore interface with Cloudflare's async KV API. This is optional — the SDK uses deterministic MurmurHash bucketing, so the same visitor ID always gets the same variation. You only need KV persistence if you want to:
Cookie Helpers
Parse and set visitor ID cookies on Workers
Request/Responseobjects.Cache Helpers
Build variation-aware cache keys and set appropriate headers.
6. Usage Patterns
Pattern 1: Page-Level A/B Test (HTMLRewriter)
This is the core pattern. The Worker fetches the origin page, then uses Cloudflare's
HTMLRewriterto modify elements before delivery.Pattern 2: Asset / Image Swap
Replace images, stylesheets, or scripts per variation:
Pattern 3: Split URL Redirect
Serve a completely different origin page without the visitor seeing a redirect:
Pattern 4: SPA Injection
For single-page applications, inject bucketing decisions as a global JS variable so the client-side framework can apply them:
Then in your SPA (React, Vue, etc.):
Pattern 5: Feature Flags at the Edge
Use feature flags to gate entire page sections:
7. Caching Strategies
SDK Config Caching
The
EdgeConfigCacheclass uses Cloudflare's nativecf.cacheTtlfetch option to cache the SDK config response at the edge. This is:To force a refresh (e.g. after updating your Convert project config):
Edge Cache Per Variation
Cache origin responses per variation to avoid redundant origin fetches:
Recommended Cache-Control Headers
public, max-age=300+Vary: Cookiepublic, max-age=31536000, immutablecf.cacheTtl: 300When to Bypass Cache
8. Performance Considerations
What This Adds
SDK Behaviour in Workers
Two SDK patterns behave differently in Workers than in long-lived servers:
Config refresh timer (
setTimeoutincore.ts) — In a long-lived Node.js server, this refreshes config every 5 minutes. In Workers, the isolate may not persist that long. Solution: Pass config data directly viaEdgeConfigCacheand let the edge cache TTL handle refresh. ThesetTimeoutis harmless (it just won't fire).Event batching (
ApiManager.enqueue()) — The SDK queues tracking events and releases them after asetTimeoutdelay (default: 1-10 seconds). In Workers, the isolate finishes before that timer fires, so events would be lost. Solution: Always callcontext.releaseQueues()insidectx.waitUntil()at the end of each request. This immediately flushes all pending events without blocking the response:Cloudflare Worker Limits
The Convert SDK at 24 KB gzipped (136 KB uncompressed) fits well within script size limits.
9. Troubleshooting
Visitor Gets Different Variations Across Requests
Cause: Visitor cookie not being set or read correctly.
Check:
Fix: Ensure
setVisitorIdCookie()is called and the response headers are forwarded. Check that the cookie domain matches your site.SDK Returns Null Context
Cause: Config data is empty or invalid.
Check:
# Verify config fetch curl https://cdn-4.convertexperiments.com/api/v1/config/YOUR_ACCOUNT_ID/YOUR_PROJECT_IDFix: Verify your SDK key. Check that the experience is active (not paused/draft) in the Convert dashboard.
HTMLRewriter Changes Not Appearing
Cause: Selectors don't match the origin HTML, or the response is not HTML.
Check:
console.login the Worker and check withwrangler tailContent-Typeistext/htmlExperience Returns a RuleError
Cause: The visitor doesn't match the experience's audience/location rules.
Check: The
locationPropertiesandvisitorPropertiespassed torunExperience()must match the rules configured in Convert.Tracking Events Not Appearing in Convert Dashboard
Cause:
releaseQueues()not called before the Worker finishes.Fix: Always call
context.releaseQueues()insidectx.waitUntil():Without this, the SDK's internal
setTimeout-based release timer will never fire because the Worker isolate finishes first.Edge Cache Serving Stale Config
Fix: Call
configCache.refreshConfig()programmatically (e.g. via a cron trigger or webhook endpoint). This fetches withcacheTtl: 0which bypasses the edge cache.10. FAQ
Can I use this with any website, or only Cloudflare-hosted sites?
Any website that has Cloudflare as its DNS/proxy provider (orange cloud enabled). The origin server can be hosted anywhere (AWS, Vercel, your own server). Cloudflare Workers intercept requests at the edge regardless of where the origin sits.
How does this compare to client-side Convert tracking scripts?
They serve different purposes. The client-side tracking script is a drop-in solution for marketing teams. Edge experimentation via Workers is for developers who want full control, zero flicker, and server-side experimentation on their own infrastructure.
Do I need Cloudflare KV?
No. The basic setup requires no KV at all:
fetchcache (cf.cacheTtl) — free on all plansKV is only needed if you want to persist bucketing decisions across experience config changes, or store custom visitor attributes. See the KVDataStore section.
Does the SDK work in Cloudflare Pages Functions?
Yes. Pages Functions are Cloudflare Workers under the hood. The same patterns apply. Place your function in
functions/[[path]].tsto intercept all routes.Can I run multiple experiments on the same page?
Yes. Use
context.runExperiences()to get all variation decisions at once, then chain multipleHTMLRewriterhandlers:How do I track conversions?
Call
context.trackConversion()in the Worker (for server-side events like form submissions) or from the client-side SDK (for click events):What happens if the Worker throws an error?
The demo wraps everything in a try/catch that falls back to
fetch(request)— the unmodified origin page. Visitors always get a working page; experiments degrade gracefully.Why must I call
releaseQueues()manually?The SDK internally batches tracking events and releases them after a
setTimeoutdelay. This works in long-lived Node.js servers but not in Cloudflare Workers where the isolate finishes before the timer fires.releaseQueues()immediately flushes all pending events. Wrapping it inctx.waitUntil()ensures the tracking POST completes without blocking the response to the visitor.Additional Resources
packages/cloudflare/— Utility package sourcedemo/cloudflare-workers/— Complete working example