URL als State: Warum SearchParams oft besser sind als useState
AI generated
</>
{ }
React · SearchParams · nuqs · Deep Linking
URL als State
warum SearchParams oft die bessere Wahl sind

Filter, Sortierung, Pagination und aktive Tabs landen in vielen React Anwendungen in useState, obwohl sie eigentlich Teil der Adresse sind. Wer diesen Zustand stattdessen in der URL speichert, bekommt Deep Linking, funktionierenden Browser Zurück Button und teilbare Links geschenkt, ganz ohne zusätzlichen Store.

16 Min. Lesezeit SearchParams · nuqs · History API · SSR React 19 · Next.js · React Router

1. Warum URL Zustand oft besser ist als useState

Eine Produktliste mit Filtern für Kategorie, Preisbereich und Sortierung wird in vielen React Anwendungen mit useState umgesetzt. Das funktioniert auf den ersten Blick, hat aber einen entscheidenden Nachteil: Der Zustand existiert nur im Arbeitsspeicher der aktuellen Seite. Ein Neuladen der Seite setzt die Filter zurück, ein geteilter Link zeigt beim Empfänger die ungefilterte Liste, und der Browser Zurück Button springt nicht zwischen Filterzuständen, sondern verlässt direkt die Seite. Das URL als State Pattern löst all diese Probleme, indem Filterzustand, Pagination und aktive Tabs direkt in die Adresse geschrieben werden.

Die URL ist im Web seit jeher der native Ort für adressierbaren Zustand, genau dafür wurden Query Parameter ursprünglich erfunden. Wird Zustand konsequent in SearchParams statt in useState gehalten, wird jede Kombination aus Filter, Sortierung und Seite automatisch zu einer eigenen, teilbaren, bookmarkbaren Adresse. Für SEO relevante Listenseiten ist das URL als State Pattern zudem oft die einzige Möglichkeit, überhaupt indexierbare, eindeutige URLs pro Filterkombination zu erzeugen.

Der Umstieg auf URL Zustand bedeutet nicht, dass useState komplett verschwindet. Rein visuelle, kurzlebige Interaktionszustände wie ein geöffnetes Dropdown Menü gehören weiterhin in lokalen Component State. Die Faustregel lautet: Alles, was ein Nutzer als Teil der aktuell angezeigten Ansicht verstehen und mit anderen teilen würde, gehört in die URL, alles rein Interne bleibt in useState.

2. Native SearchParams mit useSearchParams

React Router und Next.js bieten beide einen useSearchParams Hook, der die aktuellen Query Parameter als URLSearchParams Objekt zurückgibt und eine Setter Funktion zum Aktualisieren bereitstellt. Anders als bei useState triggert ein Update hier eine tatsächliche Navigation, wodurch die Adresszeile des Browsers synchron zum angezeigten Zustand bleibt, ohne dass Entwickler manuell window.history manipulieren müssen.

Der native Ansatz erfordert allerdings manuelles Parsen und Serialisieren: Ein numerischer Filter kommt als String aus der URL zurück, ein Array von ausgewählten Kategorien muss selbst kodiert und dekodiert werden, und ein fehlender Parameter muss explizit mit einem Default Wert abgefangen werden. Für einfache Fälle mit ein bis zwei Parametern ist das gut handhabbar, bei komplexeren Filterformularen mit mehreren Typen wird der Boilerplate schnell unübersichtlich.


// components/ProductFilters.jsx — native useSearchParams, manual parsing required
import { useSearchParams } from 'react-router-dom';

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

  const category = searchParams.get('category') ?? 'all';
  const page = Number(searchParams.get('page') ?? '1'); // manual parsing to number

  function setCategory(value) {
    setSearchParams((prev) => {
      const next = new URLSearchParams(prev);
      next.set('category', value);
      next.set('page', '1'); // reset pagination when the filter changes
      return next;
    });
  }

  return (
    <select value={category} onChange={(e) => setCategory(e.target.value)}>
      <option value="all">All categories</option>
      <option value="shoes">Shoes</option>
      <option value="bags">Bags</option>
    </select>
  );
}

3. nuqs: typsichere Query Parameter ohne Boilerplate

