August 19 2025
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
Founder & Engineer
4 min Intermediate Frontend
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:
- server state
- UI state
- 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
useEffectblocks 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
- Server state, UI state, and URL state exist to solve different problems.
- Duplicating server state into local state without a real reason usually creates useless synchronization.
- Filters, sorting, and pagination often belong in the URL, not only inside the component.
- Frontend architecture gets more predictable when each kind of state has a clear owner and lifecycle.
Practice checklist
Use this when you answer
- Can I say clearly what came from the server, what only controls UI, and what needs to survive through the URL?
- Am I copying remote data into local state without a concrete reason?
- If the page refreshes, what should stay the same and what should reset?
- Does my choice of where to store this state make sharing, refresh, and debugging better or worse?
You finished this article
Next step
Who Owns This State? Next step →Share this page
Copy the link manually from the field below.