How to Fix: How to set dynamic assetPrefix with __next_set_public_path__

7 min read

Dynamic assetPrefix with __next_set_public_path__ fails because that runtime hook is not how modern Next.js resolves its JavaScript and CSS assets. If you leave assetPrefix unset and expect the browser to switch the /_next/ asset base at runtime, Next.js will still emit asset URLs using the build-time public path, which leads to missing chunks, broken CSS, or hydration failures when assets are served from a different host or path.

Understanding the Root Cause

In Next.js, assetPrefix is primarily a build/runtime configuration used by the framework itself to generate URLs for assets under /_next/. The key detail is that this is not equivalent to Webpack setups where __webpack_public_path__ or similar globals can be changed early in the page lifecycle to redirect chunk loading dynamically.

The confusion usually comes from code snippets referencing __next_set_public_path__. That internal mechanism is not a supported public API for setting a fully dynamic asset base in userland across all Next.js runtimes and routers. In practice:

  • Next.js precomputes or injects asset paths for scripts, CSS, manifests, and chunks.
  • The browser begins loading critical assets very early.
  • If your deployment host for static assets differs per request, changing the path too late will not fix already emitted URLs.
  • If you do not configure assetPrefix correctly, the app still requests files from the default origin, usually the current host.

That is why the issue appears when trying to have no fixed assetPrefix but still wanting a dynamic CDN or asset host. The root problem is timing and framework ownership: Next.js controls asset URL generation, and unsupported runtime mutation does not reliably override that behavior.

The supported pattern is to decide the asset base using Next.js configuration, environment variables, reverse proxying, or deployment architecture, instead of depending on __next_set_public_path__.

Step-by-Step Solution

The correct fix depends on what “dynamic” means in your deployment. Below are the safe approaches, ordered from most reliable to most advanced.

1. Use assetPrefix from environment configuration

If your asset host changes by environment, set assetPrefix in next.config.js or next.config.mjs.

/** @type {import('next').NextConfig} */
const isProd = process.env.NODE_ENV === 'production'
const cdnHost = process.env.NEXT_PUBLIC_CDN_HOST || ''

const nextConfig = {
  assetPrefix: isProd && cdnHost ? cdnHost : '',
}

module.exports = nextConfig

Example environment file:

NEXT_PUBLIC_CDN_HOST=https://cdn.example.com

This makes Next.js emit /_next/ assets from your CDN in production while keeping local development simple.

2. If the asset host changes per deployment, inject it at startup, not per request

If each deployed environment has its own CDN base, generate the configuration when the app starts.

const nextConfig = {
  assetPrefix: process.env.ASSET_PREFIX || '',
}

module.exports = nextConfig

Then start the app with:

ASSET_PREFIX=https://static.example.com npm run start

This is still dynamic operationally, but not dynamic inside the browser after the page begins loading.

3. If you need one application to serve different tenants, prefer a reverse proxy

If your real goal is per-request asset host selection, the most stable solution is usually to keep Next.js pointing to a single predictable asset path and let infrastructure route requests.

For example, keep asset URLs as normal:

const nextConfig = {
  assetPrefix: '',
}

module.exports = nextConfig

Then configure your edge server, load balancer, or CDN so that requests to /_next/static/ are forwarded to the correct backend bucket or CDN origin.

This works because:

  • Next.js can continue emitting standard URLs.
  • Your infrastructure becomes responsible for resolving the correct origin.
  • You avoid relying on unsupported client-side mutation.

4. If you are using a CDN, upload only the Next static assets

Typical setups move the generated static files to a CDN while application routes still run on the main server. In that case, serve the .next/static contents from your CDN and point assetPrefix to that origin.

const nextConfig = {
  assetPrefix: 'https://cdn.example.com',
}

module.exports = nextConfig

After build, ensure your CDN contains the contents of:

.next/static

Resulting requests will look like:

https://cdn.example.com/_next/static/...

5. Do not rely on __next_set_public_path__ as the primary fix

If you found internal code or screenshots mentioning __next_set_public_path__, treat it as an implementation detail, not a stable integration contract. Even if it appears to work in one version, it can break across:

  • Next.js upgrades
  • App Router vs Pages Router differences
  • Server-side rendering and streaming timing
  • Prefetching and preload behavior

For most teams, this is the safest production-ready setup:

/** @type {import('next').NextConfig} */
const nextConfig = {
  assetPrefix: process.env.NEXT_PUBLIC_ASSET_PREFIX || '',
}

module.exports = nextConfig
NEXT_PUBLIC_ASSET_PREFIX=https://cdn.example.com

Then deploy your .next/static files to that CDN and verify that the browser loads chunk files from the configured host.

How to verify the fix

  1. Build and run the app.
  2. Open the browser developer tools.
  3. Inspect the Network tab.
  4. Reload the page.
  5. Confirm that JavaScript and CSS files under /_next/static/ load from your expected prefix.

If they still load from the app origin, your assetPrefix is not being applied correctly, or the app was built with stale configuration.

Common Edge Cases

Using basePath together with assetPrefix

basePath and assetPrefix solve different problems. basePath changes the application route prefix, while assetPrefix changes where Next.js assets are loaded from. Mixing them incorrectly can produce malformed URLs.

const nextConfig = {
  basePath: '/docs',
  assetPrefix: 'https://cdn.example.com',
}

module.exports = nextConfig

Test both page routes and static chunk URLs after enabling both settings.

Hardcoding public asset URLs in application code

assetPrefix only affects Next-managed assets. Files in /public or custom fetch URLs may still point to the wrong place if you hardcode paths manually.

<img src="/logo.png" alt="Logo" />

If those also need CDN routing, handle them separately through your CDN strategy or application config.

CDN missing the latest build files

A very common failure is setting assetPrefix correctly but not uploading the new build output. Then the HTML points to chunk filenames that do not exist yet on the CDN, causing 404 errors.

Always deploy the matching .next/static artifacts for the exact application build.

Development mode confusion

Some asset delivery behaviors differ in development. Validate the final setup with a production build:

npm run build
npm run start

Do not conclude that the production CDN setup is broken only because behavior looks different under the development server.

Multi-zone or multi-tenant deployments

If each tenant requires a different asset domain in the same running process, assetPrefix alone is usually not enough. This is where a reverse proxy or tenant-aware CDN routing becomes the right solution, because the emitted page markup must already contain correct URLs before the browser fetches assets.

FAQ

Can I set assetPrefix dynamically in the browser after the page starts loading?

Not reliably for Next.js application assets. By the time a runtime hook executes, many critical scripts and styles may already be resolved or requested. Use next.config, environment-based configuration, or a proxy/CDN layer instead.

What is the difference between assetPrefix and basePath?

assetPrefix changes the host or prefix used for Next.js-generated assets like chunks and CSS. basePath changes the application URL space, such as serving pages under /docs. They are related but not interchangeable.

Why does __next_set_public_path__ appear in examples or internal code?

Because Next.js has internal runtime logic for asset loading, but that does not mean it is a supported public API for custom dynamic asset host switching. Internal symbols can change without warning, so production code should avoid depending on them.

What is the best production fix for this issue?

The best fix is usually one of these two: set assetPrefix from an environment variable at build/start time, or keep assetPrefix standard and use a reverse proxy/CDN rule to route /_next/static/ requests correctly. Both are more stable than trying to mutate the public path at runtime.

In short, if your goal is to make Next.js load assets from different locations, solve it with supported configuration and infrastructure, not with __next_set_public_path__. That approach avoids brittle internals and gives you predictable chunk loading across builds, routers, and deployments.

Leave a Reply

Your email address will not be published. Required fields are marked *