diff --git a/assets/jsutils/README.md b/assets/jsutils/README.md
deleted file mode 100644
index 5a441965dbd50e24af483de555e5695df44c5472..0000000000000000000000000000000000000000
--- a/assets/jsutils/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-This directory stores javascript utility files for xxdk-wasm. These
-include wasm worker loaders and golang's wasm_exec.js.
diff --git a/assets/jsutils/channelsIndexedDbWorker.js b/assets/jsutils/channelsIndexedDbWorker.js
deleted file mode 100644
index 5a4595fe7ae3fe83e45782741d1ec118a8c1b50f..0000000000000000000000000000000000000000
--- a/assets/jsutils/channelsIndexedDbWorker.js
+++ /dev/null
@@ -1,31 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2022 xx foundation                                             //
-//                                                                            //
-// Use of this source code is governed by a license that can be found in the  //
-// LICENSE file.                                                              //
-////////////////////////////////////////////////////////////////////////////////
-
-// NOTE: wasm_exec.js must always be in the same directory as this script.
-importScripts('./wasm_exec.js');
-// NOTE: This relative path must be preserved in distribution.
-const binPath = require('../wasm/xxdk-channelsIndexedDbWorker.wasm');
-
-const isReady = new Promise((resolve) => {
-    self.onWasmInitialized = resolve;
-});
-
-const go = new Go();
-go.argv = [
-    '--logLevel=2',
-    '--threadLogLevel=2',
-]
-// NOTE: This is wonky, we are in the right path inside the publicPath
-// (usually `dist` folder), which is where webpack paths start. They also
-// always prefix with a /, so we are going up a directory to come right back
-// into e.g., ../dist/assets/wasm/[somefile].wasm
-WebAssembly.instantiateStreaming(fetch('..' + binPath), go.importObject).then(async (result) => {
-    go.run(result.instance);
-    await isReady;
-}).catch((err) => {
-    console.error(err);
-});
diff --git a/assets/jsutils/dmIndexedDbWorker.js b/assets/jsutils/dmIndexedDbWorker.js
deleted file mode 100644
index 3da9d3704dcbcca2dc59b357f5efc5ed54a5d4d1..0000000000000000000000000000000000000000
--- a/assets/jsutils/dmIndexedDbWorker.js
+++ /dev/null
@@ -1,32 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2022 xx foundation                                             //
-//                                                                            //
-// Use of this source code is governed by a license that can be found in the  //
-// LICENSE file.                                                              //
-////////////////////////////////////////////////////////////////////////////////
-
-// NOTE: wasm_exec.js must always be in the same directory as this script.
-importScripts('./wasm_exec.js');
-// NOTE: This relative path must be preserved in distribution.
-const binPath = require('../wasm/xxdk-dmIndexedDbWorker.wasm');
-
-const isReady = new Promise((resolve) => {
-    self.onWasmInitialized = resolve;
-});
-
-const go = new Go();
-go.argv = [
-    '--logLevel=2',
-    '--threadLogLevel=2',
-]
-// NOTE: This is wonky, we are in the right path inside the publicPath
-// (usually `dist` folder), which is where webpack paths start. They also
-// always prefix with a /, so we are going up a directory to come right back
-// into e.g., ../dist/assets/wasm/[somefile].wasm
-console.log("WORKERWASM: " + binPath);
-WebAssembly.instantiateStreaming(fetch('..' + binPath), go.importObject).then(async (result) => {
-    go.run(result.instance);
-    await isReady;
-}).catch((err) => {
-    console.error(err);
-});
diff --git a/assets/jsutils/logFileWorker.js b/assets/jsutils/logFileWorker.js
deleted file mode 100644
index ea466ed4dc6498de21f3be08a523db9f54edda0e..0000000000000000000000000000000000000000
--- a/assets/jsutils/logFileWorker.js
+++ /dev/null
@@ -1,27 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2022 xx foundation                                             //
-//                                                                            //
-// Use of this source code is governed by a license that can be found in the  //
-// LICENSE file.                                                              //
-////////////////////////////////////////////////////////////////////////////////
-
-// NOTE: wasm_exec.js must always be in the same directory as this script.
-importScripts('./wasm_exec.js');
-// NOTE: This relative path must be preserved in distribution.
-const binPath = require('../wasm/xxdk-logFileWorker.wasm');
-
-const isReady = new Promise((resolve) => {
-    self.onWasmInitialized = resolve;
-});
-
-const go = new Go();
-// NOTE: This is wonky, we are in the right path inside the publicPath
-// (usually `dist` folder), which is where webpack paths start. They also
-// always prefix with a /, so we are going up a directory to come right back
-// into e.g., ../dist/assets/wasm/[somefile].wasm
-WebAssembly.instantiateStreaming(fetch('..' + binPath), go.importObject).then(async (result) => {
-    go.run(result.instance);
-    await isReady;
-}).catch((err) => {
-    console.error(err);
-});
diff --git a/assets/jsutils/stateIndexedDbWorker.js b/assets/jsutils/stateIndexedDbWorker.js
deleted file mode 100644
index 5f4502916716e1f305ffb0c136a24bebca387d82..0000000000000000000000000000000000000000
--- a/assets/jsutils/stateIndexedDbWorker.js
+++ /dev/null
@@ -1,31 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2022 xx foundation                                             //
-//                                                                            //
-// Use of this source code is governed by a license that can be found in the  //
-// LICENSE file.                                                              //
-////////////////////////////////////////////////////////////////////////////////
-
-// NOTE: wasm_exec.js must always be in the same directory as this script.
-importScripts('./wasm_exec.js');
-// NOTE: This relative path must be preserved in distribution.
-const binPath = require('../wasm/xxdk-stateIndexedDbWorker.wasm');
-
-const isReady = new Promise((resolve) => {
-    self.onWasmInitialized = resolve;
-});
-
-const go = new Go();
-go.argv = [
-    '--logLevel=2',
-    '--threadLogLevel=2',
-]
-// NOTE: This is wonky, we are in the right path inside the publicPath
-// (usually `dist` folder), which is where webpack paths start. They also
-// always prefix with a /, so we are going up a directory to come right back
-// into e.g., ../dist/assets/wasm/[somefile].wasm
-WebAssembly.instantiateStreaming(fetch('..' + binPath), go.importObject).then(async (result) => {
-    go.run(result.instance);
-    await isReady;
-}).catch((err) => {
-    console.error(err);
-});
diff --git a/assets/jsutils/ndf.js b/ndf.js
similarity index 100%
rename from assets/jsutils/ndf.js
rename to ndf.js
diff --git a/src/paths.ts b/src/paths.ts
index fba9bf141e305294ec40b85418df278cef8a3877..10c64fca3a6447214567f2bd633c14577e71a7d3 100644
--- a/src/paths.ts
+++ b/src/paths.ts
@@ -1,11 +1,24 @@
 import { BundleVersion } from './version';
 
