When working on interactive websites, we constantly face the challenge of defining if something should be executed on the server or in the client, but how do we decide it? This article will help you know how.
This article does not applies to SPAs.
Different types of web apps
We have two types of web apps: Offline-first and Online-first.
Offline-first apps are the ones where you need an insanely fast response for the user. They are extremely interactive. Good examples are Notion (or any WYSIWYG editors), Google Calendar, Trello, and Google Sheets.
Online-first apps are the most common cases, all apps that don't need to be offline-first should be online-first. They rely on the server's responses to execute things and show the results to the users only after it.
For offline-first apps, you must execute everything on the client BEFORE executing it on the server, this way you don't have to deal with the server's latency, so it's a solved case.
Now, online-first apps, are the real deal. They prompt us with the challenge of deciding where to execute something.
The criteria
To determine if you should execute something in the client or on the server, you need to ask yourself the following questions:
Does the user will lose something if the power goes out?
If they lose something, will it be extremely important?
Now, let's see how to apply these questions.
Our example
Let's have a web app with a sidebar that displays the username of the currently logged user and a page that has a button "edit username button":
When clicking on the "edit username button", a modal shows up:
And after the username is successfully updated, the username on the sidebar changes, and a success toast appears on the top right corner of the page:
This is not a real web app, I created these images using Photopea
State
Notice that we have multiple states here:
The user's username is a state
The open/closed modal is a state
The toast open/closed and the message are states
The client
should only manage short-lived, unimportant states, that control mostly visual things.
The server
should have no state, but should be able to operate with states:
query string
sessions
cookies
In summary, avoid states as best as you can, but be aware that we rarely can avoid them, so we must learn how to deal with them.
Opposite poles
We have multiple ways of doing it, and I'll tell you the 2 cases: One that does all the changes through the client and the other that does all the changes through the server.
All client
Initial load:
The page loads with all the elements, but the username is empty
Does a request to the server's API to get the username
Changes the HTML using JS with the data returned by the server's API
The user clicks on the "edit username button" button:
- It triggers a JS function that opens the modal (removing a
hidden
class, creating it, and appending it to the HTML, it doesn't matter)
The user inserts his new username and clicks on the "submit" button:
The client makes a request on the server's API and waits for it to succeed
Then update the HTML text of the username with the new username
Closes the modal
Makes the toast appear with the message "username successfully updated!" and disappears after some seconds.
All server
Initial load:
Get username from session / JWT / database / doesn't matter
Returns the page already completed with the username
The user clicks on the "edit username button" button:
Redirects the user to another page, that has the modal open and the input
Because the username still appears on the sidebar, it will have to get the username again
The user inserts his new username and clicks on the "submit" button:
Submits a form that makes a POST request to a route in the server, that changes the username and redirects to another page
The new page has the modal closed, the new username, and a toast rendered
Why do both cases suck
Let's see why none of the cases work very well to create a good user experience:
All client
For the ones that only ever worked with React/SPAs, this seems like the only way to do things, and nothing seems wrong, but let's see the drawbacks that this solution has for the user experience:
The user will see a glitch on their username in the bottom left corner since it has to be loaded after the page loads
It's hard to isolate the things and reproduce the steps to get to the "new username" state (in this case, make the success toast show up)
All server
In this case, I think that it's easier for everyone to see why this approach is bad:
You will need to copy the page with the "update username button", with the addition of a modal on top of it. Increases nonsense code duplication.
As we can't use any client-side interactions, the toast will stay there for as long as the user stays on the page
Uh, but what about the latency?? If you need to worry about latency in 2024, you probably are doing something wrong. Please, reconsider where are you hosting your servers, how you are delivering your content, and what are your routes doing. Read this article from the Yahoo guys, they rule.
Balance
The best approach is neither all server-side nor all client-side, but a mix of both. And we need to balance this properly.
The correct flow
Initial load:
[server] Get username from session / JWT / database / doesn't matter
[server] Returns the page already completed with the username
The user clicks on the "edit username button" button:
-
[client] It triggers a JS function that opens the modal (removing a
hidden
class, creating it, and appending it to the HTML, it doesn't matter)
The user inserts his new username and clicks on the "submit" button:
[server] Submits a form that makes a POST request to a route in the server, that changes the username and redirects to the same page, but this time using a query param
toastJSON
[server] The route will then check if
toastJSON
is present in the query params, and in addition to the page, will also return a toast without JS.[client] The client will have an
window.onload
event, that searches for toast elements and applies the necessary JS to them.
Now the user never will lose an important toast nor have glitches in the UI.
It's a lot easier to test if the UI is working: all of our state is shortlived and has no impact if it's lost.
States
Let's review our states and see which part of our system is controlling them:
[server] The user's username is a state
[client] The open/closed modal is a state
[server + client] The toast open/closed and the message are states
Conclusion
Mixing client and server actions helps us to develop more stable and testable applications, without having to manage complex unreproducible states.
Thanks for reading.
Top comments (1)
Great