Skip to content

fix: correctly merge query params when preview-url contains query string#34685

Closed
aayushbaluni wants to merge 1 commit into
storybookjs:nextfrom
aayushbaluni:fix/34615-preview-url-query-string
Closed

fix: correctly merge query params when preview-url contains query string#34685
aayushbaluni wants to merge 1 commit into
storybookjs:nextfrom
aayushbaluni:fix/34615-preview-url-query-string

Conversation

@aayushbaluni
Copy link
Copy Markdown

@aayushbaluni aayushbaluni commented May 2, 2026

Problem

When --preview-url includes a query string (e.g., http://localhost:6007/?custom=true), Storybook 10.2+ appends additional parameters with a second ? instead of &, producing a malformed iframe URL.

Change

Use proper URL/query string merging when building the iframe src so existing query parameters from the preview URL are preserved and new parameters are appended with &.

Fixes #34615.

Made with Cursor

Summary by CodeRabbit

  • Bug Fixes
    • Improved URL parameter handling for story preview links to properly preserve existing query parameters and prevent malformed URLs.
    • Enhanced URL encoding consistency when constructing preview URLs with dynamic parameters.
    • Fixed edge case handling when preview base URLs already contain query parameters.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

📝 Walkthrough

Walkthrough

The change refactors getStoryHrefs in the URL manager to resolve previewBase into an absolute URL and use the native URL API with searchParams for setting id, viewMode, and optional refId instead of string concatenation. Preview href assembly now conditionally includes globalsParam only when refId is absent. A test verifies correct behavior when PREVIEW_URL already contains query parameters.

Changes

URL Resolution and Query Parameter Handling

Layer / File(s) Summary
URL Parsing and Setup
code/core/src/manager-api/modules/url.ts (lines 264–278)
previewBase is resolved to an absolute URL via new URL(), then id, viewMode, and optional refId are set on its searchParams. The old refParam helper is removed.
Preview Href Assembly
code/core/src/manager-api/modules/url.ts (lines 279–304)
previewHref is constructed from previewHrefPrefix (origin only), previewPathQuery, previewSuffix (conditionally includes globalsParam only when no refId), and previewHash. Query string assembly via string concatenation is replaced with URL API composition.
Tests
code/core/src/manager-api/tests/url.test.js
New test case validates that getStoryHrefs correctly preserves and merges query parameters when PREVIEW_URL already contains custom parameters, preventing malformed query strings.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • storybookjs/storybook#33647: Modifies the same getStoryHrefs function and test file in parallel to address URL construction and query parameter handling.
  • storybookjs/storybook#33535: Also refactors getStoryHrefs to prevent manager-side query params from propagating incorrectly into preview URLs via URL API usage.
  • storybookjs/storybook#33896: Updates previewBase and previewHref construction logic in the same module with related URL resolution patterns.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/core/src/manager-api/modules/url.ts (1)

290-304: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Merge queryParams into the URL object instead of appending them.

This still duplicates keys that already exist in PREVIEW_URL. For example, PREVIEW_URL='.../?custom=true' plus queryParams: { custom: 'false' } produces both values, so URLSearchParams.get('custom') still resolves to the old one. The fix should overwrite preview-side params on previewUrlObject.searchParams before serializing the final href.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/manager-api/modules/url.ts` around lines 290 - 304, The preview
href is built by string-concatenating query strings which preserves existing
PREVIEW_URL keys instead of overwriting them; replace that string concat with a
URL object merge: construct a URL (e.g., new URL(previewHrefPrefix +
previewPathQuery + previewHash) or new URL(previewHrefPrefix) with
previewPathQuery applied), then iterate over the provided queryParams (and any
computed params like customPreviewParams/argsParam/globalsParam) and call
previewUrlObject.searchParams.set(key, value) to overwrite existing keys on
previewUrlObject.searchParams, then serialize previewHref from
previewUrlObject.toString(); update the return that sets previewHref (and keep
managerHref logic unchanged) and ensure you reference previewHrefPrefix,
previewPathQuery, previewHash, customPreviewParams, argsParam, globalsParam, and
previewHref when making these changes.
🧹 Nitpick comments (1)
code/core/src/manager-api/tests/url.test.js (1)

507-524: ⚡ Quick win

Add a relative PREVIEW_URL case here too.

This only covers the absolute branch, but the original repro also uses relative overrides like iframe.html?foo=bar. A companion assertion for a relative preview URL would cover the isAbsolutePreviewUrl === false path that now does the extra resolve/strip-origin work.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/manager-api/tests/url.test.js` around lines 507 - 524, The test
only exercises the absolute PREVIEW_URL branch; add a companion test that sets
global.PREVIEW_URL to a relative value like 'iframe.html?custom=true', calls
initURL (same usage as the existing test), calls
api.getStoryHrefs('test--story'), parses previewHref and asserts the same
expectations (search param 'custom' === 'true', 'id' === 'test--story',
'viewMode' === 'story', and no duplicate '?' in previewHref), and clean up by
deleting global.PREVIEW_URL; this will exercise the isAbsolutePreviewUrl ===
false path and the resolve/strip-origin logic in initURL/getStoryHrefs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@code/core/src/manager-api/modules/url.ts`:
- Around line 290-304: The preview href is built by string-concatenating query
strings which preserves existing PREVIEW_URL keys instead of overwriting them;
replace that string concat with a URL object merge: construct a URL (e.g., new
URL(previewHrefPrefix + previewPathQuery + previewHash) or new
URL(previewHrefPrefix) with previewPathQuery applied), then iterate over the
provided queryParams (and any computed params like
customPreviewParams/argsParam/globalsParam) and call
previewUrlObject.searchParams.set(key, value) to overwrite existing keys on
previewUrlObject.searchParams, then serialize previewHref from
previewUrlObject.toString(); update the return that sets previewHref (and keep
managerHref logic unchanged) and ensure you reference previewHrefPrefix,
previewPathQuery, previewHash, customPreviewParams, argsParam, globalsParam, and
previewHref when making these changes.

---

Nitpick comments:
In `@code/core/src/manager-api/tests/url.test.js`:
- Around line 507-524: The test only exercises the absolute PREVIEW_URL branch;
add a companion test that sets global.PREVIEW_URL to a relative value like
'iframe.html?custom=true', calls initURL (same usage as the existing test),
calls api.getStoryHrefs('test--story'), parses previewHref and asserts the same
expectations (search param 'custom' === 'true', 'id' === 'test--story',
'viewMode' === 'story', and no duplicate '?' in previewHref), and clean up by
deleting global.PREVIEW_URL; this will exercise the isAbsolutePreviewUrl ===
false path and the resolve/strip-origin logic in initURL/getStoryHrefs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b05e0993-cef6-41e1-aa61-228acdf415b4

📥 Commits

Reviewing files that changed from the base of the PR and between f9060db and 2423d4a.

📒 Files selected for processing (2)
  • code/core/src/manager-api/modules/url.ts
  • code/core/src/manager-api/tests/url.test.js

@valentinpalkovic
Copy link
Copy Markdown
Contributor

Hi @aayushbaluni,

Due to a recent high volume of unreviewed AI-generated PRs, we are requesting verification and proof that the implemented fix actually works. Please provide a simple GIF/Video or image of how the fix works, optimally with before-and-after comparisons.

Thank you for your understanding!

@valentinpalkovic
Copy link
Copy Markdown
Contributor

Closing due to inactivity

@github-project-automation github-project-automation Bot moved this from Human verification to Done in Core Team Projects May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug]: --preview-url with query string produces malformed iframe URL in 10.2+

2 participants