+import { startLogFileWorker, startChannelsIndexedDbWorker,
+  startDmIndexedDbWorker, startStateIndexedDbWorker } from './workers.js';
+
+// wasmExec is the path to wasm_exec.js, which is also the root at the
+// publicPath in webpack set as an entry point in the config.
+// FIXME: I could not derive this via require/import and it is not
+//        clear from docs how to do so. We don't need to load it because we
+//        might downloaded it from a CDN. Hardcoded for now since it does not
+//        change. In an ideal world, we'd be able to get this as a hash name and
+//        have a special loader that checks the hash.
+const wasmExec = '/dist/wasm_exec.js';
+
 // TODO: should this global decl be in like the general types folder?
 // we reference it everywhere and it woudl be useful to specify all
 // the window properties we need in a single place.
 declare global {
   interface Window {
       xxdkBasePath: URL;
+      xxdkWasmExecBlobURL: URL;
   }
 }
 
@@ -33,19 +46,67 @@ export function setXXDKBasePath(newPath: URL) {
 
 // TODO: These functions should be kept internal to this package but the current API and legacy
 // apps need to maintain access
-// NOTE: we do not use require here, because these are defined entry points in the webpack config
-export function logFileWorkerPath(): URL {
-  return new URL(window!.xxdkBasePath + '/dist/logFileWorker.js');
+export async function logFileWorkerPath(): Promise<URL> {
+  const binPath = require('../assets/wasm/xxdk-logFileWorker.wasm');
+  const wasm = new URL(window!.xxdkBasePath + binPath.toString());
+  console.info("Loading logFileWorker (" + wasm + ")");
+  return downloadWorkerToBlobURL(wasm, startLogFileWorker);
 }
 
-export function channelsIndexedDbWorkerPath(): URL {
-  return new URL(window!.xxdkBasePath + '/dist/channelsIndexedDbWorker.js');
+export async function channelsIndexedDbWorkerPath(): Promise<URL> {
+  const binPath = require('../assets/wasm/xxdk-channelsIndexedDbWorker.wasm');
+  const wasm = new URL(window!.xxdkBasePath + binPath.toString());
+  console.info("Loading channelsIndexedDbWorker (" + wasm + ")");
+  return downloadWorkerToBlobURL(wasm, startChannelsIndexedDbWorker);
 }
 
-export function dmIndexedDbWorkerPath(): URL {
-  return new URL(window!.xxdkBasePath + '/dist/dmIndexedDbWorker.js');
+export async function dmIndexedDbWorkerPath(): Promise<URL> {
+  const binPath = require('../assets/wasm/xxdk-dmIndexedDbWorker.wasm');
+  const wasm = new URL(window!.xxdkBasePath + binPath.toString());
+  console.info("Loading dmIndexedDbWorker (" + wasm + ")");
+  return downloadWorkerToBlobURL(wasm, startDmIndexedDbWorker);
 }
 
-export function stateIndexedDbWorkerPath() {
-  return new URL(window!.xxdkBasePath + '/dist/stateIndexedDbWorker.js');
+export async function stateIndexedDbWorkerPath(): Promise<URL> {
+  const binPath = require('../assets/wasm/xxdk-stateIndexedDbWorker.wasm');
+  const wasm = new URL(window!.xxdkBasePath + binPath.toString());
+  console.info("Loading stateIndexedDbWorker (" + wasm + ")");
+  return downloadWorkerToBlobURL(wasm, startStateIndexedDbWorker);
+}
+
+// NOTE: Whereas we can load the workers via function.toString(), they all need
+// to reference the wasm_exec.js import script, preferably the same one. To handle this,
+// we download that script into it's own blob url, then explicitly call importScrips(blob url).
+async function downloadWorkerToBlobURL(wasm: URL, workerFn: (wasm: URL) => any): Promise<URL> {
+  const wasmExec = await wasmExecBlob();
+  const blobElems = [
+    "importScripts('" + wasmExec + "');  ",
+    "(" + workerFn.toString() + ")('" + wasm + "');"
+  ];
+  const urlStr = URL.createObjectURL(new Blob(blobElems, {type: 'text/javascript'}));
+  console.info("[XXDK] Loaded " + wasm.toString() + " worker at: " + urlStr.toString());
+  console.trace("[XXDK] worker contents: " + blobElems.toString());
+  return new URL(urlStr);
+}
+
+// wasmExecBlob downloads the wasm_exec.js file from the xxdkBasePath, which could
+// be locally hosted or on a CDN, then makes it available via a blob url.
+async function wasmExecBlob(): Promise<URL> {
+  if (window!.xxdkWasmExecBlobURL !== undefined) {
+    return window!.xxdkWasmExecBlobURL;
+  }
+
+  const url = new URL(window!.xxdkBasePath + wasmExec.toString());
+  console.trace("[XXDK] wasm_exec.js download url: " + url.toString());
+  try {
+    const response = await fetch(url);
+    const data = await response.text();
+    const urlStr = URL.createObjectURL(new Blob([data], {type: 'text/javascript'}));
+    window!.xxdkWasmExecBlobURL = new URL(urlStr);
+  } catch (x) {
+    console.error("[XXDK] Unable to load wasm_exec.js into a blob url: " + x);
+    throw(x);
+  }
+  console.info("[XXDK] wasm_exec.js loaded at: " + window!.xxdkWasmExecBlobURL);
+  return window!.xxdkWasmExecBlobURL;
 }
diff --git a/src/workers.js b/src/workers.js
new file mode 100644
index 0000000000000000000000000000000000000000..31cfde8e16b035c8caec2a4b6b540dd96df69b08
--- /dev/null
+++ b/src/workers.js
@@ -0,0 +1,84 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2024 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+// NOTE: These javascript functions are used to load web workers. They
+// can't call other functions and they must use the webworker
+// available APIs to function. Thats why they are duplicate code.
+
+export function startLogFileWorker(wasm) {
+  console.trace("[XXDK] logFileWorker loading from: " + wasm.toString());
+  const isReady = new Promise((resolve) => {
+    self.onWasmInitialized = resolve;
+  });
+
+  const go = new Go();
+  WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(async (result) => {
+    go.run(result.instance);
+    await isReady;
+    console.info("[XXDK] logFileWorker started");
+  }).catch((err) => {
+    console.error(err);
+  });
+}
+
+export function startChannelsIndexedDbWorker(wasm) {
+  console.trace("[XXDK] channelsIndexedDbWorker loading from: " + wasm.toString());
+  const isReady = new Promise((resolve) => {
+    self.onWasmInitialized = resolve;
+  });
+  const go = new Go();
+  go.argv = [
+    '--logLevel=2',
+    '--threadLogLevel=2',
+  ]
+  WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(async (result) => {
+    go.run(result.instance);
+    await isReady;
+    console.info("[XXDK] channelsIndexedDbWorker started");
+  }).catch((err) => {
+    console.error(err);
+  });
+}
+
+export function startDmIndexedDbWorker(wasm) {
+  console.trace("[XXDK] dmIndexedDbWorker loading from: " + wasm.toString());
+  const isReady = new Promise((resolve) => {
+    self.onWasmInitialized = resolve;
+  });
+  const go = new Go();
+  go.argv = [
+    '--logLevel=2',
+    '--threadLogLevel=2',
+  ]
+  WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(async (result) => {
+    go.run(result.instance);
+    await isReady;
+    console.info("[XXDK] dmIndexedDbWorker started");
+  }).catch((err) => {
+    console.error(err);
+  });
+}
+
+export function startStateIndexedDbWorker(wasm) {
+  console.trace("[XXDK] stateIndexedDbWorker loading from: " + wasm.toString());
+  const isReady = new Promise((resolve) => {
+    self.onWasmInitialized = resolve;
+  });
+  const go = new Go();
+  go.argv = [
+    '--logLevel=2',
+    '--threadLogLevel=2',
+  ]
+  WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(async (result) => {
+    go.run(result.instance);
+    await isReady;
+    console.info("[XXDK] stateIndexedDbWorker started");
+  }).catch((err) => {
+    console.error(err);
+  });
+}
+
diff --git a/src/xxdk.ts b/src/xxdk.ts
index 3f899f70eeeba37a0fc92e4da2019270dc964db3..e476d3fe189dfdc626df9cf75228ca2f8b0762b0 100644
--- a/src/xxdk.ts
+++ b/src/xxdk.ts
@@ -35,11 +35,13 @@ export const InitXXDK = () => new Promise<XXDKUtils>(async (xxdkUtils) => {
   console.log("Fetching xxdkWASM base: " + window!.xxdkBasePath);
   console.log("Fetching xxdkWASM path: " + xxdkWasm);
 
+  const logWorker = await logFileWorkerPath();
+  console.log("Got logworkerURL: " + logWorker);
   let go = new window!.Go();
   go.argv = [
     '--logLevel=1',
     '--fileLogLevel=1',
-    '--workerScriptURL=' + logFileWorkerPath(),
+    '--workerScriptURL=' + logWorker,
   ]
 
   let stream = await WebAssembly?.instantiateStreaming(