Skip to content

Commit

Permalink
fix: Log an error in development if URI size of 2000 characters is ex…
Browse files Browse the repository at this point in the history
…ceeded. (#747)

* feat: add limits tab to docs

* feat: add error logging if maximum uri size is exceeded

* fix: linting

* Update errors/NUQS-414.md

Co-authored-by: François Best <[email protected]>

* Update packages/nuqs/src/errors.ts

Co-authored-by: François Best <[email protected]>

* Update packages/nuqs/src/update-queue.ts

Co-authored-by: François Best <[email protected]>

* fix: update solution text

* fix: add link to max url lengths

* fix: move warning function call and refactor some code

* fix: linting

* fix: rename constant

* doc: Wording

* ref: Move warning to url-encoding, add tests

---------

Co-authored-by: niklasbec <[email protected]>
Co-authored-by: François Best <[email protected]>
  • Loading branch information
3 people authored Nov 12, 2024
1 parent 6bc229a commit 3017660
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 3 deletions.
28 changes: 28 additions & 0 deletions errors/NUQS-414.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Max Safe URL Length Exceeded

This error occurs if your URL length exceeds 2,000 characters.

There are varying browser limitations for max URL lengths, [read more](https://nuqs.47ng.com/docs/limits#max-url-lengths).

URLs that are too long might break in some browsers, or not be able to be
processed by some servers.

## Possible Solutions

Keeping your URLs short is a good practice: not all state has to live in the URL.

- Server state/data is best managed in a local cache like TanStack Query or SWR.
- Transient state (that doesn't need persisting or sharing) can be managed in local state.
- Device-persistent state can be managed in local storage.

When deciding to put state in the URL, ask yourself:

- Do I need it to persist across page refresh?
- Do I need to share it with others?
- Do I need to link to it from other places?
- Do I need to be able to bookmark it?
- Do I need to be able to use the Back/Forward buttons to navigate to it?
- Is it always going to be a small amount of data?

If the answer to any of these questions is no, then you might want to consider
an alternative state storage solution.
2 changes: 1 addition & 1 deletion packages/docs/content/docs/limits.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ Most modern browsers enforce a max URL length, which can vary:

Additionally, transport mechanisms like social media, messaging apps, and emails may impose significantly lower limits on URL length. Long URLs may be truncated, wrapped, or rendered unusable when shared on these platforms.

Keep in mind that not all application state should be stored in URLs. Exceeding the 2,000-character range may indicate the need to reconsider your state management approach.
Keep in mind that not all application state should be stored in URLs. Exceeding the 2,000-character range may indicate the need to reconsider your state management approach.
1 change: 1 addition & 0 deletions packages/nuqs/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const errors = {
404: 'nuqs requires an adapter to work with your framework.',
409: 'Multiple versions of the library are loaded. This may lead to unexpected behavior. Currently using `%s`, but `%s` was about to load on top.',
414: 'Max safe URL length exceeded. Some browsers may not be able to accept this URL. Consider limiting the amount of state stored in the URL.',
429: 'URL update rate-limited by the browser. Consider increasing `throttleMs` for key(s) `%s`. %O',
500: "Empty search params cache. Search params can't be accessed in Layouts.",
501: 'Search params cache already populated. Have you called `parse` twice?'
Expand Down
11 changes: 10 additions & 1 deletion packages/nuqs/src/url-encoding.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, test } from 'vitest'
import { describe, expect, test, vi } from 'vitest'
import { encodeQueryValue, renderQueryString } from './url-encoding'

describe('url-encoding/encodeQueryValue', () => {
Expand Down Expand Up @@ -124,6 +124,15 @@ describe('url-encoding/renderQueryString', () => {
'?a %26b%3Fc%3Dd%23e%f%2Bg"h\'i`j<k>l(m)n*o,p.q:r;s/t=value'
)
})
test('emits a warning if the URL is too long', () => {
const search = new URLSearchParams()
search.set('a', 'a'.repeat(2000))
const warn = console.warn
console.warn = vi.fn()
renderQueryString(search)
expect(console.warn).toHaveBeenCalledTimes(1)
console.warn = warn
})
})

test.skip('encodeURI vs encodeURIComponent vs custom encoding', () => {
Expand Down
23 changes: 22 additions & 1 deletion packages/nuqs/src/url-encoding.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { error } from './errors'

export function renderQueryString(search: URLSearchParams) {
if (search.size === 0) {
return ''
Expand All @@ -14,7 +16,9 @@ export function renderQueryString(search: URLSearchParams) {
.replace(/\?/g, '%3F')
query.push(`${safeKey}=${encodeQueryValue(value)}`)
}
return '?' + query.join('&')
const queryString = '?' + query.join('&')
warnIfURLIsTooLong(queryString)
return queryString
}

export function encodeQueryValue(input: string) {
Expand All @@ -40,3 +44,20 @@ export function encodeQueryValue(input: string) {
.replace(/>/g, '%3E')
)
}

// Note: change error documentation (NUQS-414) when changing this value.
export const URL_MAX_LENGTH = 2000

export function warnIfURLIsTooLong(queryString: string) {
if (process.env.NODE_ENV === 'production') {
return
}
if (typeof location === 'undefined') {
return
}
const url = new URL(location.href)
url.search = queryString
if (url.href.length > URL_MAX_LENGTH) {
console.warn(error(414))
}
}

0 comments on commit 3017660

Please sign in to comment.