{"id":28200188,"url":"https://github.com/rtrampox/nuqs-svelte","last_synced_at":"2026-03-02T17:04:37.653Z","repository":{"id":292982062,"uuid":"982552277","full_name":"rtrampox/nuqs-svelte","owner":"rtrampox","description":"nuqs port for Svelte ✨","archived":false,"fork":false,"pushed_at":"2025-09-10T02:41:19.000Z","size":444,"stargazers_count":67,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-12T04:40:02.902Z","etag":null,"topics":["nuqs","nuqs-alternative","query-string","searchparams","svelte","sveltekit","url","url-states"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rtrampox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-13T04:09:36.000Z","updated_at":"2025-09-10T02:40:26.000Z","dependencies_parsed_at":"2025-06-05T19:21:07.083Z","dependency_job_id":"a1dbc97c-629e-467b-8f47-39bc68c18e20","html_url":"https://github.com/rtrampox/nuqs-svelte","commit_stats":null,"previous_names":["rtrampox/nuqs-svelte"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/rtrampox/nuqs-svelte","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtrampox%2Fnuqs-svelte","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtrampox%2Fnuqs-svelte/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtrampox%2Fnuqs-svelte/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtrampox%2Fnuqs-svelte/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rtrampox","download_url":"https://codeload.github.com/rtrampox/nuqs-svelte/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtrampox%2Fnuqs-svelte/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274776867,"owners_count":25347641,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-12T02:00:09.324Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nuqs","nuqs-alternative","query-string","searchparams","svelte","sveltekit","url","url-states"],"created_at":"2025-05-16T20:10:52.796Z","updated_at":"2025-10-25T09:13:58.501Z","avatar_url":"https://github.com/rtrampox.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003c!-- commented sections of the readme means that some of those examples are not yet implemented by nuqs-svelte, but exist on the original nuqs readme --\u003e\n\n# nuqs-svelte\n\n### `nuqs-svelte` is an unnoficial Svelte port of [nuqs](https://github.com/47ng/nuqs) library.\n\n[![NPM](https://img.shields.io/npm/v/nuqs-svelte?color=red)](https://www.npmjs.com/package/nuqs-svelte)\n[![MIT License](https://img.shields.io/github/license/rtrampox/nuqs-svelte.svg?color=blue)](https://github.com/rtrampox/nuqs-svelte/blob/main/LICENSE)\n[![CI/CD](https://github.com/rtrampox/nuqs-svelte/actions/workflows/ci-cd.yaml/badge.svg?branch=next)](https://github.com/rtrampox/nuqs-svelte/actions/workflows/ci-cd.yaml)\n![dependabot](https://img.shields.io/badge/dependabot-active-brightgreen?style=flat-square\u0026logo=dependabot)\n\nType-safe search params state manager for Svelte. Like `$state()`, but stored in the URL query string.\n\n## Features\n\n- 🔀 **new:** Supports SvelteKit, and custom routers via [adapters](#adapters)\n- 🧘‍♀️ Simple: the URL is the source of truth\n- 🕰 Replace history or [append](#history) to use the Back button to navigate state updates\n- ⚡️ Built-in [parsers](#parsing) for common state types (integer, float, boolean, Date, and more). Create your own parsers for custom types \u0026 pretty URLs\n- ♊️ Related querystrings with [`useQueryStates`](#usequerystates)\n- 📡 [Shallow mode](#shallow) by default for URL query updates\n\n\u003c!-- ## Documentation\n\nRead the complete documentation at [nuqs.47ng.com](https://nuqs.47ng.com). --\u003e\n\n## Installation\n\n```shell\npnpm add nuqs-svelte\n```\n\n```shell\nyarn add nuqs-svelte\n```\n\n```shell\nnpm install nuqs-svelte\n```\n\n## Adapters\n\nYou will need to wrap your Svelte component tree with an adapter. _(expand the appropriate section below)_\n\n\u003cdetails\u003e\n  \u003csummary\u003e\n    \u003cimg src=\"https://avatars.githubusercontent.com/u/23617963?s=20\u0026v=4\" alt=\"SvelteKit\" style=\"vertical-align: middle;\" /\u003e\n  SvelteKit\n  \u003c/summary\u003e\n\n\u003e Supported Svelte versions: `\u003e=5.0.0`.\n\n```svelte filename=\"src/routes/+layout.svelte\"\n// src/routes/+layout.svelte\n\u003cscript lang=\"ts\"\u003e\n  import { NuqsAdapter } from \"nuqs-svelte/adapters/svelte-kit\";\n  import \"../app.css\";\n\n  let { children } = $props();\n\u003c/script\u003e\n\n\u003cNuqsAdapter\u003e\n  {@render children()}\n\u003c/NuqsAdapter\u003e\n```\n\n\u003c/details\u003e\n\n## Usage\n\n```svelte filename=\"src/routes/+page.svelte\"\n// src/routes/+page.svelte\n\u003cscript lang=\"ts\"\u003e\n  import { useQueryState } from \"nuqs-svelte\";\n  import \"../app.css\";\n\n  const name = useQueryState(\"name\");\n\u003c/script\u003e\n\n\u003ch1\u003eHello, {name.current || \"anonymous visitor\"}!\u003c/h1\u003e\n\u003cinput bind:value={name.current} /\u003e\n\u003cbutton onclick={() =\u003e name.current = null}\u003eClear\u003c/button\u003e\n```\n\n`useQueryState` takes one required argument: the key to use in the query string.\n\nIt returns an object with the value present in the query\nstring as a string (or `null` if none was found).\n\nExample outputs for our hello world example:\n\n| URL          | name value | Notes                                                             |\n| ------------ | ---------- | ----------------------------------------------------------------- |\n| `/`          | `null`     | No `name` key in URL                                              |\n| `/?name=`    | `''`       | Empty string                                                      |\n| `/?name=foo` | `'foo'`    |\n| `/?name=2`   | `'2'`      | Always returns a string by default, see [Parsing](#parsing) below |\n\n## Parsing\n\nIf your state type is not a string, you must pass a parsing function in the\nsecond argument object.\n\nWe provide parsers for common and more advanced object types:\n\n```ts\nimport {\n  parseAsString,\n  parseAsInteger,\n  parseAsFloat,\n  parseAsBoolean,\n  parseAsTimestamp,\n  parseAsIsoDateTime,\n  parseAsArrayOf,\n  parseAsJson,\n  parseAsStringEnum,\n  parseAsStringLiteral,\n  parseAsNumberLiteral,\n} from \"nuqs-svelte\";\n\nuseQueryState(\"tag\"); // defaults to string\nuseQueryState(\"count\", parseAsInteger);\nuseQueryState(\"brightness\", parseAsFloat);\nuseQueryState(\"darkMode\", parseAsBoolean);\nuseQueryState(\"after\", parseAsTimestamp); // state is a Date\nuseQueryState(\"date\", parseAsIsoDateTime); // state is a Date\nuseQueryState(\"array\", parseAsArrayOf(parseAsInteger)); // state is number[]\nuseQueryState(\"json\", parseAsJson\u003cPoint\u003e()); // state is a Point\n\n// Enums (string-based only)\nenum Direction {\n  up = \"UP\",\n  down = \"DOWN\",\n  left = \"LEFT\",\n  right = \"RIGHT\",\n}\n\nconst direction = useQueryState(\n  \"direction\",\n  parseAsStringEnum\u003cDirection\u003e(Object.values(Direction)) // pass a list of allowed values\n    .withDefault(Direction.up),\n);\n\n// Literals (string-based only)\nconst colors = [\"red\", \"green\", \"blue\"] as const;\n\nconst color = useQueryState(\n  \"color\",\n  parseAsStringLiteral(colors) // pass a readonly list of allowed values\n    .withDefault(\"red\"),\n);\n\n// Literals (number-based only)\nconst diceSides = [1, 2, 3, 4, 5, 6] as const;\n\nconst side = useQueryState(\n  \"side\",\n  parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values\n    .withDefault(4),\n);\n```\n\nYou may pass a custom set of `parse` and `serialize` functions:\n\n```svelte\n\u003cscript lang=\"ts\"\u003e\n  import { useQueryState } from \"nuqs-svelte\";\n\n  const hex = useQueryState(\"hex\", {\n    // TypeScript will automatically infer it's a number\n    // based on what `parse` returns.\n    parse: (query: string) =\u003e parseInt(query, 16),\n    serialize: (value) =\u003e value.toString(16),\n  });\n\u003c/script\u003e\n```\n\n\u003e Note: parsers **don't validate** your data. If you expect positive integers\n\u003e or JSON-encoded objects of a particular shape, you'll need to feed the result\n\u003e of the parser to a schema validation library, like [Zod](https://zod.dev).\n\n## Default value\n\nWhen the query string is not present in the URL, the default behaviour is to\nreturn `null` as state.\n\nIt can make state updating and UI rendering tedious. Take this example of a simple counter stored in the URL:\n\n```svelte\n\u003cscript lang=\"ts\"\u003e\n  import { useQueryState, parseAsInteger } from \"nuqs-svelte\";\n\n  const count = useQueryState(\"count\", parseAsInteger);\n\u003c/script\u003e\n\n\u003cpre\u003ecount: {count.current}\u003c/pre\u003e\n\u003cbutton onclick={() =\u003e count.current = 0}\u003eReset\u003c/button\u003e\n\u003c!-- handling null values in setCount is annoying: --\u003e\n\u003cbutton onclick={() =\u003e count.set((c) =\u003e c ?? 0 + 1)}\u003e+\u003c/button\u003e\n\u003cbutton onclick={() =\u003e count.set((c) =\u003e c ?? 0 - 1)}\u003e-\u003c/button\u003e\n\u003cbutton onclick={() =\u003e count.set(null)}\u003eClear\u003c/button\u003e\n\n```\n\nYou can specify a default value to be returned in this case:\n\n```ts\n\u003cscript lang=\"ts\"\u003e\n  import { useQueryState, parseAsInteger } from \"nuqs-svelte\";\n\n  const count = useQueryState(\"count\", parseAsInteger.withDefault(0));\n  const increment = () =\u003e count.set((c) =\u003e c + 1); // c will never be null\n  const decrement = () =\u003e count.set((c) =\u003e c - 1); // c will never be null\n  const clearCount = () =\u003e count.set(null); // Remove query from the URL\n\u003c/script\u003e\n\n```\n\nNote: the default value is internal to Svelte, it will **not** be written to the\nURL.\n\nSetting the state to `null` will remove the key in the query string and set the\nstate to the default value.\n\n## Options\n\n### History\n\nBy default, state updates are done by replacing the current history entry with\nthe updated query when state changes.\n\nYou can see this as a sort of `git squash`, where all state-changing\noperations are merged into a single history value.\n\nYou can also opt-in to push a new history item for each state change,\nper key, which will let you use the Back button to navigate state\nupdates:\n\n```ts\n// Default: replace current history with new state\nuseQueryState(\"foo\", { history: \"replace\" });\n\n// Append state changes to history:\nuseQueryState(\"foo\", { history: \"push\" });\n```\n\nAny other value for the `history` option will fallback to the default.\n\nYou can also override the history mode when calling the state updater function:\n\n```ts\nconst query = useQueryState(\"q\", { history: \"push\" });\n\n// This overrides the hook declaration setting:\nquery.set(null, { history: \"replace\" });\n```\n\n### Shallow\n\n\u003e Note: this feature only applies to the SvelteKit adapter\n\nBy default, query state updates are done in a _client-first_ manner: there are\nno network calls to the server.\n\nTo opt-in to query updates notifying the server (re-fetch data from loaders),\nyou can set `shallow` to `false`:\n\n```ts\nconst state = useQueryState(\"foo\", { shallow: false });\n\n// You can also pass the option on calls to setState:\nstate.set(\"bar\", { shallow: false });\n```\n\n### Throttling URL updates\n\nBecause of browsers rate-limiting the History API, internal updates to the\nURL are queued and throttled to a default of 50ms, which seems to satisfy\nmost browsers even when sending high-frequency query updates, like binding\nto a text input or a slider.\n\nSafari's rate limits are much higher and would require a throttle of around 340ms.\nIf you end up needing a longer time between updates, you can specify it in the\noptions:\n\n```ts\nuseQueryState(\"foo\", {\n  // Send updates to the server maximum once every second\n  shallow: false,\n  throttleMs: 1000,\n});\n\n// You can also pass the option on calls to setState:\nstate.set(\"bar\", { throttleMs: 1000 });\n```\n\n\u003e Note: the state returned by the hook is always updated instantly, to keep UI responsive.\n\u003e Only changes to the URL, and server requests when using `shallow: false`, are throttled.\n\nIf multiple hooks set different throttle values on the same event loop tick,\nthe highest value will be used. Also, values lower than 50ms will be ignored,\nto avoid rate-limiting issues. [Read more](https://francoisbest.com/posts/2023/storing-react-state-in-the-url-with-nextjs#batching--throttling).\n\n## Configuring parsers, default value \u0026 options\n\nYou can use a builder pattern to facilitate specifying all of those things:\n\n```ts\nuseQueryState(\n  \"counter\",\n  parseAsInteger.withDefault(0).withOptions({\n    history: \"push\",\n    shallow: false,\n  }),\n);\n```\n\nYou can get this pattern for your custom parsers too, and compose them\nwith others:\n\n```ts\nimport { createParser, parseAsHex } from \"nuqs-svelte\";\n\n// Wrapping your parser/serializer in `createParser`\n// gives it access to the builder pattern \u0026 server-side\n// parsing capabilities:\nconst hexColorSchema = createParser({\n  parse(query) {\n    if (query.length !== 6) {\n      return null; // always return null for invalid inputs\n    }\n    return {\n      // When composing other parsers, they may return null too.\n      r: parseAsHex.parse(query.slice(0, 2)) ?? 0x00,\n      g: parseAsHex.parse(query.slice(2, 4)) ?? 0x00,\n      b: parseAsHex.parse(query.slice(4)) ?? 0x00,\n    };\n  },\n  serialize({ r, g, b }) {\n    return parseAsHex.serialize(r) + parseAsHex.serialize(g) + parseAsHex.serialize(b);\n  },\n})\n  // Eg: set common options directly\n  .withOptions({ history: \"push\" });\n\n// Or on usage:\nuseQueryState(\n  \"tribute\",\n  hexColorSchema.withDefault({\n    r: 0x66,\n    g: 0x33,\n    b: 0x99,\n  }),\n);\n```\n\n\u003c!-- Note: see this example running in the [hex-colors demo](\u003c./packages/docs/src/app/playground/(demos)/hex-colors/page.tsx\u003e). --\u003e\n\n## Multiple Queries (batching)\n\nYou can call as many state update function as needed in a single event loop\ntick, and they will be applied to the URL asynchronously:\n\n```ts\n\u003cscript lang=\"ts\"\u003e\n  const lat = useQueryState(\"lat\", parseAsFloat);\n  const lng = useQueryState(\"lng\", parseAsFloat);\n\n  const randomCoordinates = () =\u003e {\n    lat.set(Math.random() * 180 - 90);\n    lng.set(Math.random() * 360 - 180);\n  }\n\u003c/script\u003e\n```\n\nIf you wish to know when the URL has been updated, and what it contains, you can\nawait the Promise returned by the state updater function, which gives you the\nupdated URLSearchParameters object:\n\n```ts\nconst randomCoordinates = () =\u003e {\n  setLat(42);\n  return setLng(12);\n};\n\nrandomCoordinates().then((search: URLSearchParams) =\u003e {\n  search.get(\"lat\"); // 42\n  search.get(\"lng\"); // 12, has been queued and batch-updated\n});\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cem\u003eImplementation details (Promise caching)\u003c/em\u003e\u003c/summary\u003e\n\nThe returned Promise is cached until the next flush to the URL occurs,\nso all calls to a `state.set` (of any hook) in the same event loop tick will\nreturn the same Promise reference.\n\nDue to throttling of calls to the Web History API, the Promise may be cached\nfor several ticks. Batched updates will be merged and flushed once to the URL.\nThis means not every `state.set` will reflect to the URL, if another one comes\noverriding it before flush occurs.\n\nThe returned Svelte state will reflect all set values instantly,\nto keep UI responsive.\n\n---\n\n\u003c/details\u003e\n\n## `useQueryStates`\n\nFor query keys that should always move together, you can use `useQueryStates`\nwith an object containing each key's type:\n\n```ts\nimport { useQueryStates, parseAsFloat } from \"nuqs-svelte\";\n\nconst coordinates = useQueryStates(\n  {\n    lat: parseAsFloat.withDefault(45.18),\n    lng: parseAsFloat.withDefault(5.72),\n  },\n  {\n    history: \"push\",\n  },\n);\n\nconst { lat, lng } = coordinates;\n\n// Set all (or a subset of) the keys in one go:\nconst search = await coordinates.set({\n  lat: Math.random() * 180 - 90,\n  lng: Math.random() * 360 - 180,\n});\n```\n\n\u003c!--\n## Loaders\n\nTo parse search params as a one-off operation, you can use a **loader function**:\n\n```tsx\nimport { createLoader } from \"nuqs\"; // or 'nuqs/server'\n\nconst searchParams = {\n  q: parseAsString,\n  page: parseAsInteger.withDefault(1),\n};\n\nconst loadSearchParams = createLoader(searchParams);\n\nconst { q, page } = loadSearchParams(\"?q=hello\u0026page=2\");\n```\n\nIt accepts various types of inputs (strings, URL, URLSearchParams, Request, Promises, etc.). [Read more](https://nuqs.47ng.com/docs/server-side#loaders)\n\n## Accessing searchParams in Server Components\n\nIf you wish to access the searchParams in a deeply nested Server Component\n(ie: not in the Page component), you can use `createSearchParamsCache`\nto do so in a type-safe manner.\n\n\u003e Note: parsers **don't validate** your data. If you expect positive integers\n\u003e or JSON-encoded objects of a particular shape, you'll need to feed the result\n\u003e of the parser to a schema validation library, like [Zod](https://zod.dev).\n\n```tsx\n// searchParams.ts\nimport { createSearchParamsCache, parseAsInteger, parseAsString } from \"nuqs/server\";\n// Note: import from 'nuqs/server' to avoid the \"use client\" directive\n\nexport const searchParamsCache = createSearchParamsCache({\n  // List your search param keys and associated parsers here:\n  q: parseAsString.withDefault(\"\"),\n  maxResults: parseAsInteger.withDefault(10),\n});\n\n// page.tsx\nimport { searchParamsCache } from \"./searchParams\";\n\nexport default function Page({\n  searchParams,\n}: {\n  searchParams: Record\u003cstring, string | string[] | undefined\u003e;\n}) {\n  // ⚠️ Don't forget to call `parse` here.\n  // You can access type-safe values from the returned object:\n  const { q: query } = searchParamsCache.parse(searchParams);\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003eSearch Results for {query}\u003c/h1\u003e\n      \u003cResults /\u003e\n    \u003c/div\u003e\n  );\n}\n\nfunction Results() {\n  // Access type-safe search params in children server components:\n  const maxResults = searchParamsCache.get(\"maxResults\");\n  return \u003cspan\u003eShowing up to {maxResults} results\u003c/span\u003e;\n}\n```\n\nThe cache will only be valid for the current page render\n(see React's [`cache`](https://react.dev/reference/react/cache) function).\n\nNote: the cache only works for **server components**, but you may share your\nparser declaration with `useQueryStates` for type-safety in client components:\n\n```tsx\n// searchParams.ts\nimport { parseAsFloat, createSearchParamsCache } from \"nuqs/server\";\n\nexport const coordinatesParsers = {\n  lat: parseAsFloat.withDefault(45.18),\n  lng: parseAsFloat.withDefault(5.72),\n};\nexport const coordinatesCache = createSearchParamsCache(coordinatesParsers);\n\n// page.tsx\nimport { coordinatesCache } from \"./searchParams\";\nimport { Server } from \"./server\";\nimport { Client } from \"./client\";\n\nexport default async function Page({ searchParams }) {\n  await coordinatesCache.parse(searchParams);\n  return (\n    \u003c\u003e\n      \u003cServer /\u003e\n      \u003cSuspense\u003e\n        \u003cClient /\u003e\n      \u003c/Suspense\u003e\n    \u003c/\u003e\n  );\n}\n\n// server.tsx\nimport { coordinatesCache } from \"./searchParams\";\n\nexport function Server() {\n  const { lat, lng } = coordinatesCache.all();\n  // or access keys individually:\n  const lat = coordinatesCache.get(\"lat\");\n  const lng = coordinatesCache.get(\"lng\");\n  return (\n    \u003cspan\u003e\n      Latitude: {lat} - Longitude: {lng}\n    \u003c/span\u003e\n  );\n}\n\n// client.tsx\n// prettier-ignore\n'use client'\n\nimport { useQueryStates } from \"nuqs\";\nimport { coordinatesParsers } from \"./searchParams\";\n\nexport function Client() {\n  const [{ lat, lng }, setCoordinates] = useQueryStates(coordinatesParsers);\n  // ...\n}\n```\n\n## Serializer helper\n\nTo populate `\u003cLink\u003e` components with state values, you can use the `createSerializer`\nhelper.\n\nPass it an object describing your search params, and it will give you a function\nto call with values, that generates a query string serialized as the hooks would do.\n\nExample:\n\n```ts\nimport {\n  createSerializer,\n  parseAsInteger,\n  parseAsIsoDateTime,\n  parseAsString,\n  parseAsStringLiteral,\n} from \"nuqs/server\";\n\nconst searchParams = {\n  search: parseAsString,\n  limit: parseAsInteger,\n  from: parseAsIsoDateTime,\n  to: parseAsIsoDateTime,\n  sortBy: parseAsStringLiteral([\"asc\", \"desc\"] as const),\n};\n\n// Create a serializer function by passing the description of the search params to accept\nconst serialize = createSerializer(searchParams);\n\n// Then later, pass it some values (a subset) and render them to a query string\nserialize({\n  search: \"foo bar\",\n  limit: 10,\n  from: new Date(\"2024-01-01\"),\n  // here, we omit `to`, which won't be added\n  sortBy: null, // null values are also not rendered\n});\n// ?search=foo+bar\u0026limit=10\u0026from=2024-01-01T00:00:00.000Z\n```\n\n### Base parameter\n\nThe returned `serialize` function can take a base parameter over which to\nappend/amend the search params:\n\n```ts\nserialize(\"/path?baz=qux\", { foo: \"bar\" }); // /path?baz=qux\u0026foo=bar\n\nconst search = new URLSearchParams(\"?baz=qux\");\nserialize(search, { foo: \"bar\" }); // ?baz=qux\u0026foo=bar\n\nconst url = new URL(\"https://example.com/path?baz=qux\");\nserialize(url, { foo: \"bar\" }); // https://example.com/path?baz=qux\u0026foo=bar\n\n// Passing null removes existing values\nserialize(\"?remove=me\", { foo: \"bar\", remove: null }); // ?foo=bar\n```\n\n## Parser type inference\n\nTo access the underlying type returned by a parser, you can use the\n`inferParserType` type helper:\n\n```ts\nimport { parseAsInteger, type inferParserType } from \"nuqs\"; // or 'nuqs/server'\n\nconst intNullable = parseAsInteger;\nconst intNonNull = parseAsInteger.withDefault(0);\n\ninferParserType\u003ctypeof intNullable\u003e; // number | null\ninferParserType\u003ctypeof intNonNull\u003e; // number\n```\n\nFor an object describing parsers (that you'd pass to `createSearchParamsCache`\nor to `useQueryStates`, `inferParserType` will\nreturn the type of the object with the parsers replaced by their inferred types:\n\n```ts\nimport { parseAsBoolean, parseAsInteger, type inferParserType } from \"nuqs\"; // or 'nuqs/server'\n\nconst parsers = {\n  a: parseAsInteger,\n  b: parseAsBoolean.withDefault(false),\n};\n\ninferParserType\u003ctypeof parsers\u003e;\n// { a: number | null, b: boolean }\n```\n\n## Testing\n\nSince nuqs v2, you can use a testing adapter to unit-test components using\n`useQueryState` and `useQueryStates` in isolation, without needing to mock\nyour framework or router.\n\nHere's an example using Testing Library and Vitest:\n\n```tsx\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { NuqsTestingAdapter, type UrlUpdateEvent } from \"nuqs/adapters/testing\";\nimport { describe, expect, it, vi } from \"vitest\";\nimport { CounterButton } from \"./counter-button\";\n\nit(\"should increment the count when clicked\", async () =\u003e {\n  const user = userEvent.setup();\n  const onUrlUpdate = vi.fn\u003c[UrlUpdateEvent]\u003e();\n  render(\u003cCounterButton /\u003e, {\n    // Setup the test by passing initial search params / querystring,\n    // and give it a function to call on URL updates\n    wrapper: ({ children }) =\u003e (\n      \u003cNuqsTestingAdapter searchParams=\"?count=42\" onUrlUpdate={onUrlUpdate}\u003e\n        {children}\n      \u003c/NuqsTestingAdapter\u003e\n    ),\n  });\n  // Initial state assertions: there's a clickable button displaying the count\n  const button = screen.getByRole(\"button\");\n  expect(button).toHaveTextContent(\"count is 42\");\n  // Act\n  await user.click(button);\n  // Assert changes in the state and in the (mocked) URL\n  expect(button).toHaveTextContent(\"count is 43\");\n  expect(onUrlUpdate).toHaveBeenCalledOnce();\n  expect(onUrlUpdate.mock.calls[0][0].queryString).toBe(\"?count=43\");\n  expect(onUrlUpdate.mock.calls[0][0].searchParams.get(\"count\")).toBe(\"43\");\n  expect(onUrlUpdate.mock.calls[0][0].options.history).toBe(\"push\");\n});\n```\n\nSee [#259](https://github.com/47ng/nuqs/issues/259) for more testing-related discussions.\n\n## Debugging\n\nYou can enable debug logs in the browser by setting the `debug` item in localStorage\nto `nuqs`, and reload the page.\n\n```js\n// In your devtools:\nlocalStorage.setItem(\"debug\", \"nuqs\");\n```\n\n\u003e Note: unlike the `debug` package, this will not work with wildcards, but\n\u003e you can combine it: `localStorage.setItem('debug', '*,nuqs')`\n\nLog lines will be prefixed with `[nuqs]` for `useQueryState` and `[nuq+]` for\n`useQueryStates`, along with other internal debug logs.\n\nUser timings markers are also recorded, for advanced performance analysis using\nyour browser's devtools.\n\nProviding debug logs when opening an [issue](https://github.com/47ng/nuqs/issues)\nis always appreciated. 🙏\n\n### SEO\n\nIf your page uses query strings for local-only state, you should add a\ncanonical URL to your page, to tell SEO crawlers to ignore the query string\nand index the page without it.\n\nIn the app router, this is done via the metadata object:\n\n```ts\nimport type { Metadata } from \"next\";\n\nexport const metadata: Metadata = {\n  alternates: {\n    canonical: \"/url/path/without/querystring\",\n  },\n};\n```\n\nIf however the query string is defining what content the page is displaying\n(eg: YouTube's watch URLs, like `https://www.youtube.com/watch?v=dQw4w9WgXcQ`),\nyour canonical URL should contain relevant query strings, and you can still\nuse `useQueryState` to read it:\n\n```ts\n// page.tsx\nimport type { Metadata, ResolvingMetadata } from \"next\";\nimport { useQueryState } from \"nuqs\";\nimport { parseAsString } from \"nuqs/server\";\n\ntype Props = {\n  searchParams: { [key: string]: string | string[] | undefined };\n};\n\nexport async function generateMetadata({ searchParams }: Props): Promise\u003cMetadata\u003e {\n  const videoId = parseAsString.parseServerSide(searchParams.v);\n  return {\n    alternates: {\n      canonical: `/watch?v=${videoId}`,\n    },\n  };\n}\n```\n\n### Lossy serialization\n\nIf your serializer loses precision or doesn't accurately represent\nthe underlying state value, you will lose this precision when\nreloading the page or restoring state from the URL (eg: on navigation).\n\nExample:\n\n```ts\nconst geoCoordParser = {\n  parse: parseFloat,\n  serialize: (v) =\u003e v.toFixed(4), // Loses precision\n};\n\nconst [lat, setLat] = useQueryState(\"lat\", geoCoordParser);\n```\n\nHere, setting a latitude of 1.23456789 will render a URL query string\nof `lat=1.2345`, while the internal `lat` state will be correctly\nset to 1.23456789.\n\nUpon reloading the page, the state will be incorrectly set to 1.2345. --\u003e\n\n## License\n\n[MIT](https://github.com/rtrampox/nuqs-svelte/blob/main/LICENSE)\n\n![Project analytics and stats](https://repobeats.axiom.co/api/embed/94319c1c6d9da034e470c6340e0cc1b76f421e3b.svg \"Repobeats analytics image\")\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frtrampox%2Fnuqs-svelte","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frtrampox%2Fnuqs-svelte","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frtrampox%2Fnuqs-svelte/lists"}