Resolving ChunkLoadError during PSPDFKit-to-Nutrient migration in Ember

Migrating from PSPDFKit to Nutrient Web SDK can occasionally introduce unexpected challenges, particularly in complex build environments like Ember.js applications. One of the most common issues developers encounter is the dreaded ChunkLoadError during automated testing, which can make your test suite flaky and unreliable.

If you’re experiencing chunk loading failures after migrating to Nutrient Web SDK, you’re not alone. This guide walks through the root causes and provides proven solutions to resolve these issues permanently.

Understanding the ChunkLoadError

The ChunkLoadError typically manifests as the following:

ChunkLoadError: Loading chunk 42 failed.
(error: http://localhost:36173/assets/nutrient-viewer-lib/chunk-standalone-server-076587c32d94e70b.js)

This error occurs when Nutrient Web SDK cannot locate its required chunk files during initialization. While it might seem like a network issue, it’s usually related to how static assets are served in your application.

Root cause: Missing asset integration

Nutrient Web SDK uses dynamic imports to load various chunks on demand, optimizing performance by only loading what’s needed when it’s needed. In the following case, the issue stemmed from how the SDK assets were being handled in the build process:

"copy-nutrient-files": "cp -R ./node_modules/@nutrient-sdk/viewer/dist/ ./public/assets/"

The common migration mistake

A typical migration approach might include:

  1. Updating package dependencies from pspdfkit to @nutrient-sdk/viewer.
  2. Copying SDK assets using a build script: cp -R ./node_modules/@nutrient-sdk/viewer/dist/ ./public/assets/.
  3. Updating initialization code to use Nutrient APIs.

While this approach works in development, it could fail in other environments, especially when running parallel tests. The issue is that copying assets via script doesn’t properly integrate them into your build system’s asset pipeline.

Two approaches to resolve ChunkLoadError

When facing ChunkLoadError issues during migration, there are two primary approaches you can take to resolve the problem.

The most effective approach is to integrate the Nutrient assets directly into your Ember build process using Broccoli’s Funnel plugin(opens in a new tab). This ensures assets are always available and properly served.

Add the following to your ember-cli-build.js file:

const Funnel = require("broccoli-funnel");
const mergeTrees = require("broccoli-merge-trees");
module.exports = function (defaults) {
const app = new EmberApp(defaults, {
// Your existing configuration.
});
// Add Nutrient assets to build tree.
const nutrientAssets = new Funnel(
"node_modules/@nutrient-sdk/viewer/dist/nutrient-viewer-lib",
{
srcDir: "/",
destDir: "/assets/nutrient-viewer-lib",
},
);
// Include Nutrient assets in the build output.
return mergeTrees([app.toTree(), extraAssets, nutrientAssets]);
};

Important path note: If you encounter a “Directory not found” error with the node_modules/@nutrient-sdk/viewer/nutrient-viewer-lib path, use node_modules/@nutrient-sdk/viewer/dist/nutrient-viewer-lib instead, as the assets are located in the dist folder.

Solution 2: Parallel execution adjustments

As an alternative or complementary approach, you can reduce the parallel execution load and add detailed CI logging to minimize race conditions:

  1. Reduce parallelism — Change from --parallel=6 to --parallel=3 in your test command.
  2. Add detailed CI logging to monitor asset loading behavior.
  3. Implement retry logic in your preloadWorker function.
// Update your exam script.
"exam": "ember exam --parallel=3 --load-balance --test-port=0 --random"

Why solution 1 works best

The funnel approach (solution 1) succeeds where copy scripts fail because of:

  1. Build-time integration — Assets are included in the build tree, ensuring they’re always available when your application loads.
  2. Consistent asset serving — The assets are served through Ember’s asset pipeline with proper caching headers.
  3. Parallel test support — Multiple test processes can access assets simultaneously without race conditions.
  4. Environment consistency — The same asset handling works across development, testing, and production environments.

Complete setup guide

Before diving into the detailed setup, make sure your project is prepared to correctly reference Nutrient’s assets throughout the application.

Configure your baseUrl

Ensure your SDK initialization uses the correct baseUrl:

const config = {
baseUrl: location.protocol + "//" + location.host + "/assets/",
// ... other configuration options.
};

Remove copy scripts

Once you’ve implemented solution 1, you can remove any manual copy scripts from your package.json file:

{
"scripts": {
// Remove this line.
// "copy-nutrient-files": "cp -R ./node_modules/@nutrient-sdk/viewer/dist/ ./public/assets/",
// Keep your exam command as is (or reduce parallelism if needed).
"exam": "ember exam --parallel=6 --load-balance --test-port=0 --random"
}
}

Additional considerations

Once the main setup is complete, there are a few additional considerations to ensure your assets and tests behave reliably in all environments.

Fingerprinting

If you’re using asset fingerprinting in production, ensure the Nutrient assets are excluded from fingerprinting to maintain consistent URLs:

fingerprint: {
enabled: isProductionLike,
exclude: [
'/nutrient-viewer-lib/**/*'
],
}

Error handling in tests

For additional robustness, consider adding retry logic to your preloadWorker function:

async preloadWorker() {
if (this.hasPreloaded) return;
const maxRetries = 3;
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
this.set('hasPreloaded', true);
return await PSPDFKit.preloadWorker(this.config);
} catch (error) {
if (attempt === maxRetries) throw error;
console.warn(`Preload attempt ${attempt} failed, retrying...`);
await delay(1000 * attempt);
}
}
}

Verification

After implementing these changes, verify the fix by:

  1. Running your full test suite with parallel execution using ember exam --parallel=6.
  2. Checking that chunk files load consistently across all test runs.
  3. Confirming the solution works in both local and CI environments.

Key takeaways

When migrating from PSPDFKit to Nutrient Web SDK:

  • Don’t rely on copy scripts for asset management in production applications
  • Integrate assets into your build process using proper build tools like Broccoli Funnel
  • Test thoroughly in parallel execution environments that mirror your CI setup
  • Configure asset serving correctly to handle concurrent requests

ChunkLoadError is ultimately an asset serving issue, not a networking problem. By properly integrating Nutrient’s assets into your build pipeline, you ensure reliable SDK initialization across all environments and execution contexts.

This approach not only fixes the immediate ChunkLoadError, but it also provides a more robust foundation for your Nutrient integration, preventing similar issues as you scale your application.