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

@ifds/route-state

v0.1.0

Published

Type-safe URL query state management for Nuxt 3 & 4 — useState for the URL.

Readme

@ifds/route-state

Type-safe, SSR-safe state management for Nuxt that syncs state with the URL's query parameters — useState for the URL.

const page = useRouteState('page', { type: 'integer', default: 1 })
page.value = 2 // → router.replace({ query: { ...route.query, page: '2' } })

Shareable, bookmarkable page state (pagination, filters, search, tabs) with full type inference, built-in parsers, and Nuxt 3 + Nuxt 4 support.

Install

npm i @ifds/route-state
# optional, for schema-based states:
npm i zod

Add to nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@ifds/route-state'],
})

useRouteState, useRouteStates, and the parser factories (arrayParser, zodParser, jsonParser, …) are auto-imported — no manual imports needed.

Usage

Strings (default)

const name = useRouteState('name') // Ref<string>, default ''
const q    = useRouteState('q', { default: 'all', debounce: 200 })

Built-in parsers

const page    = useRouteState('page', { type: 'integer', default: 1 })   // ?page=2
const price   = useRouteState('price', { type: 'float', default: 2.5 })  // ?price=3.14
const flag    = useRouteState('flag', { type: 'boolean', default: false }) // ?flag=true / ?flag=false
const from    = useRouteState('from', { type: 'date', default: new Date() }) // ISO-8601 UTC
const raw     = useRouteState('raw', { type: 'json' })
const section = useRouteState('section', { type: 'slug', default: 'getting-started' }) // ?section=getting-started

Enums — a fixed set of values

const status = useRouteState('status', {
  parser: enumParser(['draft', 'published', 'archived'] as const), // Ref<'draft' | 'published' | 'archived'>
  default: 'draft',
})
// → ?status=published

String and number enums both work (enumParser([1, 2, 3] as const)); anything outside the set falls back to the default (+ dev warning).

Arrays — repeated params

const tags = useRouteState('tags', {
  parser: arrayParser(stringParser), // or arrayParser(integerParser), …
  default: ['vue', 'nuxt'],
})
// → ?tags=vue&tags=nuxt

JSON objects — single encoded param

const filter = useRouteState('filter', { parser: jsonParser, default: { status: 'active' } })
// → ?filter=%7B%22status%22%3A%22active%22%7D

Zod schemas (validation + type inference)

const userSchema = z.object({ name: z.string().min(2), age: z.number().int() })
const user = useRouteState('user', {
  parser: zodParser(userSchema),
  default: { name: 'buffy', age: 1 },
})
// Invalid values fall back to the default (+ dev warning). Type is z.output.

Multi-key: useRouteStates

Manage several params in one call. Every write merges all managed keys into a single router call (no stale-copy overwrites).

const { sort, limit } = useRouteStates({
  sort: { default: 'recent' },
  limit: { type: 'integer', default: 20 },
})
limit.value = 50 // → one router.replace merging { sort, limit }

Custom parsers

interface Parser<T> {
  parse(raw: string | string[] | null): T | null // null ⇒ unparseable ⇒ default (+ dev warn)
  serialize(value: T): string | string[] | null   // null ⇒ remove param
}

const upperParser: Parser<string> = {
  parse: (raw) => (Array.isArray(raw) ? raw[0] : raw)?.toUpperCase() ?? null,
  serialize: (v) => v.toUpperCase(),
}
const name = useRouteState('name', { parser: upperParser, default: '' })

Options API

Works inside setup() of Options-API components (the standard Vue 3 composable path):

export default defineComponent({
  setup() {
    return { page: useRouteState('page', { type: 'integer', default: 1 }) },
  },
  methods: {
    go(p: number) { this.page = p },
  },
})

Options

| Option | Type | Default | Meaning | |---|---|---|---| | type | 'string' \| 'number' \| 'float' \| 'integer' \| 'boolean' \| 'date' \| 'json' \| 'slug' | — | Shorthand selecting a built-in parser | | parser | Parser<T> \| ZodSchema \| parser key | string parser | Custom parser, Zod schema, or shorthand key | | default | T | '' | Value when the param is absent or unparseable | | history | 'replace' \| 'push' | 'replace' | How writes are applied; push lets back step through changes | | debounce | number (ms) | off | Coalesce rapid writes; only the final value after a pause is written | | persist | boolean | false | Keep the param across route.path changes (otherwise it resets) | | clear | 'remove' \| 'empty' | 'remove' | URL behavior when the value is undefined/null/empty array | | shallow | boolean | false | Use a shallow ref (perf for large JSON values) |

Behavior notes

  • Type inference is always derived from the parser: shorthand key, custom Parser<T>, Zod schema (z.output), or the default value's runtime type.
  • SSR (client-only sync): values are applied after hydration from the URL. Server and first client render both show defaults — no hydration mismatch. Tradeoff: a brief flash of defaults, and SSR HTML does not reflect query state (interactive state by design).
  • Back/forward: query changes are watched; the shared ref updates reactively without page reloads.
  • Route changes: by default the param is dropped and the state resets to its default when route.path changes (nested routes included). Set persist: true to keep it.
  • Sharing: useRouteState('key') in multiple components returns the same underlying ref (like useState). The first call's parser/default win; conflicting later calls log a dev warning.
  • Edge cases: empty string ?q= → default; malformed values → default + dev warning; NaN/Infinity → param removed; multiple values for a scalar param → first value + dev warning; non-component contexts → local ref + dev warning.
  • Deep mutations of object states (e.g. state.value.filter.x = 1) update templates but do not sync to the URL — replace the whole value instead (the URL is a string).

Development

npm install
npm run dev        # playground (module loaded from source)
npm run test       # vitest — parsers + composable behavior
npm run typecheck  # vue-tsc on src + tests
npm run build      # unbuild → dist/

Nuxt 4 parity

Nuxt 3 and Nuxt 4 can't share one node_modules, so the Nuxt 4 playground is a standalone app with its own install that consumes the built module — the same path real npm i @ifds/route-state users take.

cd playground-nuxt4 && npm install   # one-time: Nuxt 4 deps
cd ..
npm run prepare:nuxt4               # unbuild → dist/ → vendored into playground-nuxt4/node_modules
npm run build:nuxt4                  # nuxt build against the vendored module
npm run dev:nuxt4                    # dev server (Nuxt 4)

The vendored copy resolves nuxt/app, vue, and vue-router from the app's own Nuxt 4 install, and the module has no Nuxt-4-only internals (compatibility: { nuxt: '>=3.0.0' }) — so a passing build:nuxt4 plus the SSR smoke test is parity evidence for both majors.

Structure and Architecture

For this matter, you may read this file.

Publishing

See PUBLISHING.md for the npm release process.

License

MIT.