nuqs ist eine spezialisierte Bibliothek, die genau dieses Parsen und Serialisieren übernimmt und dabei eine API bietet, die sich fast identisch zu useState anfühlt. Statt manuell mit URLSearchParams zu arbeiten, definiert useQueryState einen Parameter mit einem Parser, etwa parseAsInteger oder parseAsArrayOf, und liefert direkt den typisierten Wert samt Setter Funktion zurück, exakt wie ein normaler React State Hook.

Ein zentrales Feature von nuqs ist die eingebaute Debounce und Throttle Unterstützung für die URL Aktualisierung: Bei einem Live Suchfeld, das bei jedem Tastendruck die URL ändern würde, lässt sich über die Option shallow und einen konfigurierbaren Timeout verhindern, dass bei jedem Zeichen ein neuer History Eintrag entsteht. Für Next.js und React Router bringt nuqs jeweils eigene Adapter mit, die sich um die Integration mit dem jeweiligen Routing System kümmern.


// components/ProductFilters.jsx — nuqs feels like useState but syncs to the URL
import { useQueryState, parseAsInteger, parseAsStringEnum } from 'nuqs';

function ProductFilters() {
  const [category, setCategory] = useQueryState(
    'category',
    parseAsStringEnum(['all', 'shoes', 'bags']).withDefault('all')
  );
  const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));

  function handleCategoryChange(value) {
    setCategory(value);
    setPage(1); // reset pagination alongside the filter change
  }

  return (
    <select value={category} onChange={(e) => handleCategoryChange(e.target.value)}>
      <option value="all">All categories</option>
      <option value="shoes">Shoes</option>
      <option value="bags">Bags</option>
    </select>
  );
}

4. Browser History: push versus replace richtig einsetzen

Ein häufig übersehenes Detail beim URL als State Pattern ist die Wahl zwischen pushState und replaceState. Jede Filteränderung mit push in die History zu schreiben führt dazu, dass der Zurück Button des Browsers durch jede einzelne Filteränderung navigiert, statt zur vorherigen Seite zurückzukehren, was bei Live Suchfeldern mit Debounce schnell zu einem frustrierenden Dutzend History Einträgen für eine einzige Eingabe führt.

Die pragmatische Regel: Bewusste, diskrete Nutzeraktionen wie das Wechseln einer Kategorie oder das Umschalten einer Sortierung rechtfertigen einen push Eintrag, weil der Nutzer plausibel dorthin zurückkehren möchte. Kontinuierliche Eingaben wie Tippen in ein Suchfeld oder das Ziehen eines Preisreglers sollten stattdessen mit replace aktualisiert werden, sodass die History nicht mit Zwischenzuständen überflutet wird. Sowohl die native History API als auch nuqs unterstützen diese Unterscheidung über eine explizite Options Angabe pro Update.

5. Mehrere unabhängige Zustände in einer URL kombinieren

Sobald eine Seite mehrere unabhängige URL Zustände gleichzeitig verwaltet, etwa Filter, Sortierung, Pagination und einen aktiven Tab, wird die Koordination zwischen diesen Parametern wichtig. nuqs bietet dafür useQueryStates, mit dem sich mehrere zusammengehörige Parameter als ein Objekt lesen und in einem einzigen atomaren Update schreiben lassen, statt mit mehreren separaten Aufrufen mehrere History Einträge oder Race Conditions zu riskieren.

Ein praktisches Beispiel: Ändert sich die Kategorie, muss die Pagination gleichzeitig auf Seite eins zurückgesetzt werden. Werden beide Parameter mit zwei separaten Setter Aufrufen aktualisiert, kann es je nach Batching Verhalten zu zwei getrennten Navigationen kommen. Mit useQueryStates lässt sich das gesamte Update als ein Objekt in einem einzigen Aufruf durchführen, wodurch nur eine einzige URL Änderung und ein einziger History Eintrag entstehen.


// components/ProductListState.jsx — coordinate multiple related query params atomically
import { useQueryStates, parseAsInteger, parseAsStringEnum } from 'nuqs';

