How to Fix: CDN static resources issue with not-found component during the request
When Next.js is deployed behind a CDN assetPrefix, the not-found UI can break during a request because some static assets are resolved from the wrong origin. The result is usually missing CSS, broken JS chunks, or a 404 page that renders without the resources it needs. The fix is not just “set assetPrefix correctly” — it requires understanding how App Router, 404 rendering, and CDN-hosted _next/static files interact in production.
Table of Contents
Understanding the Root Cause
This issue typically appears when assetPrefix points static files to a CDN, but the not-found component is rendered during a request path that does not fully align with how those assets are generated or requested.
In Next.js, assetPrefix changes where build assets such as /_next/static/* are loaded from. In production, that means JavaScript chunks, CSS files, and other runtime resources are expected to come from the CDN instead of the application origin.
The problem shows up in one or more of these situations:
- The CDN is serving only part of the _next/static output.
- The CDN origin path does not match the app’s generated asset paths.
- The app uses basePath and assetPrefix together, but the resulting URL structure is inconsistent.
- The not-found.tsx page is rendered for a request where the browser then requests chunks from an incorrect host or prefix.
- A reverse proxy or CDN rewrites application routes correctly, but does not forward _next/static requests correctly.
Technically, the not-found component itself is not the real problem. The real problem is that the 404 rendering path still depends on the same compiled static resources as the rest of the app. If those files are unavailable, the page can partially render, hydrate incorrectly, or lose styling entirely.
Another subtle cause is that some teams expect assetPrefix to rewrite every URL in the app. It does not. It is meant for Next.js-managed assets, mainly _next/static. It is not a general-purpose routing or proxy solution for arbitrary files or API routes.
Step-by-Step Solution
The safest fix is to make sure your CDN hosts the exact _next/static output for the same build, and that Next.js generates URLs pointing to that location consistently.
1. Configure assetPrefix only for production
Your next.config.js should use a CDN prefix in production and no prefix in local development.
/** @type {import('next').NextConfig} */
const isProd = process.env.NODE_ENV === 'production'
const nextConfig = {
assetPrefix: isProd ? 'https://cdn.example.com' : '',
}
module.exports = nextConfig
If your CDN serves assets from a subpath, include that subpath explicitly:
const nextConfig = {
assetPrefix: isProd ? 'https://cdn.example.com/my-app' : '',
}
module.exports = nextConfig
This means Next.js will generate asset URLs such as:
https://cdn.example.com/_next/static/...
// or
https://cdn.example.com/my-app/_next/static/...
2. Upload the full .next/static directory to the CDN
After running the production build, Next.js emits static client assets under .next/static. Those files must exist on the CDN under _next/static.
The mapping should look like this:
Local build output:
.next/static/...
CDN path:
https://cdn.example.com/_next/static/...
If you use a subpath in assetPrefix:
Local build output:
.next/static/...
CDN path:
https://cdn.example.com/my-app/_next/static/...
A very common production mistake is uploading the wrong folder level, which creates invalid paths like:
https://cdn.example.com/static/...
https://cdn.example.com/.next/static/...
Those will not match what Next.js requests.
3. Keep the CDN build in sync with the deployed server build
The server rendering the request and the CDN serving _next/static must belong to the same build. If the app server deploys build B but the CDN still serves chunks from build A, the not-found page may request chunk files that do not exist.
Your deployment pipeline should follow this order:
- Run next build
- Upload the generated .next/static assets to the CDN
- Deploy the application server using the same build output
- Invalidate stale CDN cache if needed
# Example build flow
npm run build
# upload .next/static to CDN target _next/static
# deploy server artifacts from the same build
# purge CDN cache for changed assets if your setup requires it
4. Verify how not-found is rendered in your routing setup
If you are using the App Router, your app/not-found.tsx is rendered when notFound() is triggered or when a route is unresolved in the appropriate segment.
// app/not-found.tsx
export default function NotFound() {
return <h1>Page not found</h1>
}
If the HTML appears but styling or scripts fail only on this page, open the browser network tab and inspect requests to _next/static. If those requests return 404, 403, or the wrong host, the issue is your CDN pathing, not the component itself.
5. If using basePath, combine it carefully with assetPrefix
If your app is deployed under a subpath, you may also be using basePath. This is different from assetPrefix.
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
basePath: '/docs',
assetPrefix: isProd ? 'https://cdn.example.com' : '',
}
In that case:
- basePath affects application routes like /docs/page
- assetPrefix affects asset loading like https://cdn.example.com/_next/static/…
If your CDN also expects the app under a subpath, configure it explicitly and test both route URLs and asset URLs independently.
6. Do not use assetPrefix for public folder files unless your setup handles them separately
Files in public/ are not always solved by assetPrefix in the same way teams expect. If your 404 page references images like /logo.png, those may still come from the app origin unless you explicitly move them behind a CDN strategy.
export default function NotFound() {
return (
<div>
<img src="/logo.png" alt="Logo" />
<h1>Not Found</h1>
</div>
)
}
If your app origin and CDN behavior differ, static files from public can create false signals that make the issue look specific to not-found.
7. Test the generated asset URLs directly
After deployment, open page source or DevTools and confirm that scripts and styles point to the expected CDN host. Then open one of those asset links directly in the browser.
Expected outcome:
- The URL uses the configured assetPrefix
- The CDN returns 200 OK
- The file content matches the latest deployment
If needed, review the official Next.js assetPrefix documentation and the not-found file convention.
8. Example of a production-safe configuration
/** @type {import('next').NextConfig} */
const isProd = process.env.NODE_ENV === 'production'
const cdnHost = process.env.NEXT_PUBLIC_CDN_HOST
module.exports = {
reactStrictMode: true,
assetPrefix: isProd && cdnHost ? cdnHost : '',
}
Example environment variable:
NEXT_PUBLIC_CDN_HOST=https://cdn.example.com
This keeps local development simple while making production deterministic.
Common Edge Cases
- CDN cache lag: your app deploys instantly, but stale chunks remain cached on the CDN. Result: the not-found page references chunk names that do not exist yet.
- Trailing slash mismatch: an assetPrefix ending with a slash can produce double-slash URLs depending on surrounding infrastructure. Normalize the prefix carefully.
- basePath confusion: teams often prepend the base path to CDN paths manually and end up with duplicated segments.
- Custom rewrites/proxy rules: the main app routes work, but _next/static requests are blocked or rewritten incorrectly by Nginx, CloudFront, or another edge layer.
- Public assets on 404 pages: the page shell loads from CDN, but images or fonts referenced from public/ still hit the app origin and fail.
- Mixed builds across instances: one server instance renders HTML for a newer deployment while the CDN or another node still serves older static manifests.
- Static export assumptions: if your deployment process treats the app like a generic static site, dynamic App Router behavior around notFound() may not match expectations.
FAQ
Does assetPrefix affect API routes or page navigation?
No. assetPrefix is mainly for Next.js-generated assets such as _next/static. It does not automatically move API routes, page requests, or all public files to the CDN.
Why does the app work on normal pages but fail on the not-found page?
Normal routes may already have cached assets in the browser, while the not-found route triggers a different chunk graph or fresh asset requests. That makes CDN path issues much more visible on 404 rendering.
Should I use basePath instead of assetPrefix?
Only if the app itself is hosted under a URL subpath. Use basePath for route structure and assetPrefix for static asset hosting. They solve different problems and are often used together, not interchangeably.
The practical fix is simple: ensure your CDN serves the exact _next/static files for the same deployment, verify the generated asset URLs on the not-found request path, and avoid assuming that assetPrefix rewrites everything. Once those pieces are aligned, the production 404 page behaves like any other Next.js route.