Skip to content

fix(turbopack): prevent double-encoding of WASM fetch URLs when deployment suffix already present#92369

Open
sleitor wants to merge 2 commits intovercel:canaryfrom
sleitor:fix-92356
Open

fix(turbopack): prevent double-encoding of WASM fetch URLs when deployment suffix already present#92369
sleitor wants to merge 2 commits intovercel:canaryfrom
sleitor:fix-92356

Conversation

@sleitor
Copy link
Copy Markdown
Contributor

@sleitor sleitor commented Apr 5, 2026

What?

Fixes #92356 (regression from #88828, introduced in 16.2.0).

Why?

When a WASM module exports its path via __turbopack_export_url__ (.q()), the exported value already has ASSET_SUFFIX (?dpl=xxx) appended. When the loader later calls fetchWebAssembly(wasmChunkPath), which calls getChunkRelativeUrl(wasmChunkPath), the function:

  1. Splits the path by / — the last segment now contains ?dpl=xxx
  2. Runs encodeURIComponent on each segment — encoding ?%3F
  3. Appends ASSET_SUFFIX again

Result: /_next/static/chunks/xxx.wasm%3Fdpl%3Ddpl_xxx?dpl=dpl_xxx404

JS and CSS chunks are unaffected because they are loaded via <script>/<link> tags where the URL is set directly, not through getChunkRelativeUrl.

How?

In getChunkRelativeUrl, detect if chunkPath already contains a query string. If so, split the path from the query before encoding, and use the existing query string as the suffix instead of appending ASSET_SUFFIX a second time. If no query string is present (normal JS/CSS chunks), behaviour is unchanged.

// Before
function getChunkRelativeUrl(chunkPath) {
  return `${CHUNK_BASE_PATH}${chunkPath.split('/').map(p => encodeURIComponent(p)).join('/')}${ASSET_SUFFIX}`
}

// After — detects when path already has query string
function getChunkRelativeUrl(chunkPath) {
  const qi = chunkPath.indexOf('?')
  const pathPart = qi !== -1 ? chunkPath.slice(0, qi) : chunkPath
  const querySuffix = qi !== -1 ? chunkPath.slice(qi) : ASSET_SUFFIX
  return `${CHUNK_BASE_PATH}${pathPart.split('/').map(p => encodeURIComponent(p)).join('/')}${querySuffix}`
}

Also updates all affected snapshot test files to reflect the new implementation.

Fixes #92356

@nextjs-bot nextjs-bot added the Turbopack Related to Turbopack with Next.js. label Apr 5, 2026
@nextjs-bot
Copy link
Copy Markdown
Contributor

Allow CI Workflow Run

  • approve CI run for commit: 4b4e15cda8b7edc78215d8b0d8e57eaccaea6dca

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@nextjs-bot
Copy link
Copy Markdown
Contributor

nextjs-bot commented Apr 5, 2026

Allow CI Workflow Run

  • approve CI run for commit: 2ae3d45

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 5, 2026

Merging this PR will degrade performance by 3.31%

❌ 1 regressed benchmark
✅ 16 untouched benchmarks
⏩ 3 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation packages-bundle.js[full] 990.3 ms 1,024.2 ms -3.31%

Comparing sleitor:fix-92356 (6e8ec0f) with canary (15d9b4d)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@mischnic
Copy link
Copy Markdown
Member

mischnic commented Apr 5, 2026

Could you add this to the test case in test/production/deployment-id-handling/deployment-id-handling.test.ts ?
So just adding loading a Wasm file in that test app should be enough

@sleitor
Copy link
Copy Markdown
Contributor Author

sleitor commented Apr 6, 2026

Done — added in commit 6e8ec0f:

  • Added a minimal add.wasm binary (the same one used in test/production/app-dir-edge-runtime-with-wasm/) to the deployment-id-handling test app under app/app/add.wasm
  • Added a new /wasm-page route that dynamically instantiates the WASM module on button click
  • Added a Turbopack-only test case in deployment-id-handling.test.ts that intercepts WASM fetch requests and verifies:
    1. Each WASM URL includes dpl= exactly once
    2. No percent-encoded %3F (which would indicate a double-encoded query string)