function useProductListState() {
  return useQueryStates({
    category: parseAsStringEnum(['all', 'shoes', 'bags']).withDefault('all'),
    sort: parseAsStringEnum(['price', 'newest']).withDefault('newest'),
    page: parseAsInteger.withDefault(1),
  });
}

function ProductList() {
  const [{ category, sort, page }, setState] = useProductListState();

  function changeCategory(value) {
    // Single atomic update — one navigation, one history entry
    setState({ category: value, page: 1 });
  }

  return <p>{category} / {sort} / page {page}</p>;
}

6. Server Side Rendering und URL Zustand

Ein wesentlicher Vorteil von URL Zustand gegenüber Client seitigem State zeigt sich bei Server Side Rendering: Weil die Query Parameter bereits im initialen Request an den Server verfügbar sind, kann ein Next.js Server Component den korrekten, gefilterten Datensatz direkt beim ersten Rendern laden, ohne auf einen Client seitigen Effekt und einen zweiten Request warten zu müssen. Das verbessert sowohl Time to Content als auch die Konsistenz zwischen Server und Client gerenderten Inhalten erheblich.

In der App Router Architektur von Next.js werden SearchParams als searchParams Prop direkt an Server Components durchgereicht, wodurch sich Filterlogik teilweise komplett auf den Server verlagern lässt, während der Client lediglich für die interaktive Aktualisierung der URL zuständig bleibt. Diese Kombination aus serverseitigem initialem Rendering und clientseitiger URL Manipulation ist einer der Hauptgründe, warum das URL als State Pattern in modernen React Frameworks so gut funktioniert.

7. Grenzen des Patterns: was nicht in die URL gehört

Nicht jeder Zustand gehört in die URL. Sensible Daten wie Authentifizierungstoken oder personenbezogene Informationen dürfen niemals als Query Parameter erscheinen, weil URLs in Browser Historien, Server Logs und Analytics Tools landen und damit faktisch nicht vertraulich sind. Ebenso ungeeignet sind sehr große Datenmengen, etwa der komplette Inhalt eines mehrstufigen Formulars, weil Browser und Server praktische Längenbeschränkungen für URLs durchsetzen, üblicherweise irgendwo zwischen zwei und acht Kilobyte je nach Browser und Server Konfiguration.

Auch rein transiente UI Zustände wie die Animation eines Modals oder der Fokus Zustand eines Eingabefelds sollten nicht in die URL wandern, weil sie für den Nutzer keinen adressierbaren Wert haben und die URL unnötig mit technischem Rauschen füllen würden. Die Trennlinie verläuft klar entlang der Frage, ob ein Zustand Teil dessen ist, was ein Nutzer als aktuelle Ansicht der Anwendung versteht und mit einem Link teilen würde.

8. URL Zustand mit Client und Server State kombinieren

In der Praxis existiert URL Zustand fast nie isoliert, sondern als ein Baustein neben Server State über TanStack Query und gelegentlichem Client State über Zustand oder Context. Ein typisches Muster: Die aktuellen Filterwerte kommen aus der URL über nuqs, werden als Query Key an useQuery übergeben, und TanStack Query übernimmt Caching und Deduplizierung für die tatsächlich geladenen Produktdaten.

Diese Kombination sorgt dafür, dass ein Wechsel der Filter automatisch einen neuen, korrekt gecachten Query auslöst, während der Browser Zurück Button zuverlässig zum vorherigen Filterzustand samt zugehörigem, bereits gecachtem Ergebnis zurückspringt. Die URL fungiert dabei als einzige Quelle der Wahrheit für den aktuellen Filterzustand, TanStack Query kümmert sich ausschließlich um die zugehörigen Serverdaten.

9. URL Zustand im Vergleich zu useState und Context

Die folgende Tabelle zeigt, für welche Art von Zustand die URL die passende Wahl ist, im Vergleich zu klassischem Component State und Context.

Kriterium useState Context URL / SearchParams
Überlebt Seiten Reload Nein Nein Ja, immer
Teilbar per Link Nein Nein Ja, nativ
Browser Zurück funktioniert Nein Nein Ja, über History
Eignung für sensible Daten Geeignet Geeignet Ungeeignet
Server Component Zugriff (SSR) Nicht möglich Nicht möglich Direkt über searchParams Prop

