I have a case to demonstrate my view on modern hybrid SSR vs Client-Side web applications. The initial timeline is a bit murky, because there was some instability in the development team, but a team of 5 developers built a Single Page Application (SPA) with React and a Node.js GraphQL backend API in about a year and a half. The timeline stretches further, with some gaps when development was paused, but that is approximately how long it took.

This SPA and API combination replaced an existing Ruby on Rails (RoR) application, a fairly large one, and even migrated to a new database. Performance-wise, both applications are pretty close. The RoR application used MySQL, which is slightly faster itself, but the database suffered from poor indexing and a lot of the queries being used were very inefficient. The SPA and API uses GraphQL and PostgreSQL, which is a little slower by default than MySQL, but is boosted by much better indexing and query optimization.

The SPA actually performed better in some areas, due to GraphQL caching, but tended to suffer some size issues on some clients. It was just a giant code base for an SPA, but to its credit, was performant. The SPA launched in May 2023 and it became evident immediately that client-side could not be the only solution. Search indexing dropped considerably and the web site lost any social capabilities that required useful meta tags. To reduce the size of the code base, a lot of component re-use was going on and this created a lot of headaches fixing bugs and adding new features, which often affected multiple pages and components. A lot of context instances were also being used, which further complicated things like content filters and managing query state or content updates.

Browser navigation using Back or after using pagination was also a burden, often breaking. A lot of it was initial design choices, which could have been partially mitigated early on, but most of those choices were to reduce re-renders and speed up initial renders. There was a lot of give and very little take. The development team was also handed some choices, such as the style framework and content layouts. These had issues in individual browsers, mobile resolutions, etc. I thought we had turned the page on browser specific fixes, but I assure you we haven’t.

About a month into our life with our “new” SPA, I began to realize we needed to look at other options. The other developers were not ready to replace this new SPA and start from scratch. I spent my free time in June researching server side options. I looked into some non-Node.js options, with the intent to keep the Node.js API as a backend. I tried to scaffold out the application using Next.js, but it was literally building a new site that looked like the SPA. After about a week into Next.js, I had maybe 5% of the site functional, it didn’t work with our API sessions yet, and it felt painfully slow in comparison.

I had almost given up and begun building my own SSR framework, when I came across Remix. It’s the end of June 2023 and I still hadn’t found anything that could easily be remodeled to function as our SPA had. I found Remix and decided to just build a web site with it, not rebuild our SPA, but a new, unrelated web site. It quickly proved to be really simple and really, well, just React. I spent a little less than a week learning how Remix worked and everything made me fairly confident we could merge our SPA into Remix.

I’m going to back up a bit here, because there is something special about our API. Typically, a Remix site would likely act as the backend too. It just makes sense. The problem, for us, is our API does a lot of work on it’s own. We are heavily dependent on GraphQL, because we have built a special GraphQL parser that does a vast amount of work for us. We basically build out the GraphQL schema (we use Code-First schema), add some resolver logic for filters, sorting, etc, and then point it at our database schema. Our parser does everything else. We can build out new API features in a very short amount of time, so our API is pretty much invaluable, there’s nothing else like (at least available to the public).

So the first requirement is the frontend has to use GraphQL, sometimes on the server and sometimes on the client. I bundled our API server with Remix and Remix is served from the API’s Node.js server, separate, but also together. Remix gets it’s session from the API, through context, and the API can handle the cookie from Remix. This was day 1 of the effort to convert our SPA into Remix, around July 1, 2023. I replaced about a quarter of our routes from our Application component with Remix routes, pointing to the original page components from the SPA. I moved first view data queries into loaders. This was around July 7, 2023.

The middle of July, I had to take a break from Remix and build some new site features into the SPA, but I’d discussed what I had done with my boss and he gave me the go ahead to pursue Remix, when I had time. Other features were also being worked on in the SPA as well. Toward the end of July, I pushed everything I had into a repository for the other developers to investigate. We only have 3 developers now, 1 of them is mostly part time. The 2 full time developers jumped in head first and we spent most of August working on the Remix version. On September 1, 2023, we determined functional parity between the SPA and the Remix version, with Remix having fixed many of the deficiencies of the SPA. On September 5, 2023 we replaced the live API server with the server running both the API and Remix, as a test (in production, shhh) and still supporting the SPA.

Today is September 7, 2023 and we have added several new features that surpass the SPA. Consider that timelime, compared to building the SPA itself. It wasn’t copy and paste into Remix, we had to build out all of the routes, fix some issues that aren’t easily portable from browser rendering to server rendering. Redesign all of the deeply nested context issues. Decide where to use nested routes versus unnested, redesign those components as needed. This wasn’t a small task, but basically in 2 months we rebuilt a giant SPA, that took over a year to build, into an SSR application with more features and fewer issues. We also now have a hybrid rendered application. The initial render is server side, then it acts as a normal client side React application, when we want it to.

I’m not a fan of saying there’s 1 way to do something, that’s not a smart way to look at development. I also don’t fully understand why so many people choose Next.js, even over an SPA. Remix can be very simple to build with, it can also scale to work with a very large code base like we are dealing with, even integrated into a Node.js GraphQL server. I can’t imagine it would have taken less than 6 months to do the same with Next.js, if it would even work the same in the end.

I once only built SPAs, in one form or another, I felt like that was the modern web application. From now on, hybrid seems like the right way to do it. SSR and Client-side. Best of both worlds, without the drawbacks of both worlds. Oh, we also now have oEmbed provider capability and our links work in social media…I can’t imagine what all we’ll have in a year.