Skip to content

[backport] Ensure setAssetPrefix updates config instance #82165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1707,7 +1707,8 @@ export default abstract class Server<
): Promise<void>

public setAssetPrefix(prefix?: string): void {
this.renderOpts.assetPrefix = prefix ? prefix.replace(/\/$/, '') : ''
this.nextConfig.assetPrefix = prefix ? prefix.replace(/\/$/, '') : ''
Copy link

@vercel vercel bot Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setAssetPrefix() method now sets this.nextConfig.assetPrefix instead of this.renderOpts.assetPrefix, but rendering code expects the asset prefix to be available in renderOpts.

View Details

Analysis

The change from this.renderOpts.assetPrefix = prefix to this.nextConfig.assetPrefix = prefix breaks the asset prefix functionality during rendering. Multiple parts of the rendering pipeline expect renderOpts.assetPrefix to be available:

  1. packages/next/src/server/post-process.ts:45 uses renderOpts.assetPrefix for CSS optimization
  2. packages/next/src/server/async-storage/work-store.ts:137 uses renderOpts?.assetPrefix || '' for work store setup
  3. Both the Pages and App Router RenderOptsPartial type definitions include assetPrefix?: string as an expected property

The renderOpts object is created by spreading this.renderOpts (lines 1798 and 2552), but the initial renderOpts construction (line 562+) doesn't include assetPrefix from nextConfig. The asset prefix is meant to be set dynamically via the setAssetPrefix() method, but now it's being set on the wrong object.

This will cause asset prefix functionality to fail, resulting in incorrect URLs for static assets, potentially breaking CSS optimization, and causing the work store to default to an empty asset prefix.


Recommendation

Change line 1710 back to set the asset prefix on renderOpts instead of nextConfig:

this.renderOpts.assetPrefix = prefix ? prefix.replace(/\/$/, '') : ''

If there's a need to also update nextConfig.assetPrefix for consistency, both should be set:

const normalizedPrefix = prefix ? prefix.replace(/\/$/, '') : ''
this.renderOpts.assetPrefix = normalizedPrefix
this.nextConfig.assetPrefix = normalizedPrefix

this.renderOpts.assetPrefix = this.nextConfig.assetPrefix
}

protected prepared: boolean = false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { nextTestSetup } from 'e2e-utils'

describe('app-dir absolute assetPrefix', () => {
const { next } = nextTestSetup({
files: __dirname,
nextConfig: {
assetPrefix: 'https://example.vercel.sh/',
},
})

it('bundles should return 200 on served assetPrefix', async () => {
const $ = await next.render$('/')

let bundles = []
for (const script of $('script').toArray()) {
const { src } = script.attribs
if (src?.includes('https://example.vercel.sh/_next/static')) {
bundles.push(src)
}
}

expect(bundles.length).toBeGreaterThan(0)

for (const src of bundles) {
// Remove hostname to check if pathname is still used for serving the bundles
const bundlePathWithoutHost = decodeURI(new URL(src).pathname)
const { status } = await next.fetch(bundlePathWithoutHost)

expect(status).toBe(200)
}
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { nextTestSetup } from 'e2e-utils'
describe('app-dir absolute assetPrefix', () => {
const { next } = nextTestSetup({
files: __dirname,
nextConfig: {
assetPrefix: 'https://example.vercel.sh/custom-asset-prefix',
},
})

it('bundles should return 200 on served assetPrefix', async () => {
Expand Down
3 changes: 0 additions & 3 deletions test/e2e/app-dir/asset-prefix-absolute/next.config.js

This file was deleted.

Loading