SPAs: where did it all go wrong
Introduction
The speaker begins by discussing the accidental cruise missile alert in Hawaii and how it relates to the challenges of user interface design. They highlight the frustration of content layout shift, where UI elements move unexpectedly after an action is taken.
Multi-page vs. Single-page Applications
The speaker introduces the two main types of web applications: multi-page and single-page applications (SPAs). They explain that multi-page applications load a new page for every action, while SPAs load the entire application upfront and handle navigation within the front end.
Deep Dive into Multi-page Applications
The speaker provides a detailed explanation of how multi-page applications work, emphasizing the role of the browser in handling HTTP requests, rendering HTML, and managing user interactions like clicking links. They explain how browsers handle race conditions and ensure expected behavior for the user.
The Rise of Progressive Enhancement
The speaker discusses the desire to build more interactive web applications, leading to the concept of progressive enhancement. This approach involves enhancing the base HTML document with JavaScript to add dynamic functionality and interactivity, creating "islands of interactivity" within multi-page applications.
The Challenges of Mixing Server-side and Client-side Logic
The speaker highlights the problems that arise from having both server-side and client-side logic for handling things like form validation. This duplication of effort leads to code coupling, making applications harder to maintain and more prone to breakage.
The Emergence of Single-page Applications (SPAs)
The speaker introduces SPAs as a solution to the problems of coupling between server-side and client-side logic. By moving all application logic to the front end, SPAs aimed to simplify development. However, this shift brought its own set of challenges.
Challenges and Drawbacks of SPAs
The speaker discusses the challenges of early SPAs, such as difficulties with client-side routing, search engine crawling, authentication, state management, and code splitting. They argue that SPAs, while enabling rapid experimentation, led to JavaScript fatigue due to the constant churn of new tools and libraries.
The JavaScript Ecosystem Explosion
The speaker presents a visual representation of the JavaScript ecosystem, emphasizing the overwhelming number of tools, libraries, and frameworks that have emerged to solve similar problems in different ways. This explosion of options contributed to the feeling of JavaScript fatigue.
SPAs and the Front-end/Back-end Divide
The speaker delves into the architecture of SPAs, explaining how they typically involve serving a static HTML file that then loads JavaScript to handle all application logic. They highlight the advantages of this approach, such as the ability to use different back-end technologies and leverage existing APIs.
Pros and Cons of Different Back-end Technologies
The speaker compares and contrasts traditional back-end frameworks like ASP.NET, Django, and Ruby on Rails with Node.js. They discuss the strengths and weaknesses of each approach, emphasizing the trade-offs between batteries-included frameworks and the flexibility of Node.js.
Advantages of SPAs for User Experience
The speaker reiterates the advantages of SPAs in terms of user experience, such as hot reloading, rich interactivity, better offline support, real-time updates, and faster front-end iteration. They argue that SPAs have significantly raised the bar for what's possible in web development.
SPAs as a Detour and the Path Forward
The speaker posits that SPAs, while beneficial for experimentation, were ultimately a detour in web development. They suggest that the ideal path forward involves leveraging the web platform more effectively and simplifying the development landscape.
Introducing React Server Components
The speaker introduces React Server Components as a potential solution to the challenges of SPAs. They provide a live demo of a Next.js application built with React Server Components, demonstrating how this approach enables server-side rendering while maintaining interactivity.
Benefits of React Server Components
The speaker explains the benefits of React Server Components, such as the ability to control how much JavaScript is sent to the client, improved performance, and a more seamless development experience. They highlight how this approach encourages progressive enhancement and reduces reliance on large JavaScript bundles.
The Problem of Latency and the Arms Race for Solutions
The speaker discusses the persistent problem of network latency in web applications and how it impacts user experience. They describe the ongoing "arms race" in the JavaScript ecosystem to develop solutions that mitigate latency and improve responsiveness.
Bridging the Gap: Back-end Frameworks Reaching Towards the Front-end
The speaker examines various approaches taken by back-end frameworks to bridge the gap with front-end technologies. They discuss solutions like Hotwire, HTMX, Phoenix LiveView, InertiaJS, and Astro, explaining how these frameworks aim to enhance HTML and enable partial DOM updates for a more interactive experience.
Bridging the Gap: Front-end Frameworks Reaching Towards the Back-end
The speaker shifts focus to front-end frameworks and their efforts to bridge the gap with back-end technologies. They discuss how frameworks like Remix and Next.js are evolving into full-stack web frameworks, providing a more integrated development experience across the front end and back end.
React Server Components as a Unifying Solution
The speaker revisits React Server Components, arguing that they offer a compelling solution to the challenges of bridging the gap between front-end and back-end development. They emphasize how this approach allows developers to write code in a single language (JavaScript) and control its execution environment (server or client).
Remix and the Future of Full-stack JavaScript
The speaker expresses their enthusiasm for the Remix framework, highlighting its innovative approach to building full-stack web applications. They discuss Remix's integration with React Router and how it enables developers to progressively enhance existing SPAs with server-side rendering capabilities.
Atwood's Law and the Convergence of Web Development
The speaker cites Atwood's Law, which states that "any application that can be written in JavaScript will eventually be written in JavaScript." They believe that this law is coming to fruition, predicting a future where front-end technologies like React will play a more central role in web development, potentially replacing traditional back-end templating languages.
The Speaker's Preferred Approach and Conclusion
The speaker concludes by reiterating their preference for Remix and its approach to full-stack web development. They encourage the audience to explore different solutions and engage in further discussion about the future of web development.
Does anyone remember this where they accidentally sent out a cruise missile alert to the whole of Hawaii and everyone panicked?
The post mortem was someone clicked the wrong button and this GIF is great because you, you can But this happens all the time, right?
The one that pisses me off is on the iPhone, you go to call someone and then home and mobile switch.
Anything that has like content layout shift, that things move when there's a action you need to take, is absolutely infuriating.
But it's not all bad.
There's this really good article on, in defense of single page applications.
Oh we've got two main types of applications.
We've got the multi page web application, and then you've got the single page web application.
Your multi page, every time you click or perform an action, you get a whole new page.
That's the same for submitting a form, it will always redirect to a new page, even if it's the same page.
Single page applications load up, and then the entire application is in the front end.
I'm going to really cover the how and why we moved away from multi page applications to single page applications.
Let's start with multi page applications.
Most people have a really good concept of this, but I'm going to run it through quickly.
You go into a website, a whole bunch of stuff happens, we call the server.
The server responds with HTML.
The browser knows how to render that.
It also knows how to render specific tags that have semantic meaning, like links.
Links are a form of hypermedia that tell the browser that there is a link available that goes to another page.
So if I click that link, the browser knows to update the URL.
Make a new HTTP request, get that content and render it.
But that's not all the browser does.
If I've got two links on the same page, and I click the first one, and the browser makes that HTTP request, if I then go click the second link, The browser will cancel the first in flight request, make, update the URL, and make a new request returning the HTML.
It's handling race conditions and these, making sure that what you expect as a user, if you click on one link and then it's oh, no, I want the other link, that suddenly it doesn't load the first link and those unexpected behaviors.
In single page appland, you have to do all of this yourself, and that's where the challenges start.
Also, things are really great.
Building on the server was super easy, everything was stateless, and the browsers handled all of these hard problems for us.
The thing is, we didn't just want to build documents, we wanted to build applications, and really leverage that web platform, right?
So we started doing what's called progressive enhancement, where the base document would come along, and then we enhance it using JavaScript to make it more functional, more dynamic, and more interesting.
What that kind of looks like is you've got a multi page application, and then you enhance it with a kind of like client side tool, application.
It's not a single page application at the, it, at the moment because you'll still navigate between things, but you've got like islands of interactivity.
So what do JavaScript programmers do?
They add more JavaScript.
They make a good thing After that, what we ended up creating is two applications, not just one anymore.
We've got the server side application, which is using a language like, a framework like ASP.
NET, or Django, or Laravel, or Ruby on Rails, that are really good at rendering templates.
They have all this cool stuff around form validation logic.
Heaps of batteries included, and they render HTML on the server, so it owns the templates.
Our second application is built in JavaScript.
It also has form validation logic, which you have to implement again in a different language, and it updates The HTML created by the first application.
The challenge with this is, now you've got partial templates in your JavaScript application that have to align with what your server application is working.
Otherwise your styles are broken, things break.
This type of coupling is really bad because it breaks often and it's super hard to maintain.
So some smart people are like, ah, let's remove the server.
And this is where single page applications came into being.
It's also where JavaScript fatigue started because, as I said earlier, we took a step away from the platform and, we'll do it all in the front end.
So we had to re solve all of these problems that the browser was giving us.
Things like client side routing, single, search engine crawling.
Original SPAs, they just had one URL.
They didn't, we didn't have HTML5 push, APIs to update the URL.
We had to use things like Hashbang, if anyone remembers those awful things.
Auth became way harder because now it's super, super hard to secure a client side application.
And the list goes on.
State management.
Codesplitting, scroll restoration, data fetching and caching, navigation progress indications so users know that something is happening, form management, and also just general maintainability.
Up to this point, JavaScript wasn't really a real language that you would use for building large applications.
So there were some real issues with building scalable JavaScript applications.
A bunch of these things still remain now, and if you're building a full web application that doesn't need to be controlled by, or searched by Google or internal apps that don't have kind of the same requirements, you can get away with it, but there should be something better that allows us to use the platform.
Because when we took a step away from the platform, as I said, we've got all of these problems we need to solve again.
There's a really good upside of stepping away from the platform.
I've heard a theme today around the platform is great and over the last couple of years it's got some amazing capabilities built into it.
We've also heard horror stories around, prototype poisoning and really mistakes that have made it into the platform that have made it really, hard to evolve over time.
So the Standards Committee is really defensive about putting things into the platform that have to last forever.
So single page applications were a real necessary evil, I'll call them, because they enabled us to rapidly experiment.
And that's what's caused this JavaScript fatigue.
We've created so many packages, that only have a lifetime of one or two years because we realized they were the wrong solutions to the problem we were having.
Yep.
Built up.
This is a very small side slide.
Some of the other speakers had much more impressive, JavaScript ecosystem blowout slides.
But, you get the idea that we got all of this massive amount of tooling and libraries and frameworks solving similar problems in different ways.
This experimentation was great.
It enabled us to do some really cool things and break through the way we build JavaScript applications.
So going back to our front end and back end concepts.
When HTTP call, in the single packet page applications, you're generally serving up a static file.
That static file has no content in it, just links off to JavaScript files.
Then we expand the scope of what our front end is doing with this single page application.
It boots up, it goes and calls one of our back end frameworks.
Our back ends don't have to be written in Node.
We can continue building them in whatever the back end team is comfortable with.
They don't have to adopt all of this new technology.
So our SPAs can call into the backend APIs that could be legacy, it could be something that's been built for 20 plus years, and continue leveraging that.
We don't have to rebuild all of our business.
There's heaps of really good options in that space outside the JavaScript community.
It just talks over HTTP, returns JSON, and then the responsibility of the single page application is to update the HTML.
Each of these things have different pros and cons.
The backend, stateless, we talked about that makes it really easy for us to maintain.
Stateful applications are great.
A lot of these backend frameworks have batteries included.
They have the ORM, they have auth built in, they have background workers and jobs.
They have all of this stuff that you need when you're building any sort of, application of scale.
And they also have strong conventions.
So if you go to a Rails app at one company and you go to a Rails app at another company, they look very similar because Rails comes with all of these conventions and they tend to be pretty fast to develop.
Node.
Js is a Because of this explosion of the ecosystem, and now we've got different runtimes and, more fragmentation, there's no single back end framework.
What we, and because of that, we lack a single framework that has all the batteries included.
There are attempts like RedwoodJS and Blitz and a couple of others, but they are their own communities in themselves.
They don't represent the JavaScript community as a whole.
But what Node does have, is it has amazing collection of all of these, libraries on NPM.
And if you're building backends in Node, you probably have your own little suite of tools that you bring together to build your own framework.
SPA, they're stateful, brought us things like Hot Reload.
Rich user experiences, better offline support, real time updates, and just faster front end iteration speed.
That's why they've gained so much popularity, is because we can build really rich, compelling interactive user experiences on our front end apps.
Much more than we could do in this back end document model.
But ultimately I think SPAs are really just a detour.
They enabled us to do that rapid experimentation and figure out how we want, what's the bar we want to be able to achieve in the front end development space.
We now have a really good idea of what the web ecosystem is capable of.
Apps like Linear show us that you can build amazing user experiences using the web platform.
And that's where our bar is set.
Now we just need the tooling to come together, put in some conventions, simplify the landscape, so it's easier for all of us to build those great user experiences using something ideally leveraging the platform a little bit better than SPAs do.
But if there are a detour What were they a detour to?
I'm going to give you a quick demo of React server components because I find them really, interesting.
This talk, I'm not trying to make it super specific to individual technology, but I do think React server components, if you're not familiar with them, are a very interesting, and compelling solution to the problem, which we'll talk about in a minute.
Here I've got my little, NextJS application, which is the agenda page.
You'll notice it's running on local host.
It is just a NextJS application because NextJS is the, currently the only one that supports React server components.
And if we have a look and step through, we'll actually notice that this application is a server rendered only application.
There's no interactivity, and I'm running it in development mode, and NextJS also ships a bunch of JavaScript even if your app isn't there.
But these views don't go to the client.
They are just rendered on the server.
It is an entirely server side rendered application.
What I want to do is make it a little bit more interactive.
So if we have a look here, you'll notice that all of these talk descriptions are cut off.
They only contain the first paragraph.
What I want to do is allow you to see the entire talk description in the main agenda page.
That's the goal.
I'm going to jump into the schedule component and touch on something quickly, from Jess's talk.
React Server components and React 19 start bringing some really nice things, I can render my title tag inside, you'll notice here that, It's just Create Next App.
I can render a title tag in any component in my render tree, save that, and jump back, refresh the page, and you'll see my title updates.
So React now has an understanding of the various semantic components.
I can render a title and it will hoist that into the head of the page to update things.
If I go further down, I've got my Session Slot component.
So I'm gonna make it expandable.
Here, with my, useState hook, if you're not familiar with React, I'm not going to go into it, apologies, but you'll get the idea.
And I'm going to put a button, which, before, Jess's talk, I just had an onClick handler on this abstract div here, so I quickly changed that to a button using Copilot earlier, because accessibility is good.
So if we save that and then I go back, you'll notice that it was broken and saying fail to compile.
You are using a component that needs useState and it only works in client components.
I said before that this is a server side application.
All of that stuff only runs on the server.
So what React server components allows us to do is jump up to the top of the file and add a directive similar to when they I did use strict years ago, if people remember, and I can go use client.
This directive instructs the bundler, in this case it's Webpack, but Vite will have support when Remix ships all of that stuff.
That marks this module as something that will go across the network into the client and is now available as a server side rendered component, but also is now in my client bundle, so it is an interactive application.
Now if I jump back, I've got this little show more, and I can click the button and it expands.
What's really subtle about this is, in terms of code splitting and whatnot, none of my application JavaScript went to the client.
After I added that directive, only that class is emitted in JavaScript and goes to the client.
So now I've got a lever which controls how much JavaScript I'm sending to the client, and we've got that idea of progressive enhancement built in to the framework, which I think is really cool.
So that's React server components, and it that idea serves as a foundation for what I'm going to talk about next.
So we were talking about the front end calling the back end.
What happens there is it has to go over the network.
We've talked about the edge and latency to get back into the back end.
And that latency is a killer.
You cannot get away from it.
And that's where you often create poor user experiences, where You make a change to the application, don't realize something's happening, do it again, and then you get two items added, or various other things.
If everything was immediately added, and immediate user feedback, and the network wasn't a problem, These issues wouldn't exist.
So there's a bit of an arms race in the JavaScript ecosystem, and this is the problem pretty much all of the frameworks are trying to solve at the moment.
There's so many different ways and approaches, and it's not even limited to JavaScript.
All of these back end frameworks are trying to solve the similar problem around JavaScript.
Moving documents and creating that interactivity.
So I've got all of my backend frameworks over here.
What they're doing is extending over the wire, using, like in Rails, you've got Hotwire.
Hotwire works the experience of GitHub, where if you merge a pull request, it updates parts of the DOM manually.
HTMX is a similar thing where you can attribute buttons or forms, and instead of replacing the whole page, you can do partial updates.
It does a bit more, but that's the idea, is enhancing HTML to allow partial updates.
Yeah.
And you've got Phoenix Live View, which I've never really used, but it streams a lot of the responses.
So each of these things are these back end frameworks trying to bridge the gap to the front end and create these rich experiences.
The challenge with this is it's another, in my opinion, is another abstraction.
And you often have problems managing state.
So in GitHub, has anyone had the issue where you merge a pull request and bits of the UI Update, but still the label is saying open, and the count isn't correct and you refresh and then all of those UI inconsistencies disappear.
That is a symptom of all of your templates and logic for rendering existing on the server and trying to do partial DOM updates.
But it is a really great way to just add some interactivity.
You've got others which are like, creating islands of interactivity using, InertiaJS and the Laravel community allows you to render React components inside your Laravel views.
What they do is they spin up NodeJS on the server, do a server side render, take the markup across to the client, and then hydrate that React component on the front end.
Astro is all written in JavaScript, so they don't have to run a NodeJS process inside PHP, but the idea is the same thing.
This also has limitations, because the back end owns routing, and so there's always going to be limitations there around, server side rendering and the next page, because you have to make that round trip to the server.
On the other side, this is the SPA model we were talking about.
We have all these, Awesome SPA libraries like React Router, libraries like React Query that control like data loading, caching, invalidation, all of those nice things.
What's happening is a lot of these full stack web frameworks that we've seen are building on top of those fundamentals and they're bridging the gap backwards to the server.
So that's what Remix and Next has been doing for quite some time is trying to bridge that gap from single page applications to a full stack web framework and when I'm talking about web full stack I'm simply talking about the stack spans both the front end and the back end and crosses that bridge between the network.
As I showed React Server Components is a really interesting solution to this problem, because it allows you to control how much of your templates live on the client and how much are on the server.
The previous model that we saw with Inertia, where let's say we were using Laravel to build our agenda site, and then we wanted to add that, I would have to rewrite that page into React, so I could add that component because my back end is in a different templating language.
So that's what these JavaScript frameworks and a front end first going to the back is giving us means we are writing in the same language and then React server components allow us to pull a lever and control how much of that is on the client and the server.
Ideally preventing us from these giant monolithic JavaScript bundles, and it also encourages us to use the platform.
So Remix and React Server Components and Server Actions uses form semantics and a lot of kind of the fundamental building blocks that we've been used to rather than everything having to be hooks and dynamically loaded.
So it's giving us a path to simplicity.
Which is where I'm really excited.
But once we get to the server, we can still use HTTP to talk to our back end APIs, and have our different teams, and that can be where we have these scalable back ends that talk to the database, various services, and we just use kind of the back end for front end model.
Once we solve this bridging the gap, I think that the JavaScript and Node ecosystem is going to get a lot richer.
We're seeing this already because of the amount of investment in these JavaScript runtimes to make the server components of JavaScript run way quicker.
I think we'll have something that brings all of this together really, well and start having these, cohesive back end experiences.
Referring back to Atwood's Law, which I think he was, Jeff Atwood, built Stack Overflow, and he said any application that can be written in JavaScript eventually will be written in JavaScript, and I think we're seeing that come to fruition now.
I think over the next couple of years we'll find your Laravels and your Rails and whatnot.
The usage of rendering HTML templates in those languages is going to reduce massively, and templates are going to be written in a front end framework like React with server components and bridged back to the server, and then we'll use these back end frameworks and languages that are really good for building APIs and scalable back ends.
My preference for doing this, I've mentioned a couple of times, I'm really enjoying Remix and they've got this interesting new model now, where Remix is built on React Router, and they're bringing React Server components and all of this stuff to React Router.
So all of these single page applications that have been built using React Router, they want to enable you to start bridging the gap and progressively enhancing your application with server side rendering, and the way they're doing that is just a Vite plugin for React Router, and that gives you all the features of Remix.
So I think that's really good.
But if you're in another ecosystem, all of these options are trying to solve a similar problem in different ways.
There's a heap of investment that I think is really, interesting.
And I don't have time to go into any more detail on this, but if you want to chat to me about your thoughts, I hope this has been a productive conversation.
Thank you.