TL; DR: The SPA trail is dark and full of horrors. You can fearlessly fight them ... or choose another path that will lead you to the right place: modern Rails.

I remember thinking Rails was focusing on the wrong goal when DHH announced Turbolinks in 2012. Then I was convinced that the instant response time during user interaction is the key to the excellent UX. Due to network delays, such interactivity is possible only if you minimize network dependency and, instead of network access, you maintain most of the state on the client.
')
I thought it was necessary for the applications I was working on. And with such an opinion, I tried many approaches and frameworks for implementing the same template: Single-page applications (SPA). I believed that SPA is the future. A few years later, I'm not sure what the future is, but I definitely want to find an alternative.
Rabbit Hole SPA
A one-page application is a JavaScript application that, once loaded, gets full control without having to reload the page: rendering, receiving data from the server, processing user interaction, updating the screen ...
Such applications look more native than traditional web pages, where the application depends on the server. For example, if you use Trello, you may notice how quickly cards are created.
Of course, with great force comes great responsibility. In traditional web applications, your server application includes a domain model and business rules, data access technology for working with a database, and a controller layer to control how HTML pages are collected in response to HTTP requests.
C SPA is a bit more complicated. You still need a server application that includes your domain model and rules, a web server, a database, and some kind of data access technology ... and a bunch of extra pieces on top:
For server:
- API that meets your data needs
- JSON serialization system for exchanging data with the client and a caching system that supports it
For a new javascript client:
- Template system for converting data to HTML
- Presentation of your domain model and rules
- The data access layer in the client application for data transfer to the server
- System update views when data changes
- A system for linking URLs and screens (I hope you won’t use one address for everything, it doesn’t look like a web app)
- A system for gluing all the components necessary for displaying screens and obtaining data for this
- New templates and architecture to organize everything when
- System for error handling, logging, exception tracking, etc.
- System for generating JSON for server requests
- An automated testing framework supporting your SPA
- Additional test suite for writing and support
- Additional set of tools for building, packaging and deploying a new application
Summing up, with SPA you will have another application for support. And a new set of problems to solve. And note, you cannot replace one application with another. You still need a server application (it will now render JSON instead of HTML).
If you have never worked with a SPA, you may underestimate the difficulties you will face. I know this because I made the same mistakes in the past. Render JSON? I can handle it. Rich domain object model in javascript? It sounds funny. And, you know, this framework will solve all these problems. Great cake!
Wrong.API and data exchange.
Data exchange between your new application and the server is a difficult problem.
There are two opposing forces:
- You will want to make as few requests to the server as possible to improve performance.
- Converting one record to JSON is easy. But the mixing of large models of different records to minimize the number of requests - not at all. You will need to carefully design the serialization logic to optimize the number of database queries and keep the performance level.
In addition, you need to think about what to download, and when to do it for each of their screens. I mean, you need a balance between boot, what you need right away, and what can be loaded lazily, and develop an API that allows you to do this.
Some standards may help here. JSON API to standardize JSON format; or GraphQL, to select only the necessary data, as complex as needed, in one query. But none of them will save you from:
- Study every data exchange
- Implementing queries that allow you to select data efficiently on the server
And both of these aspects represent a sufficient amount of additional work.
Boot time
People are used to associating SPA with speed, but the truth is that getting them loaded quickly is not so easy. There are many reasons for this:
- An application needs data before rendering something, and parsing a sufficiently large amount of JavaScript takes time.
- Beyond the initial HTTP request to load an application, you usually need to make one or more requests to get the JSON data needed to render the screen.
- The client must convert JSON to HTML to display at least something. Depending on the device and the amount of JSON to convert, this can introduce noticeable delays.
This does not mean that it is impossible to force the SPA to load quickly. I'm just saying that it is difficult and you should take care of it, because it will not come along with the SPA architecture.
For example, Discourse, a SPA based on Ember, has fantastic load times, but among other things, they preload a large amount of JSON data as part of the initial HTML in order not to make additional requests. And I note that the Discourse team is crazy about speed in a good way and their skills are well above average. Think about it before you can easily reproduce the same in your SPA.
The ambitious solution to this problem is isomorphic JavaScript: render your homepage on the server and quickly give it to the client while in the background the SPA is loading and gaining full control when ready.
This approach requires the execution of JavaScript runtime on the server, and it is also not without technical problems. For example, developers should consider SPA loading events, as the loading process changes.
I like the possibility of reusing the code in this idea, but I did not see the implementation that would allow me to go in the opposite direction. In addition, this page rendering process is very funny to me:
- Server: request to the server API
- Server: database query
- Server: generate JSON
- Server: convert JSON to HTML
- Client: display initial HTML
- Client: download SPA
- Client: parse initial HTML and subscribe to DOM events
Could you just request data from the database, generate HTML and start working?
This is not entirely fair, because you will not be a SPA and because most of this magic is hidden behind the framework, but it still seems wrong to me.
Architecture
Developing applications with a rich user interface is difficult. This is all because it is one of the problems that inspired the emergence of an object-oriented approach and many design patterns.
Managing the state on the client is difficult. Traditional websites usually focus on single-responsibility screens that lose their fortune when they reboot. In the SPA, the application is responsible for ensuring that all state and screen updates during use are consistent and run smoothly.
In practice, if you started writing JavaScript in small portions in order to improve the interaction, then in SPA you will have to write tons of additional JavaScript code. Here you should make sure that you are doing everything right.
There are as many different architectures as SPA frameworks:
- Most frameworks are different from the traditional MVC pattern. Ember was initially inspired by Cocoa MVC, but it changed its software model quite a bit in recent versions.
- There is a trend that developers prefer components rather than the traditional division into controller and view (some frameworks, such as Ember and Angular, have adopted this approach in recent versions). All frameworks implement some semblance of one-way data binding. Bilateral binding is not welcome because of the side effects that it can contribute.
- Most frameworks include a routing system that allows you to match URLs and screens, and determines how to create instances of components for rendering. This is a unique web approach that does not exist in traditional desktop interfaces.
- Most of the frameworks separate HTML templates from JavaScript code, but React puts on a mix of HTML generation and JavaScript and does it quite successfully, given its massive use. Now there is also a hype around embedding CSS in JavaScript. Facebook with its architecture Flux has influenced the industry quite a bit, and containers such as Redux, vuex and others are strongly influenced by it.
Of all the frameworks I've seen, Ember is my favorite. I adore his consistency and the fact that he is rather stubborn. I also like his latest programming model, which combines traditional MVC, components and routing.
On the other hand, I am strongly against the Flux / Redux camp. I have seen so many smart people applying them that I made every effort to study and understand it and not once. I can't help shaking my head in frustration when I see the code. I do not see myself happy while writing such code.
Finally, I can't accept the mix of HTML and CSS in components that are full of JavaScript logic. I understand what problem this solves, but the problems this approach introduces does not make it worth it.
Leave personal preferences, the bottom line is that if you choose the SPA path, then you will have a very difficult problem: to create the architecture of your new application correctly. And the industry is quite far from reaching an agreement on how to do it. Every year new frameworks, templates and versions of frameworks appear, which slightly changes the programming model. You will need to write and maintain a ton of code based on your architectural choice, so think about it as it should.
Code duplication
When working with SPA you will probably encounter code duplication.
For your SPA logic, you will want to create a rich model of objects representing your domain domain and business logic. And you still need the same thing for server logic. And it is a matter of time when you start copying the code.
For example, imagine that you work with invoices. You probably have an Invoice class in JavaScript that contains a total method that summarizes the price of all the elements so that you can render the cost. On the server, you will also need the Invoice class with the total method to calculate this cost in order to send it by e-mail. See? Client and server class Invoice implement the same logic. Duplication code.
As mentioned above, isomorphic javascript could even out this problem, making it easier to reuse the code. And I say level, because the correspondence between the client and the server is not always 1-to-1. You will want to be sure that some code never leaves the server. A large amount of code makes sense only for the client. Also, some aspects are simply different (for example, the server element can save data to the database, and the client can use the remote API). Reusing a code, even if possible, is a complex problem.
You can bet that you don’t need a rich model in your SPA and that you will instead work with JSON / JavaScript objects directly, distributing the logic across the UI components. Now you have the same code duplication mixed with your UI code, good luck with that.
And the same thing happens if you want templates for HTML rendering between the server and the client. For example, for SEO, how about generating pages on a server for search engines? You will need to re-write your templates on the server and make sure that they are synchronized with the client. Again duplicate code.
The need to reproduce the logic of patterns on the server and client, in my experience, is the source of the growing misfortunes of programmers. Doing it once is normal. When you do it for the 20th time, you will clutch your head. Having done this for the 50th time, you will wonder if you need all these SPA pieces.
Fragility
In my experience, developing a good SPA is a much more difficult task than writing web applications with generation on the server.
Firstly, no matter how careful you are, no matter how many tests you write. The more code you write, the more bugs you will have. And the SPA represents (sorry if I press hard) a huge pile of code for writing and support.
Secondly, as mentioned above, developing a rich GUI is difficult and results in complex systems consisting of many elements that interact with each other. The more complex the system you create, the more bugs you have. And compared to traditional MVC-based web applications, the complexity of the SPA is simply insane.
For example, to maintain consistency on the server, you can use restrictions in the database, model validation, and transactions. If something goes wrong, you reply with an error message. In the client, everything is slightly more complicated. A lot can go wrong because too much is happening. It may be that some record is saved successfully, and some other record is not. You may have gone offline in the middle of some operation. You must ensure that the UI remains consistent and that the application recovers from an error. All this is possible, of course, only much more complicated.
Organizational challenges
It sounds silly, but to develop a SPA, you need developers who know what to do with it. At the same time, you should not underestimate the complexity of the SPA, you should not think that any experienced web developer with the right motivation and understanding can write an excellent SPA from scratch. You need the right skills and experience, or expect important mistakes to be made. I know this because this is exactly my case.
This is perhaps a more important challenge for your company than you think. The SPA approach encourages narrow specialization teams instead of teams from general specialists:
- SPA frameworks are complex pieces of software that require countless hours of experience to be productive. Only people from your company who spend these hours behind the code will be able to support these applications.
- SPA frameworks require thoughtful and productive APIs. Compliance with these requirements requires a completely different set of skills than those that require work with the SPA.
The chances are that you will find yourself with people who cannot work with the SPA, and who cannot work on the server side, simply because they do not know how.
This specialization may be ideal for Facebook or Google and their teams consisting of several layers of engineering troops. But will it be good for your team of 6 people?
Modern Rails
There are 3 things in modern Rails that can change your mind about developing modern web applications:
- one of them is turbolinks and this is a brain explosion
- the other is an old friend who is being missed today: SJR responses and simple AJAX rendering requests
- and the latter was added recently: Stimulus
It is difficult to understand what kind of approach it is, without playing with it. Therefore, I will make a few references to Basecamp in the following sections. I have nothing to do with Basecamp, except as a happy user. As for this article, this is just a good living example of modern Rails, which you can try for free.
Turbolinks
The idea behind Turbolinks is simple: speed up your application by completely replacing page reloading with AJAX requests that replace the `` element. The inner witchcraft that does this work is hidden. As a developer, you can focus on traditional server programming.
Turbolinks is inspired by pjax and has gone through several revisions.
I used to worry about its performance. I was wrong. Acceleration is huge. What convinced me was how I used it in the project, but you can just try the trial version of Basecamp and play around with it. Try creating a project with some elements, and then navigate through them by clicking on sections. This will give you a good idea of ​​how Turbolinks looks.
I do not think that Turbolinks is simply amazing with its novelty (pjax - 8 years). Or its technical sophistication. It amazes me how a simple idea can increase your productivity by an order of magnitude compared to an alternative to a SPA.
Let me highlight some of the problems that it fixes:
- Data exchange. You do not have it. You do not need to serialize JSON, create APIs, or think about data requests that meet customer needs based on performance.
- Initial load. Unlike SPA, this approach stimulates fast loading time (by design). For screen rendering, you can get the data you need directly from the database. And efficiently querying data from relational databases or HTML caching is well solvable.
- Architecture: You do not need a complex architecture to organize your JavaScript code. You just need to focus on the correct architecture of your server application, which you still need to do when using SPA.
MVC on the server, in the version used by Rails and many other frameworks, is much simpler than any of the templates used for the rich GUI architecture: get a request, work with the database to satisfy it, and display the HTML page as an answer.
Finally, the restriction that is always replaced has a remarkable effect: you can focus on the initial rendering of the pages instead of updating certain sections (or updating certain states in the SPA world). In general, he just does everything.
- Duplication code. There is only one view of your application that lives on the server. Your domain model, its rules, application screens, etc. No need to duplicate concepts in the client.
- Fragility. Compared to SPA, JavaScript to work on your pages and its complexity is reduced to a small fraction, and therefore the number of errors. In addition, you can rely on the atomic execution of operations on the server using database transactions, constraints, and validations.
Notice, I’m not talking about identifying problems, but about fixing them. For example, GraphQL or SPA rehydration is the ultimate solution for very complex problems. But what if instead of trying to find a solution, you put yourself in a situation where these problems do not exist? This is a change in approach to the problem. And it took me years to fully appreciate the ability of this approach to solve problems.
Of course, Turbolinks is not a problem-free silver bullet. The biggest problem is that it can break existing JavaScript code:
- Turbolinks comes with its own custom Page Load event, and existing plugins that rely on regular page loads will not work. Today there are better ways to add behavior to the DOM, but outdated widgets will not work if they are not adapted.
- JavaScript code that modifies DOM must be idempotent, because it can be run multiple times. Again, this invalidates many existing javascript.
- The speed is excellent, but it's not quite like a SPA, which can handle some interactions without loading the server. I will talk more about compromises later.
AJAX rendering and SJR responses
Remember when rendering HTML via Ajax was in trend 15 years ago? Guess what? This is still a great tool in your arsenal:
- Getting the HTML fragment from the server and adding it to the DOM feels super fast (100ms fast).
- You can render HTML on the server, allowing you to reuse your views and extract the necessary data directly from the database.
You can see how this approach is felt in Basecamp by opening your profile menu by pressing the top right button:

