Query strings are underrated: Using the URL as your app’s state container

Query strings are underrated. Many developers overlook the URL as a place to store app state, but it is reliable, beneficial, and often worth adopting. This guide shows you where query strings fit, how to use them in React, and when to avoid them.

What are query strings

A URL has several parts. For this article, the key piece is the query string — the key–value portion at the end of a URL — which you can use to store app state.

Why use query strings for state

Query strings already power things like form inputs and user tracking. A lesser-known but practical use case is storing UI state — values you might otherwise keep only in memory via a framework or third-party state library.

Benefits of storing state in query strings

  • Shareability and bookmarkability: Users can share a link or bookmark it and return to the same app state.
  • Persistence on reload: Query strings survive reloads without reaching for localStorage or a server. The state lives in the URL, so refreshes do not wipe it.
  • Simple global state: URL state is accessible across components without prop drilling or elaborate setup. You avoid wiring a global store when all you need is transparent UI state.

How to store app state in URL query strings

This section focuses on React, though the same concepts apply to frameworks like Vue, Svelte, and Solid.

URLSearchParams

URLSearchParams is the simplest built-in API for manipulating query strings in the browser. There is nothing to install, and the methods cover basic needs.

// Assume current URL is:
// https://webapp.com/items?display=grid&theme=dark
const queryString = window.location.search;
const params = new URLSearchParams(queryString);

console.log(params.has("display")); // true
console.log(params.get("display")); // "grid"

params.set("display", "list");

// Push the update to the address bar without a full navigation
const newUrl = `${window.location.pathname}?${params.toString()}`;
window.history.replaceState(null, "", newUrl);

When to use: very simple views or one-route apps. For larger apps, routers and dedicated libraries handle query strings more cleanly. Note that URLSearchParams is not type-safe; values are strings unless you layer parsing and validation.

nuqs

nuqs is a type-safe query-string state manager for React. Its API mirrors useState, which makes adoption straightforward.

import { useQueryState } from "nuqs";

export function NameField() {
  const [name, setName] = useQueryState("name");
  return (
    <>
       setName(e.target.value)}
        placeholder="Your name"
      />
      

Hello, {name ?? “anonymous visitor”}!

</> ); }

Why it helps: intuitive React ergonomics, good developer experience, and type-safety via parsers transform URL state from ad hoc string juggling into a reliable pattern.

React Router

If you use React Router, the useSearchParams hook makes it easy to read and write query values.

import { useSearchParams } from "react-router-dom";

export function Filters() {
  const [searchParams, setSearchParams] = useSearchParams();

  const view = searchParams.get("view") ?? "grid";

  function setView(next) {
    const nextParams = new URLSearchParams(searchParams);
    nextParams.set("view", next);
    setSearchParams(nextParams, { replace: true });
  }

  return (
    <>
      
      

Current view: {view}

</> ); }

Trade-off: convenient and built-in for React Router apps, but there is no first-class type checking for params unless you add it yourself.

TanStack Router

TanStack Router provides a comprehensive, type-friendly approach to URL state. It encourages schema definitions for routes and search parameters, which improves safety and refactors.

// Pseudo-style example (conceptual)
// Define a route with a typed search schema
const itemsRoute = createFileRoute("/items")({
  validateSearch: (search) => ({
    page: Number(search.page ?? 1),
    view: (search.view === "list" ? "list" : "grid"),
  }),
});

function ItemsPage() {
  const { search, setSearch } = itemsRoute.useSearch();
  return (
    <>
      
      

Page: {search.page}, View: {search.view}

</> ); }

Why it helps: a typed search schema reduces bugs, keeps URLs canonical, and integrates with the router’s data-loading lifecycle.

Downsides and trade-offs

  • Long, messy URLs: Packing too much state into the URL hurts readability and can exceed practical limits.
  • Maintenance overhead: Keeping URL state in sync with UI can be more work than local component or store state if you do not use a good abstraction.
  • Type safety: Browser APIs are string-only. Use libraries or your own parsing to avoid subtle bugs.

When query strings are not the right fit

  • Sensitive information: Never store secrets, tokens, or passwords in the URL. It is visible, logged, and shared.
  • Non-UI or irrelevant state: Only store state that affects how the page renders, like filters, sorting, pagination, or theme.
  • Overly long URLs: If serializing state produces unwieldy URLs, shorten the data or choose a different persistence strategy.

Conclusion

We have covered the benefits, tools, and pitfalls of managing state in query strings. From simple APIs like URLSearchParams to type-safe options like nuqs and modern routers, React developers have a range of choices for persisting state in the URL. We also looked at trade-offs — long URLs, type safety, and security concerns — that can complicate this approach if mishandled.

The key takeaway is balance. Query strings are an underrated way to improve shareability, bookmarkability, and persistence across reloads without inflating app complexity. They are not a blanket replacement for framework-level state. Use them when transparency and durability matter — filters, themes, pagination — and avoid them for sensitive or bulky data.

Applied thoughtfully, query strings can be a lightweight, reliable part of your state management toolkit and help deliver smoother, more resilient user experiences.

The post Query strings are underrated: Using the URL as your app’s state container appeared first on LogRocket Blog.

 

This post first appeared on Read More