Let’s look at the problems that often occur when using single-page application (SPAs). In the traditional separation between server and client, the client is responsible for the appearance and behavior. State, buisiness logic, routing, presentation logic, and templating are found exclusively on the server (Fig. 1).
When we build an app and want to optimize it to respond more quickly and possibly provide offline capabilities, we often will move some responsibilites from the server to the client. Many companies consider it an additional benefit if we can separate these responsibilites into different departments. Backend experts then only need to provide an API but do not need to take care of any HTML/CSS/JS. Aditionally, there is a common misconception that JSON is smaller than HTML. Jon Moore explains in an article why that is not the case. Often the view logic and the templating are moved into the frontend. This is the case with technologies like React or Vue.js (Fig 2).
A further step is often to push the routing into the client. For example, this is what happens in React with React Router and in Vue.js with Vue Router. In frameworks like Angular or Ember, the client is also responsible for the routing (Fig. 3).
It may seem that the shift of responsibilities from the server to the client is only a change of boundary. However, this change in boundary has further consequences that we need to consider.
We used to only interpret HTML in the client - a functionality which the browser provides out of the box. Now we need a JSON client and an API which we can consume. This also adds a level of indirection: in the presentation logic we no longer have direct access to the business logic and the application state but must instead transform data into JSON and back again. Defining and modifying this API requires a high level of coordination and communication especially when the server and client are being developed by different teams.
If both the business logic and the application state remain completely on the server, we either have the same or more communication between client and server. If we however move a part of the business logic onto the client, we now need to continually decide which business logic belongs on the server, which business logic belongs on the client, and which needs to be duplicated in both. Here we need to pay attention to the fact that the client is not a secure or trusted environment. For this reason, we also need to verify a lot of things on the server. GraphQL is an example of a technology which attempts to use the backend as only a “database” with no business logic on the server.
The state of an application is also no longer only on the server (database/session…). We have to maintain state in the client as well. We can achieve this with solutions like LocalStorage/PouchBD and also with technologies like Redux, Flux, Reflux, and Vuex. Here we also need to decide which data belongs to the client, which data belongs to the server, and which needs to be saved in both places. We also need to think about (multi-leader) synchronization: because data can be simultaneously modified on more than one client, which could be offline for longer periods of time, there will definitely be write conflicts which need to be resolved.
A further reason for hydration is SEO: since JS is not reliably interpreted by search engines, it makes sense to provide content as HTML. With hydration we add not only futher complexity to the client but also extra infrastructure.
Execution in an uncontrolled environment
Browsers are also a more uncontrolled execution environment than our server. We need to pay attention to this when we push more code into the browser.
Additionally, the uncontrollable execution environment also has certain repercussions. On the server we can choose a version of our programming language (e.g. JDK 7). However, there are an unbelievable amount of web browsers in many different versions. Even the newest version of a browser does not support all of the specified features. The complexity of testing an application is therefore much higher.
In summary, shifting routing, templates and presentation logic onto the client increases the responsibility that it has. Additionally, more infrastructure, coordination, duplication, and indirection are necessay and we transport code from a controlled execution environment into an uncontrolled execution environment.
An alternative architecture
<span> elements. Their declarative nature also makes it possible to ensure compatibility between browser versions to a great extent. This is how server-side rendered HTML and CSS pages can be interpreted by older and newer browsers, and even devices which did not exist at the time of the creation of the code can usually interpret them to the extent that they are usable.
We recommend this approach of relying on the basic features of the web and using these three key technologies based on these core principles. For this reason, we have created a website with best practices and given the approach a name: ROCA (ROCA stands for Resource Oriented Client Architecture).
How can I apply this practially?
Make it work: HTML
<input type="date">creates a datepicker.
<audio>creates an player for audio files
<video>creates a player for video files.
Additionally, there is always a fallback for these elments so that older browsers can still continue to work. Often, the browser can make better decisions about how to render these elements than we can. An
Sometimes we also want to show a different page based on user input. Since the server is now controlling the functionality in the application, it can decide where to go next with a redirect (Fig. 5).
Another benefit of HTML is that we can arbitrarily nest elements. In order to make our UI elements easier to reuse, we can extract some element into components. Components are a reusable HTML structure which we can use as an abstraction for our functionality.
To maintain an overview of our components, we can structure them in a system like Atomic Design. You can find more about them in an article by Ute Mayer on page 58. Here larger components always contain smaller components. The easiest way to practically apply this is to define an HTML snippet and to copy the markup and switch out the content in the component. However, this isn’t easy to maintain over time. Most templating engines provide a way to define an HTML snippet once and make it reusable. In Thymeleaf we can do this with
One of the main benefits of this approach is also that we can style our components based on this HTML structure. This is where the larger challenges begin.
Make it pretty: CSS
Now we come to the point where we may be likely to fail. On the one hand, we could argue that it is most important that the application works even if it doesn’t look very nice. On the other hand, no one wants to use an application which is ugly. In this context, certain aesthetics are important.
Although “beauty” is subjective, we can invest a lot of time in topics like design or typography in order to develop a feeling for how things should appear in a finshed product. Unfortunately, many developers do not have the necessary time or desire to really get into the topic.
Luckily, we don’t always have to reinvent the wheel. It is possible to use finished component libraries like Bootstrap or Foundation. Here the designers have already made the most important decisions so that the application is usable for end users.
With HTML forms we can achieve something similar: instead of sending the browser the content of a form and retrieving the result as a new page, we can submit the form via Ajax and work with the HTML which is returned and replace only parts of the page.
The benefit here is that the logic that is defined in the
connectedCallback function will always be executed by the browser when a
SPA vs. ROCA