npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vuerl

v1.0.1

Published

Type-safe URL query state management for Vue 3

Readme

vuerl

npm version CI License: MIT

Type-safe URL query state management for Vue 3 + Vue Router.

This package gives you typed composables that keep Vue state and the URL query string in sync. You get the same ergonomics as const [value, setValue] = useQueryState(...) in React/nuqs, including parser-based type safety, automatic batching, and browser-aware rate limiting.

Installation

pnpm add vuerl
# or
npm install vuerl
# or
yarn add vuerl

Peer dependencies:

pnpm add vue@^3.3 vue-router@^4.0

Quick Start

<script setup lang="ts">
import { useQueryState, parseAsString, parseAsInteger } from 'vuerl'

const [search, setSearch] = useQueryState('q', parseAsString.withDefault(''))
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1), {
  debounce: 200,
  history: 'replace'
})
</script>

<template>
  <input v-model="search" placeholder="Search" />
  <button @click="setPage(page.value + 1)">Next Page</button>
</template>
  • State updates immediately when you type.
  • URL updates are debounced to keep browsers happy.
  • Defaults never show up in the URL (unless you turn off clearOnDefault).
  • TypeScript knows search.value is string and page.value is number.

Multi-parameter State

Use useQueryStates when several params should update together.

import {
  useQueryStates,
  parseAsString,
  parseAsInteger,
  parseAsStringLiteral
} from 'vuerl'

const [filters, setFilters] = useQueryStates({
  search: parseAsString.withDefault(''),
  status: parseAsStringLiteral(['active', 'inactive']).withDefault('active'),
  limit: parseAsInteger.withDefault(20)
}, {
  debounce: 120,
  history: 'push'
})

setFilters({ search: 'vue' })             // single field
setFilters({ status: 'inactive', limit: 50 }) // batched update → one router push
setFilters({ status: null })               // drop from URL + reset to parser default
setFilters(null)                           // reset every field to defaults

Updates inside the same tick (setFilters({...}); setFilters({...})) merge before touching the router, so you only pay for one navigation.

Parser Cheatsheet

Parsers define how a query string turns into a typed value (and back). Every parser supports .withDefault(value) and .withOptions({ ...queryStateOptions }).

| Parser | Example | URL → Value | | --- | --- | --- | | parseAsString | "hello" | 'hello' | parseAsInteger | "42" | 42 | parseAsFloat | "3.14" | 3.14 | parseAsBoolean | "true" | true | parseAsStringLiteral(['asc','desc']) | "asc" | 'asc' | parseAsStringEnum(MyEnum) | "VALUE" | MyEnum.VALUE | parseAsArrayOf(parseAsInteger) | "1,2,3" | [1,2,3] | parseAsNativeArrayOf(parseAsString) | ?tag=a&tag=b | ['a','b'] | parseAsIsoDate | "2025-11-22" | Date | parseAsIsoDateTime | "2025-11-22T10:30:00Z" | Date | parseAsJson() | "%7B%5C"id%5C":1%7D" | { id: 1 } | parseAsHex | "ff00ff" | 'ff00ff' | withDefault(customParser, defaultValue) | – | Keeps custom parser logic but adds defaults |

Need something custom? Implement the Parser<T> interface or wrap an existing parser with .withDefault/.withOptions.

const parseAsSlug = withDefault({
  parse: (value) => (value && /[a-z0-9-]+/.test(value) ? value : null),
  serialize: (value) => value ?? null
}, 'home')

Options Reference

Both hooks accept the same QueryStateOptions either directly or via parser .withOptions.

| Option | Default | What it does | | --- | --- | --- | | history | 'replace' | 'push' to record every change in browser history. | | debounce | browser-safe (50ms / 120ms Safari) | Delay before writing to the URL. | | throttle | null | Alternate rate limiter if you prefer throttling. | | clearOnDefault | true | Drop params from the URL when they match parser default. | | shallow | true | Skip full router navigation (like router.replace({ query })). Set false when SSR needs to know about query changes. | | scroll | false | Force scroll to top on navigation. |

Parser-level options merge with hook options, so you can keep most defaults global and override only the odd field:

const parser = parseAsInteger
  .withDefault(20)
  .withOptions({ clearOnDefault: false })

const [, setLimit] = useQueryState('limit', parser)

Watching External Changes

Both hooks watch route.query so back/forward navigation, shared URLs, or manual router.push calls stay in sync automatically. Pending debounced updates are cancelled when the user navigates elsewhere.

Working With Arrays

parseAsArrayOf (comma separated) and parseAsNativeArrayOf (repeated params) always return concrete arrays:

const [tags] = useQueryState('tags', parseAsArrayOf(parseAsString))
tags.value // always string[]

const [, setIds] = useQueryState('id', parseAsNativeArrayOf(parseAsInteger))
setIds([1, 2, 3]) // → ?id=1&id=2&id=3

Custom Equality

If your parser returns objects/arrays that shouldn't trigger updates when reference changes, provide eq(a, b):

const parseAsJsonFilters = withDefault({
  parse: (value) => value ? JSON.parse(value) : null,
  serialize: (value) => value ? JSON.stringify(value) : null,
  eq: (a, b) => JSON.stringify(a) === JSON.stringify(b)
}, {})

Testing Helpers

The test suite uses Vue Test Utils + Vue Router in memory. If you need to write your own tests, copy the helper from tests/helpers.ts to mount a composable inside a dummy component.

FAQ

Does this work with SSR?

Yes. Hooks guard against window access and fall back to safe defaults in SSR environments. Set { shallow: false } if your framework needs full navigations for query changes.

What about browser rate limits?

We auto-detect Safari vs other browsers and ensure the debounce/throttle never drops below 120ms/50ms respectively. You can still pass your own values—they're clamped to the safe floor.

Can I mix useQueryState and useQueryStates?

Absolutely. They both watch the same router instance and will stay in sync. When both write the same key the last writer wins, so prefer one hook per param to avoid confusion.

Contributing

PRs welcome! If you add new parsers, remember to extend src/parsers.ts, export from src/index.ts, and cover them in tests/parsers.test.ts.

License

MIT