Skip to content
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

feat: Sort query string keys #638

Closed

Conversation

hugotiger
Copy link
Contributor

@hugotiger hugotiger commented Sep 16, 2024

To avoid SEO issues from duplicate content this PR adds sorting of the query parameters based on their keys. That way the URL should be more predictable and canonicals won't be needed as often.

I went with using the native sort method on the URLSearchParams class to align it with existing standards.

As long as no user has depended on the order of the query string this PR shouldn't be breaking.

BREAKING CHANGE: drop CJS support.

Since Next has ESM support since v12, it should not really be a breaking change for most.
BREAKING CHANGE: the following deprecated APIs have been removed:
- `queryTypes` bag of parsers -> use individual `parseAsXYZ` parsers
  for better tree-shakeability.
- `subscribeToQueryUpdates` helper -> since Next.js 14.0.5,
  `useSearchParams` is reactive to shallow search params updates
  in the app router. See 47ng#425.
- Internal types and aliases
BREAKING CHANGE: package is now only updating on the `nuqs` name.

The debugging printouts no longer check for next-usequerystate, only nuqs.
BREAKING CHANGE: export path has been renamed. Contents are identical.

Since the `/parsers` export contained the server cache,
this name makes better sense and helps outline the client/server
nature of features in nuqs.
BREAKING CHANGE: Drop support for next@14.0.3

Due to a bug in the implementation of shallow routing (WHS),
14.0.3 required a special case for syncing
against external navigation.

In `nuqs@2.x`, we're cleaning this up and requiring
a version of Next.js with bug-free support for
shallow routing (with or without experimental WHS
in 14.0.4, and with stabilised WHS in 14.0.5 onwards).
Using a hack around the serializer to apply the same
encoding as the hooks would in the URL.
Next.js versions between 14.0.2 and 14.1.0 contain bugs on
shallow routing (or other routing code) that makes `nuqs`
hard to maintain for these versions.
- Follow the Rules of React to init the query ref once on mount
- Remove access to the queued value, doesn't seem to change
outcome of issue 359
Copy link

vercel bot commented Sep 16, 2024

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

A member of the Team first needs to authorize it.

@franky47
Copy link
Member

Thanks! Could you rebase it on top of the v2/release branch, and change the PR target branch to that please?

@franky47
Copy link
Member

Also, as an FYI, this change may be unexpected for most people: https://x.com/fortysevenfx/status/1835384141808021801

I'll play with this PR once it's ready, and we'll see if it makes sense to merge it as-is or to provide some other kind of API to sort the keys in a one-shot way, or to provide this behaviour as an option.

@hugotiger hugotiger force-pushed the feat/sort-search-param-keys branch from 3605b98 to 6984607 Compare September 16, 2024 10:00
@hugotiger hugotiger changed the base branch from next to v2/release September 16, 2024 10:01
@hugotiger hugotiger force-pushed the feat/sort-search-param-keys branch from 6984607 to 71c84cc Compare September 16, 2024 11:12
@hugotiger hugotiger changed the title Feat/sort search param keys feat: Sort query string keys Sep 16, 2024
@hugotiger
Copy link
Contributor Author

hugotiger commented Sep 16, 2024

Also, as an FYI, this change may be unexpected for most people: https://x.com/fortysevenfx/status/1835384141808021801

I'll play with this PR once it's ready, and we'll see if it makes sense to merge it as-is or to provide some other kind of API to sort the keys in a one-shot way, or to provide this behaviour as an option.

A rough first version is ready now. It's one of my first time contributing to an OSS-project so sorry if I'm missing something. Let me know and I'll fix it 😄

I understand this change might be considered somewhat controversial, especially regarding backward compatibility. Like you're saying, it might be good to have an option to enable/disable it.

However, I believe the default behavior should remain sorted. While it may not align with everyone’s expectations, it provides significant SEO benefits, which would likely be valuable to a majority of users. Similar to how this library has some handy opinionated defaults out of the box (replace, reset on null etc.), I believe sorting should be the default as it’s what would be the most practical.

@hugotiger hugotiger marked this pull request as ready for review September 16, 2024 11:33
const query: string[] = []
for (const [key, value] of search.entries()) {
for (const [key, value] of entries) {
Copy link
Contributor

Choose a reason for hiding this comment

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

URLSearchParams is iterable on its own which is neat

Screenshot 2024-09-23 at 7 03 53 PM

A SO answer from a self-described v8 dev on the cost of Object.entries() https://stackoverflow.com/a/64911507

I am assuming URLSearchParams proxies to Object.entries or is implemented the same way. This feels a bit pedantic, but if encoding happens frequently and with a large amount of parameters, I could imagine this mattering.

Copy link
Contributor

@tylersayshi tylersayshi Sep 24, 2024

Choose a reason for hiding this comment

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

I tried a benchmark and my theory of this helping was dead wrong lol sorry

benchmark (from perf.link)

still is neat to know imo, but please disregard other than the syntax difference

Copy link
Member

Choose a reason for hiding this comment

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

I suppose it's implemented the same way internally.

The main advantage of iterating directly on the URLSearchParams is skipping an extra entries variable, so saving memory and a few bytes of bundle size.

Copy link
Member

@franky47 franky47 left a comment

Choose a reason for hiding this comment

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

Thinking about this, we should probably mutate the URLSearchParams passed in argument rather than copying, as it will be returned by the Promise of useQueryState(s) state updater functions, and it should reflect the order.

To keep the encoding function pure, the sort call should then happen after the search params have been populated, but before encoding.

This could also allow placing this behaviour behind an option. A good place for it would be the flushUpdateQueue function in update-queue.ts.

@franky47 franky47 added this to the v2 milestone Sep 27, 2024
@franky47 franky47 force-pushed the v2/release branch 2 times, most recently from 972ec59 to 28dfb30 Compare October 3, 2024 22:22
@franky47 franky47 force-pushed the v2/release branch 4 times, most recently from c281958 to 39b84be Compare October 11, 2024 19:31
@franky47 franky47 removed this from the v2 milestone Oct 21, 2024
@franky47 franky47 deleted the branch 47ng:v2/release October 22, 2024 11:38
@franky47 franky47 closed this Oct 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants