Query Params

Server params - search, filters, pagination - live in the route query rather than in local refs or a store. A list screen is rarely one component. There's a heading with a search box, a filter bar, the list itself, and all of them need the same page and search values. Put those in the URL and nothing has to sync, each component reads the param it cares about and writes it back, with the route as the single source of truth. Links stay shareable and the back button does what people expect.

Two things deliberately stay out. Anything persisted per user, sorting included, belongs in Preferences and reads from the store instead. Display state nobody persists stays a local ref in the view, since nothing outside the page cares about it.

The primitive

useQueryParam in shared/composables/support wraps a single param as a writable computed. Reading pulls it out of the route, writing pushes a new query. Four behaviors in there are worth knowing, because none of them are obvious from a call site.

A value matching the default drops out of the query instead of being written. Page one never shows up as ?page=1, so the URL only ever carries what actually differs from the defaults.

Setting any param other than page clears page. Changing a filter or a search term means a new result set, and landing on page four of it is almost never what you want.

replace swaps the history push for a replace, which is what search uses. Without it every keystroke stacks a history entry and the back button turns into an undo for typing.

min treats anything shorter than the minimum as unset. Someone can hand edit a two character search into the URL, and if the API rejects it the param reads as empty rather than firing off a request that comes back an error.

One composable per resource

Instead of calling useQueryParam at each call site, every resource gets a composable in the app's composables/params folder that declares its full param set in one place, useBookmarkParams, useUserParams, and so on. The list and the heading both call it and get the same refs back.

Defaults come from settings, so the API owns things like the minimum search length rather than the build. Adding a param is one line in the resource's composable, and the type plus every consumer picks it up from there.

Sorting still comes back out of the same composable even though it lives in preferences, because the query key for a list wants every server param in one object. The composable wraps it to also reset the page, which route params get for free and a preference does not.

Each app keeps its own params folder because the same resource can mean different things in each. Bookmarks in the app filter by tag, bookmarks in admin only search and paginate.

See also

Settings for where the defaults these composables read come from.

Preferences for the per-user state that stays out of the route.

© Websanova 2026 About Privacy