nuqs: Add a nice-url option that does not encode `/`, objects and arrays

Right now, all URLs are encoded in a way that make all “non-trivial” characters and arrays and objects look long, complex and quite frankly ugly.

It would be great to have the option opt out of the full and automatic encoding. Which also means the developer is responsible to make sure the URL does not have any problematic characters.

Config example from sindresorhus/query-string: This library has an option to set encode:false.

I used this in a previous project (more…)

Example of how it looks right now: /regionen/trto?map=10.6%2F53.6774%2F13.267&config=%21%28i~fromTo~a~~topics~… I would like the map to look as nice as on OSM https://www.openstreetmap.org/#map=10/53.6774/13.2670 for example 😃.

Example of our previous URL…

Our previous URL was not pretty either, but still al lot nicer to look but especially shorter at then the encoded version: https://radverkehrsatlas.de/regionen/trto?lat=53.6774&lng=13.267&zoom=10.6&theme=fromTo&bg=default&config=!(i~fromTo~topics~!(i~shops~s~!(i~hidden~a~_F)(i~default~a))(i~education~s~!(i~hidden~a)(i~default~a~_F))(i~places~s~!(i~hidden~a~_F)(i~default~a)(i~circle~a~_F))(i~buildings~s~!(i~hidden~a)(i~default~a~_F))(i~landuse~s~!(i~hidden~a~_F)(i~default~a))(i~barriers~s~!(i~hidden~a~_F)(i~default~a))(i~boundaries~s~!(i~hidden~a)(i~default~a~_F)(i~level-8~a~_F)(i~level-9-10~a~_F)))(i~bikelanes~topics~!(i~bikelanes~s~!(i~hidden~a~_F)(i~default~a)(i~verification~a~_F)(i~completeness~a~_F))(i~bikelanesPresence*_legacy~s~!(i~hidden~a)(i~default~a~_F))(i~places~s~!(i~hidden~a~_F)(i~default~a)(i~circle~a~_F))(i~landuse~s~!(i~hidden~a)(i~default~a~_F)))(i~roadClassification~topics~!(i~roadClassification*_legacy~s~!(i~hidden~a~_F)(i~default~a)(i~oneway~a~_F))(i~bikelanes~s~!(i~hidden~a)(i~default~a~_F)(i~verification~a~_F)(i~completeness~a~_F))(i~maxspeed*_legacy~s~!(i~hidden~a)(i~default~a~_F)(i~details~a~_F))(i~surfaceQuality*_legacy~s~!(i~hidden~a)(i~default~a~_F)(i~bad~a~_F)(i~completeness~a~_F)(i~freshness~a~_F))(i~places~s~!(i~hidden~a~_F)(i~default~a)(i~circle~a~_F))(i~landuse~s~!(i~hidden~a)(i~default~a~_F)))(i~lit~topics~!(i~lit*_legacy~s~!(i~hidden~a~_F)(i~default~a)(i~completeness~a~_F)(i~verification~a~_F)(i~freshness~a~_F))(i~places~s~!(i~hidden~a)(i~default~a~_F)(i~circle~a~_F))(i~landuse~s~!(i~hidden~a)(i~default~a~_F)))~

Current implementation: Reading https://github.com/47ng/next-usequerystate/discussions/343#discussioncomment-6985578 the encoding is done by calling URLSearchParams.toString which encodes everything with encodeURIComponent.

Possible solution: https://stackoverflow.com/questions/71316183/urlsearchparams-set-without-uriencoding show possible solutions to decode the URL before returning it either fully or in parts.

About this issue

  • Original URL
  • State: closed
  • Created 9 months ago
  • Reactions: 2
  • Comments: 18 (13 by maintainers)

Commits related to this issue

Most upvoted comments

Giving this a try as I was porting shadcn/ui’s Tasks demo to next-usequerystate, and sorting options were indeed ugly.

Here’s what we could do 🙃 (hint: maybe don’t.)

https://github.com/47ng/next-usequerystate/assets/1174092/d5a8c4eb-ffc4-4ac5-a2ff-728741f84177

I’ll try this on multiple browsers to see how they might accept non-encoded special characters and fine tune the query string renderer.

If a good set of defaults can be supported, I think we could avoid having an option for this and make it the default.

Personally, I would be fine if the library had an option to hand over control if I want the saved (encoded) route or if I want to handle encoding myself.

I’d say the easiest way to do this would be to implement a custom parser/serializer, that pre-encodes the string value as needed before returning it from the serialize method.

I’ve published 1.9.0-beta.1 which includes test cases for your URLs, could you give it a try and see how far you can go before it breaks horribly? I have included the reasoning in PR #372 (a review would be most welcome).

