Skip to content

Conversation

gensmusic
Copy link

@gensmusic gensmusic commented Sep 10, 2025

Fix TanStack Router search params serialization

Summary

This PR fixes #1127 an issue in the Nuqs TanStack Router adapter where search params
containing objects or arrays were being incorrectly serialized into [object Object].

Changes

  • Replaced the previous manual flattening logic with stringifySearchWith(JSON.stringify) from TanStack Router.
  • Ensures that nested objects/arrays in search params are serialized as valid JSON strings.
  • Keeps the adapter behavior consistent with TanStack Router defaults, reducing differences between routers (e.g., Next.js vs TSR).

Notes

  • This PR is intentionally kept small and focused on fixing the core serialization issue.
  • I’m not deeply familiar with Nuqs internals, so additional features or improvements to the TSR adapter can be added in future PRs.
  • Related discussion: PR #953 comment
  • Issue tracking this bug: Issue #1127

Fixes #1127.

Copy link

vercel bot commented Sep 10, 2025

@gensmusic is attempting to deploy a commit to the 47ng Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

vercel bot commented Sep 10, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
nuqs Ready Ready Preview Comment Sep 10, 2025 3:41pm

@franky47
Copy link
Member

franky47 commented Sep 10, 2025

Thanks, that's a better approach indeed.

However, it would not work for folks who have customised their (de)serialisation layer (say, to encode all search params as base64, or using jsurl2):
https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization

One way we could support this is to pass the stringifySearchWith as an adapter prop, propagate it down via Context, and read it in the adapter hook. See how it's done in the React SPA adapter, there are a few contexts there to pass stuff down.

Now this would be hard to test though, as it's a global config on the router, and the e2e tests are asserting on the default SerDe layer.

For the record, the previous approach wouldn't have worked with a custom SerDe either, so there's that.. 😅

@franky47 franky47 changed the title fix: Use TanStack Router default stringify for search params (#1127) fix: Use TanStack Router default stringify for search params Sep 10, 2025
@gensmusic
Copy link
Author

@franky47 I just add the support for custom stringifySearchWith

@gensmusic
Copy link
Author

Please ignore the latest commit for now, it's far more complicated than I thought!

@gensmusic
Copy link
Author

This change has been tested locally and works as expected.

Parsing search params

Inside TanStack Router, parseSearchWith(default or custom) has already been applied, so the search we get in our code is an object that has already been parsed.

However, nuqs expects URLSearchParams. To make it compatible, we use the default defaultStringifySearch (based on JSON.stringify) to convert the object back into a format nuqs can handle correctly.

Synchronizing search params

When nuqs updates the search params, the UpdateUrlFunction receives nuqs-generated URLSearchParams.

If there is a custom stringifySearchWith, We first use the default defaultParseSearch to convert the nuqs-generated URLSearchParams into an object, then pass that object through the user-defined stringifySearchWith to produce the desired URLSearchParams.

If no custom stringifySearchWith is provided, we simply fall back to the default behavior, which is compatible with nuqs’ expectations.

        ┌─────────────────────┐
        │   Browser URL query  │
        └──────────┬──────────┘
                   │
                   ▼
        ┌─────────────────────┐
        │ TanStack Router      │
        │ parseSearchWith(...) │
        └──────────┬──────────┘
                   │ produces search object
                   ▼
        ┌─────────────────────┐
        │ Our Adapter          │
        │ defaultStringify     │
        │ → URLSearchParams    │
        └──────────┬──────────┘
                   │ passed to nuqs
                   ▼
        ┌─────────────────────┐
        │        nuqs          │
        │ consumes searchParams │
        └──────────┬──────────┘
                   │
                   ▼
        ┌─────────────────────┐
        │ User updates search  │
        │ params in nuqs       │
        └──────────┬──────────┘
                   │ provides URLSearchParams
                   ▼
        ┌─────────────────────┐
        │ Our Adapter          │
        │ defaultParseSearch   │
        │ → object → stringify │
        │ (custom or default)  │
        └──────────┬──────────┘
                   │
                   ▼
        ┌─────────────────────┐
        │ TanStack Router      │
        │ navigate(to=...)     │
        └─────────────────────┘

@gensmusic
Copy link
Author

@franky47 If you have a moment, I’d love your review on this PR.

@franky47
Copy link
Member

Thanks, I'll give it a try tonight.

@franky47 franky47 added the deploy:preview Deploy a preview version of this PR on pkg.pr.new label Sep 10, 2025
Copy link

pkg-pr-new bot commented Sep 10, 2025

pnpm add https://pkg.pr.new/nuqs@1128

commit: e6caffe

// Use TSR's default stringify to convert search object → URLSearchParams.
// This avoids issues where arrays/objects were previously flattened
// into invalid values like "[object Object]".
return new URLSearchParams(defaultStringifySearch(search))
Copy link
Contributor

Choose a reason for hiding this comment

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

I looked at this PR in the context of multi-parsers (#1134), but they completely stop working in TanStack Router when I apply the changes from this PR.

The problem seems to be this call to defaultStringifySearch. For example, if search is:

{
  test: [1,2]
}

The call to defaultStringifySearch turns that into "?test=%5B1%2C2%5D", which gives us searchParams with a size of one where the value for test is the string [1,2].

This wasn’t the case with the previous implementation, where searchParams would correctly have a size of two.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
adapters/tanstack-router Uses the TanStack Router adapter deploy:preview Deploy a preview version of this PR on pkg.pr.new
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[bug] TanStack Router adapter: search params with array of objects serialized as [object Object]
3 participants