Mironsoft

React Architektur, State Management und moderne Frontend Infrastruktur

Filter, die nach jedem Reload verschwinden?

Wir bringen Filter, Pagination und Tab Zustände dorthin, wo sie hingehören, in die URL, mit nuqs, sauberer History Behandlung und direkter Server Component Integration für Next.js.

Filter Migration

Bestehende useState Filter auf SearchParams und nuqs umstellen

SSR Integration

Server Components direkt mit searchParams für schnelleres First Paint versorgen

TanStack Query Kopplung

URL Zustand als Query Key für konsistentes Caching der Serverdaten nutzen

10. Zusammenfassung

Das URL als State Pattern verlagert Filter, Sortierung, Pagination und Tab Zustände von useState in die Adresszeile, wodurch Deep Linking, ein funktionierender Browser Zurück Button und teilbare Links automatisch entstehen. Native useSearchParams Hooks funktionieren für einfache Fälle, erfordern aber manuelles Parsen und Serialisieren. nuqs übernimmt diese Arbeit mit einer useState ähnlichen API, typsicheren Parsern und eingebauter Unterstützung für Debouncing sowie die Koordination mehrerer zusammengehöriger Parameter.

Nicht jeder Zustand gehört in die URL: Sensible Daten, sehr große Datenmengen und rein transiente UI Zustände bleiben besser in useState oder einem Client Store. Kombiniert mit Server Side Rendering und TanStack Query für die zugehörigen Serverdaten wird die URL zur einzigen verlässlichen Quelle der Wahrheit für alles, was ein Nutzer als aktuelle, adressierbare Ansicht der Anwendung verstehen und teilen würde.

URL als State: Das Wichtigste auf einen Blick

Wann URL Zustand nutzen

Für Filter, Sortierung, Pagination und Tabs, die geteilt, gebookmarkt und über Reload hinweg erhalten bleiben sollen.

nuqs statt useSearchParams

Typsichere Parser, useState ähnliche API und automatische Serialisierung ersetzen manuelles URLSearchParams Handling.

push versus replace

Diskrete Aktionen mit push, kontinuierliche Eingaben mit replace, um die Browser History nicht zu überfluten.

Grenzen des Patterns

Sensible Daten, große Datenmengen und rein transiente UI Zustände gehören nicht in die URL.

11. FAQ: URL als State

1Warum Filterzustand in der URL statt useState?
useState verliert Zustand bei Reload, ist nicht teilbar und bricht den Zurück Button. Die URL löst alle drei Probleme.
2Unterschied useSearchParams und nuqs?
useSearchParams liefert rohe Strings, nuqs übernimmt Parsen und Serialisieren mit typsicherer, useState ähnlicher API.
3Wann push statt replace?
Push bei diskreten Aktionen, replace bei kontinuierlichen Eingaben wie Tippen, um die History nicht zu überfluten.
4Dürfen sensible Daten in die URL?
Nein, URLs landen in Historien und Logs. Tokens und personenbezogene Daten gehören nicht in Query Parameter.
5Wie funktioniert URL Zustand mit SSR?
SearchParams werden direkt als Prop an Server Components übergeben, gefilterte Daten laden bereits beim ersten Rendern.
6Wie koordiniere ich mehrere URL Parameter?
Mit useQueryStates aus nuqs, mehrere Parameter als Objekt in einem atomaren Update statt mehreren Navigationen.
7Gibt es eine Längenbeschränkung?
Ja, meist zwischen zwei und acht Kilobyte je nach Browser und Server. Große Datenmengen gehören nicht in Query Parameter.
8Wie kombiniere ich URL Zustand mit TanStack Query?
Filterwerte aus der URL werden Teil des Query Keys, Änderungen lösen automatisch den passenden gecachten Query aus.
9Sollte jeder UI Zustand in die URL?
Nein, rein transiente Zustände wie ein offenes Dropdown bleiben in lokalem Component State.
10Unterstützt nuqs Debouncing?
Ja, über Timeout Optionen und shallow Updates lässt sich verhindern, dass jeder Tastendruck einen History Eintrag erzeugt.