Skip to main content

Server State vs UI State vs URL State Without Mixing Everything

A lot of frontend architecture degrades when different kinds of state get treated as if they were the same thing.

Andrews Ribeiro

Andrews Ribeiro

Founder & Engineer

The problem

One of the most common ways to overcomplicate frontend architecture is to treat every mutable value as if it were the same kind of state.

Then you start seeing things like:

  • API results copied into useState
  • important filters stored only in memory
  • visual state ending up in a global store
  • query string ignored on a screen that obviously needs to be shareable

The effect does not show up on the first screen.

It shows up later:

  • refresh loses context
  • synchronization gets fragile
  • debugging gets annoying
  • the team debates tools when the real problem was modeling

Mental model

Not every kind of state is the same.

A simple split already helps a lot:

  1. server state
  2. UI state
  3. URL state

Short version:

frontend architecture gets better when you stop asking only “where do I store this?” and start asking “what kind of state is this?”

Breaking the problem down

Server state

Server state is what comes from outside and whose main source of truth is not in the browser.

Examples:

  • order list
  • user profile
  • current balance
  • search results from the backend

This state has its own characteristics:

  • it can become stale
  • it needs refetching or invalidation
  • it may depend on cache
  • it does not truly belong to the component

The common mistake is to fetch remote data and immediately duplicate all of it into local state for no reason.

UI state

UI state exists to control the visual and interactive experience.

Examples:

  • open modal
  • selected item
  • active tab
  • temporary input value
  • expanded accordion

This state usually:

  • is born and dies with the interaction
  • is local by default
  • does not need to survive a refresh

When it gets pushed into global state without a real reason, the application becomes heavier to understand.

URL state

URL state is what needs to be shareable, navigable, or restorable.

Examples:

  • search
  • filter
  • sorting
  • pagination
  • an important tab on the screen

If the user hits refresh or copies the URL, does it make sense for that context to remain?

If the answer is yes, the URL is probably the right owner.

The classic mistake

A lot of screens mix everything like this:

  • use query parameters to fetch data
  • copy the remote result into local state
  • keep the filter only inside the component
  • use an effect to sync everything back

That creates a mini synchronization machine for no good reason.

Most of the time there is a cleaner design available.

Simple example

Imagine a users screen with:

  • search
  • status filter
  • sorting
  • remote list
  • details modal

A better split would be:

  • server state: the user list coming from the API
  • URL state: search, filter, sorting, and page
  • UI state: the open modal and the selected user

That makes it clearer:

  • what needs to survive refresh
  • what needs to be fetched again
  • what is only visual behavior

Now compare that with the messy version:

  • everything inside the component
  • multiple useEffect blocks syncing query string
  • a local copy of the API response
  • a modal depending on duplicated data

It is the same feature.

But one version is readable. The other turns into quicksand.

Common mistakes

  • Treating server state as if it were UI state.
  • Ignoring the URL on screens the user needs to share or revisit.
  • Lifting UI state to global scope without a real need.
  • Fixing bad modeling by adding more synchronization effects.

How a senior thinks

People with more frontend judgment usually ask:

  • where does the truth of this data live?
  • does this need to survive refresh?
  • does this need to be shareable by link?
  • is this remote data, visual state, or navigation context?

Those questions cut a lot of noise before the choice of library even matters.

What this changes for the team

When the split becomes clear:

  • less manual synchronization shows up
  • refresh stops feeling like a random bug
  • the data flow becomes more predictable
  • the team can discuss architecture with less semantic confusion

In the end, a lot of “state complexity” is just badly classified state.

Not every mutable value is the same kind of state.

When you mix different owners together, the frontend charges that debt fast.

Quick summary

What to keep in your head

Practice checklist

Use this when you answer

You finished this article

Next article Where Page, Layout, Component, and Hook Decisions Belong Previous article Creating Technical Clarity for the Whole Team Without Writing a Treatise

Keep exploring

Related articles