The test will catch the regression described in the issue — before the fix, fetch() on WASM modules would produce URLs like ...chunk.wasm%3Fdpl=... (with the ? encoded), whereas the fix ensures clean ...chunk.wasm?dpl=... URLs.

@sleitor
Copy link
Copy Markdown
Contributor Author

sleitor commented Apr 6, 2026

The CodSpeed 3.31% regression on packages-bundle.js[full] is expected and unavoidable — it reflects the 9 extra lines added to runtime-base.ts (the Turbopack JS runtime template that gets embedded in every bundle output). The benchmark measures bundling throughput for a full package bundle that includes the compiled runtime; a slightly larger runtime template produces a slightly larger compiled output, causing the minor throughput decrease.

This is not a regression in WASM loading performance at runtime — the fix is purely in the string manipulation logic of getChunkRelativeUrl (strip query before encoding, re-append query after). The added code is O(1) per chunk fetch call and negligible in practice.

The alternative would be a compile-time fix in the Rust code that prevents ASSET_SUFFIX from being appended before the URL hits getChunkRelativeUrl, which would avoid touching the runtime template entirely — but that would be a larger and riskier change. Happy to explore that approach if preferred.

@lukesandberg lukesandberg requested a review from mischnic April 7, 2026 01:21
sleitor added 2 commits April 18, 2026 17:07
…FFIX already present

When a WASM module exports its path via `__turbopack_export_url__` (.q()),
the exported value already has ASSET_SUFFIX (`?dpl=xxx`) appended. When the
loader later calls `fetchWebAssembly(wasmChunkPath)`, which in turn calls
`getChunkRelativeUrl(wasmChunkPath)`, the function splits by '/' and calls
`encodeURIComponent` on each segment — encoding the '?' to '%3F' — then
appends ASSET_SUFFIX a second time. This results in a URL like:

  /_next/static/chunks/xxx.wasm%3Fdpl%3Ddpl_xxx?dpl=dpl_xxx

instead of the expected:

  /_next/static/chunks/xxx.wasm?dpl=dpl_xxx

Fix: in `getChunkRelativeUrl`, detect if chunkPath already contains a query
string and, if so, separate it from the path before encoding, then use the
existing query string as the suffix instead of appending ASSET_SUFFIX again.

JS/CSS chunks are unaffected because they load via `<script>`/`<link>` tags
where the src is set directly from a ChunkUrl, not through this path.

Regression introduced in 16.2.0 (vercel#88828). Fixes vercel#92356.

Update turbopack snapshot tests to reflect new `getChunkRelativeUrl` impl.

# Conflicts:
#	turbopack/crates/turbopack-tests/tests/snapshot/debug-ids/browser/output/1do3_crates_turbopack-tests_tests_snapshot_debug-ids_browser_input_index_19boa0e.js.map
#	turbopack/crates/turbopack-tests/tests/snapshot/runtime/default_dev_runtime/output/0_9x_turbopack-tests_tests_snapshot_runtime_default_dev_runtime_input_index_17smy-b.js.map
#	turbopack/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/0_9x_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_04jskxh.js.map
#	turbopack/crates/turbopack-tests/tests/snapshot/workers/basic/output/1do3_crates_turbopack-tests_tests_snapshot_workers_basic_input_index_09-gc7x.js.map
#	turbopack/crates/turbopack-tests/tests/snapshot/workers/basic/output/1do3_crates_turbopack-tests_tests_snapshot_workers_basic_input_worker_0yr5fg0.js.map
#	turbopack/crates/turbopack-tests/tests/snapshot/workers/shared/output/1do3_crates_turbopack-tests_tests_snapshot_workers_shared_input_index_0arnewp.js.map
#	turbopack/crates/turbopack-tests/tests/snapshot/workers/shared/output/1do3_crates_turbopack-tests_tests_snapshot_workers_shared_input_worker_1xw116u.js.map
# Conflicts:
#	test/production/deployment-id-handling/deployment-id-handling.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Turbopack Related to Turbopack with Next.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Turbopack: WASM fetch URLs return 404 on Vercel due to double-encoded deployment suffix

3 participants