Thanks @tordans! From my initial tests, it’s not just about encoding for pretty URL display in the browser, but more importantly to make sure copy-pasting a URL to share it with others won’t break the link by containing characters that may indicate the end of the URL, and drop the rest of the query string.

As an example, here’s the raw URL from above, unencoded:

https://example.com?exclamationMark=!&doubleQuote="&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe='&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=<&equals==&greaterThan=>&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~

GitHub seems to break at the < and > characters. In contrast, VSCode’s builtin URL detection feature breaks earlier, at the ", ' and backtick characters. Other platforms (eg: social networks, messengers) may have other URL-breaking characters.

Chrome 117, macOS, https://www.whatsmybrowser.org/b/MO73G

https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~

Safari 16, macOS, https://www.whatsmybrowser.org/b/20H86

https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~

Firefox 118, macOS, https://www.whatsmybrowser.org/b/51WS3

https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~

Safari 16, iOS, https://www.whatsmybrowser.org/b/40G5X https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%25&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=%5B&backslash=&rightSquareBracket=%5D&caret=%5E&underscore=_&backtick=%60&leftCurlyBrace=%7B&pipe=%7C&rightCurlyBrace=%7D&tilde=~

Chrome 118, iOS, https://www.whatsmybrowser.org/b/CTINA

https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%25&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=%5B&backslash=&rightSquareBracket=%5D&caret=%5E&underscore=_&backtick=%60&leftCurlyBrace=%7B&pipe=%7C&rightCurlyBrace=%7D&tilde=~

Safari UIWebView 15 (Firefox Klar), macOS, https://www.whatsmybrowser.org/b/V5YGS

https://example.com?exclamationMark=!&doubleQuote="&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe='&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=<&equals==&greaterThan=>&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~

(Note: All macOS are copied in Workflowy first to transfer them in this issue, that might change thing again…)

I asked the LLMs to generate a URL that contains all non-alphanumerical printable ASCII characters to see which ones end up being encoded when displayed in the URL bar.

Could you please:

  1. Copy the following URL:
https://example.com?exclamationMark=!&doubleQuote="&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe='&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=<&equals==&greaterThan=>&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
  1. Paste it in a browser of your choice
  2. Copy it again from the URL bar
  3. Paste the result here with the browser info (operating system + browser name, versions are probably not relevant).

FYI, browsers on Linux would be much appreciated, thanks! 🙏

Edit: results so far:

Raw unencoded:      https://example.com?exclamationMark=!&doubleQuote="&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe='&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=<&equals==&greaterThan=>&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Firefox macOS:      https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Chrome macOS:       https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Safari macOS:       https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Edge macOS:         https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Chrome iOS:         https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%25&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=%5B&backslash=&rightSquareBracket=%5D&caret=%5E&underscore=_&backtick=%60&leftCurlyBrace=%7B&pipe=%7C&rightCurlyBrace=%7D&tilde=~
Safari iOS:         https://example.com?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%25&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=%5B&backslash=&rightSquareBracket=%5D&caret=%5E&underscore=_&backtick=%60&leftCurlyBrace=%7B&pipe=%7C&rightCurlyBrace=%7D&space=%20&tilde=~
Firefox Android:    https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Chrome Android:     https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Chrome 117, macOS   https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Safari 16, macOS    https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Firefox 118, macOS  https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Safari 16, iOS      https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%25&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=%5B&backslash=&rightSquareBracket=%5D&caret=%5E&underscore=_&backtick=%60&leftCurlyBrace=%7B&pipe=%7C&rightCurlyBrace=%7D&tilde=~
Chrome 118, iOS     https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%25&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=%5B&backslash=&rightSquareBracket=%5D&caret=%5E&underscore=_&backtick=%60&leftCurlyBrace=%7B&pipe=%7C&rightCurlyBrace=%7D&tilde=~
Safari WebView mac  https://example.com/?exclamationMark=!&doubleQuote="&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe='&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=<&equals==&greaterThan=>&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&tilde=~
Chrome, Windows     https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&space=%20&tilde=~
Edge, Windows       https://example.com/?exclamationMark=!&doubleQuote=%22&hash=%23&dollar=$&percent=%&ampersand=%26&apostrophe=%27&leftParenthesis=(&rightParenthesis=)&asterisk=*&plus=+&comma=,&hyphen=-&period=.&slash=/&colon=:&semicolon=;&lessThan=%3C&equals==&greaterThan=%3E&questionMark=?&at=@&leftSquareBracket=[&backslash=&rightSquareBracket=]&caret=^&underscore=_&backtick=`&leftCurlyBrace={&pipe=|&rightCurlyBrace=}&space=%20&tilde=~