It opens instantly. From the development side, you don’t need to worry about JSON serialization and the client side. You can simply display this snippet on the server using all the features of Rails.
A similar tool that Rails has been incorporating over the years is server-side JavaScript (SJR) responses. They allow you to respond to Ajax requests (usually form views) with JavaScript, which is executed by the client. It has the same advantages as AJAX rendering HTML fragments: it executes very quickly, you can reuse the code on the server side, and you can directly access the database to create a response.
You can see how this happens if you go to Basecamp and try to create a new todo. After you click Add todo, the server saves todo and responds with a javascript fragment that adds the new todo to the DOM.
I think that many developers today are looking at AJAX rendering and SJR responses with contempt. I remember that too. They are a tool and, as such, may be abused. But when used properly, this is an amazing solution. Let us offer great UX and interactivity at a very low price. Unfortunately, like Turbolinks, they are difficult to assess if you have not fought a SPA.
Stimulus
Stimulus is a JavaScript framework published a few months ago. It does not care about rendering or JavaScript-based state management. Instead, it's just a good, modern way of organizing the javascript you use to add HTML:
- It uses MutationObserver to bind behavior to the DOM, that is, it doesn’t matter how HTML appears on the page. Of course, this works great with Turbolinks.
- It will save you a bunch of template code to bind behavior to the DOM, to bind handlers to events, and to place elements in the specified container.
- It aims to make your HTML code readable and understandable, which is nice if you have ever encountered the problem of finding out what part of JavaScript is on this damned element.
- It encourages state preservation in the DOM. Again, this means that it does not matter how HTML is generated, which is suitable for many scenarios, including Turbolinks.
If you adopt the Rails path, your JavaScript will focus on modifying the HTML code created on the server side and improving the interaction (with a small amount of JavaScript). Stimulus is designed to organize such a code. This is not a SPA system and does not claim to be one.
I have used Stimulus in several projects, and I like it a lot. It eliminates a heap of sample code, it is built on the latest web standards and reads very nicely. And something that I love especially: now this is the standard way to do something that until now had to be solved in each application.
Compromise game
Turbolinks is usually sold as "Get all the benefits of a SPA without any inconvenience." I do not think this is completely true:
- Applications built using modern Rails look fast, but the SPA will still respond faster to interactions that are server independent.
- There are scenarios in which the SPA makes more sense. If you need to offer a high level of interactivity, you need to manage a large number of states, perform complex logic on the client, etc., SPA framework will make your life easier.
Now development is a game of compromise. And in this game:
- Modern Rails allows you to create applications that are fast enough and look great.
- For a huge variety of applications, Rails allows you to implement the same functions with less code and less complexity.
I believe that with Rails you can get 90% of what a SPA offers with 10% effort. As for performance, Rails kills SPA. As for the UX, I think that many developers are making the same mistake as me, assuming that the SPA UX is unsurpassed. This is not true. In fact, as discussed above, you'd better know what you are doing when creating your SPA, or the UX will actually be worse.
Conclusion
I watch how companies massively adopt SPA frameworks, and see countless articles on how to do fancy things in the style of SPA. I think there are many “uses of the wrong tool for the job,” as I firmly believe that the types of applications that justify the use of the SPA are limited.
And I say that they are justified because SPAs are complex. Anyway, I hope I have convinced you of this. I'm not saying that it’s impossible to create great SPA applications, or that modern Rails applications are great by definition, it's just one approach that is very complex and the other is much simpler.
While preparing this article, I stumbled upon this tweet:

It made me laugh, because I would choose the first options if the alternative was not justified. He is also a representative of a kind of thinking of developers who loves complexity and thrives on it, even to the extent that he considers other people crazy with different criteria.
After many years, I realized that complexity is often a choice. But in the world of programming it is surprisingly difficult to choose simplicity. We value complexity so much that accepting simplicity often makes you think differently, which by definition is difficult.
Remember that you can get rid of trouble. If you choose the SPA path, make sure that it is justified and you understand the problems. If you are not sure, experiment with different approaches and see for yourself. Facebook or Google, on their scale, may not have the luxury of making such decisions, but you probably can.
And if you're a Rails developer who left Rails many years ago, I recommend you go back to him. I think you will be delighted.