1. 22

  2. 12

    Possibly relevant: the author is the creator of https://pinafore.social which is the only SPA I can think of that I’ve ever actually enjoyed using.

    1. 8

      In case anyone missed it, HTML-over-the-wire makes an app a SPA:

      In some circles, “SPA” has colloquially come to mean “website with tons of JavaScript,” which brings its own set of detractors, such as folks who just don’t like JavaScript very much. This is not at all what I mean by “SPA”… By my definition, Turbolinks is an SPA framework, even if, as a framework user, you never have to dirty your hands touching any JavaScript. If it has a client-side router, it’s an SPA.

      The author then points out that Github uses Turbolinks (technically, I think it uses something proprietary, but that’s beside the point). Github, which is often held aloft as a shining example of how web apps ought to be written as server-side MPAs, is in fact not because it’s loading new content in place with client-side routing. And if you look in your sources tab, you’ll no shortage of DOM-manipulating JavaScript code as well. (I count 3.2 MB on the landing page).

      Conversely, some SPA frameworks (such as their example of Qwik or others such as Next.js or Nuxt) make it possible to build SPA sites with SSR so they are functionally MPAs on first load. So the lines are being blurred from both directions.

      The drain we seem to keep circling is people’s sentiments about writing JavaScript, which is not the author’s point here or in their previous article. Using a non-JavaScript framework to ship JS without having to look at it does not absolve one from performance considerations or warrant trolling people who work directly in JS.

      1. 4

        I agree with the author about the definition and wasn’t aware of those other inacurate definitions. However, the part about turbolinks is a little bit an empty argument. Turbolinks is just plastering the typical SPA workflow on top of a regular MPA..But the site should work normally with JavaScript disabled, loading URLs normaly on hyperlink clicks.

        I think the whole discussion around SPAs has the wrong focus. People should focus on which possibilities are open by SPAs, rather than doing the same old thing just using more complicated tech.

        I always found turbolinks a stupid idea (sorry for the honesty). Link following is a solved problem with the ideal minimum amount of tech required already well defined. It is useless by design, it doesn’t solve any problem (not even speed), but rather raises the amount of required tech.

        To write a ReST API on the server and call it a day, leaving the UI for someone else with total.separatiin if concerns… That was an invaluable idea and a technological breakthrough.

        My favourite SPA and a perfect example of this, is swagger UI. Just feed the URL to your swagger spec and a GUI will be shown to you. The authors of the UI don’t even know who is using it from their website, because it doesn’t matter, it just works.

      2. 8

        No client-side router, so no need to implement focus management, scroll restoration, etc.

        Don’t forget screen reader accessibility. A client-side router needs to announce (usually through an invisible ARIA live region) when it has navigated to a new page. This announcement can’t entirely replicate the experience that a screen reader user has when real browser navigation happens. So in this area, too, a SPA has to poorly reimplement what’s already built into the browser. For this reason alone, I’ve never shipped a SPA.

        1. 1

          Hmm, I hadn’t really thought about that issue. Thinking about something like Gmail, wouldn’t the ideal audio implementation be something more like a phone tree: “You have 5 new messages. The first message is from Sue Smith, read more? The second message is from Jane Doe, read more?” In general, I think it’s sort of a shame that people aren’t making alternative audio only implementations of webpages. There’s no reason I couldn’t be in a car and say, “Hey Dingus, read Lobsters,” and have it say, “Lobste dot rs. Top story: More Thoughts On SPAs. Read more? Next story: Why I built an alternative audio interface to Lobsters. Read more?” etc. So far though, screen readers are really just helpers for the visual experience, and not experiences in their own right.

          1. 7

            In general, I think it’s sort of a shame that people aren’t making alternative audio only implementations of webpages.

            This is tangential, but I think this sentiment is common enough that it’s worth addressing.

            We disabled minorities, e.g. blind people, are concerned with making sure that we get access to as much information and functionality as possible. We know that most providers aren’t going to design specifically for us; they’re going to design for the majority who have whatever ability we lack, usually without giving it a second thought. So ideally we want to automatically get access to the same applications, documents, etc. that are being provided to everyone else, with a minimum of extra effort specifically for us. Bringing this back to the original topic of discussion, this is why I feel that JavaScript-based navigation (as in SPAs) is a regression for accessibility by default. With real navigation, done by the browser, the application or framework developer doesn’t even have to think about accessibility (for that specific function); the browser and screen reader take care of it. But when JavaScript code takes over that function of the browser, it has to poorly replicate what the browser and screen reader were already doing.

            To finish addressing your comment, an interface designed specifically for audio output sounds nice in principle, but the reality is that even if it gets developed, it probably won’t be kept perfectly in sync with the GUI that everyone else is using. And an audio-specific interface doesn’t even serve all blind people; for example, deafblind people need to use a braille device. An interface specifically optimized for audio is certainly a nice luxury for people who want to do a small number of tasks in a restricted environment, e.g. in the car. But for blind people who should have equal access to everything that sighted people can access, it’s no substitute for grafting a screen reader onto a GUI, as clunky as that is.

            1. 1

              Yeah, I’m not imagining this as a replacement for screen reader technology. It’s just something new that should exist because it would be useful to lots of people. For sighted people, there are tons of scenarios where we can’t use our eyes because we’re busy walking, driving, doing dishes, light childcare, whatever. Just speaking personally, I consume an order of magnitude more podcasts than videos because podcasts are content I can enjoy while doing something else, but TV, movies, etc. require more engagement. Unfortunately, capitalism is always going prioritize the needs of the majority over the needs of minorities, but on the positive side, there are so many scenarios where an audio interface would be helpful, it’s a huge market that if it ever were tapped into, it could throw off benefits for other communities as well.

            2. 2

              The best breakdown of this problem that I’ve read is this article by Marcy Sutton.

              To be fair though, I think there is some work underway to solve this problem in the experimental Navigation API.

          2. 2

            By my definition, Turbolinks is an SPA framework, even if, as a framework user, you never have to dirty your hands touching any JavaScript. If it has a client-side router, it’s an SPA.

            But… turbolinks does not have a client-side router?

            1. 2

              That last sentence implies that an app with a client-side router must be a SPA, but it does not imply the inverse, that all SPAs have a client-side router.

              While Turbolinks doesn’t have a client-side router, I would say it does client-side routing in the sense that it calls history.pushState().

              1. 2

                Well, the previous context in the blog makes it more clear that this isn’t just an “all Athenians are Plato” reading:

                To me, an SPA is simply a “Single-Page App,” i.e. a website with a client-side router, where every navigation stays on the same HTML page rather than loading a new one. That’s it.

                Neither of these things are particularly true of turbolinks (or pjax apps like GitHub): there’s no client side router and while, in the presence of JS some navigation will not trigger a new page load, this is merely a progressive enhancement. Every navigation can and will happily load a different page if turbolinks is disabled or if it just decides it’s not worth trying to restructure the DOM. These are really MPAs that have adopted a partial illusion of SPA-ness as a progressive enhancement optimization (and whether that’s even a speed up anymore is certainly a question).

                While Turbolinks doesn’t have a client-side router, I would say it does client-side routing in the sense that it calls history.pushState().

                Eh, that’s a really big stretch, and not what I think anybody means by “client-side routing”. Lots of old school multi-page jQuery sites also made extensive use of PushState for odd things they were doing, but nobody considers them “client-side routed” or SPAs unless were stretching those terms beyond all normal and useful meaning just to win an argument.

                Anyways, regardless, if the client is ignorant of routing and all of the routing happens server side, and the server responds to user agents asking for different routes with many distinct and fully formed HTML pages corresponding to the requested route because there’s no such thing as multiple client-side “pages” that have no server-side “page” equivalent, calling what that server is emitting a “Single Page Application” seems like a really bad misapplication of terminology, to me.

                1. 3

                  Maybe I’m playing fast and loose with the phrase “client-side router,” but yeah, I basically mean it’s calling history.pushState(). There are always gray zones, though; you’re right. (E.g. an MPA that only uses pushState() in one little place, to pop open a modal or something.)

                  1. 2

                    My current (internal, work) app is definitely in what you’d consider the gray zone. Major sections of the site or anything starting a new workflow are full page loads, but within a common workflow, I use HTMX to replace page sections if available, and pushState() to the URL it would have gone to if JS was disabled. The same goes for things like sorting and paging search results: search result pages get URLs so they can be bookmarked or emailed, and this includes the paging and sorting.

                    I do feel like I’m not really doing client-side routing, though. Every link does in a broad sense the same thing whether it’s HTMX-enhanced or not, using the same server-side routes, which generate a full view or a partial view depending on how they were fetched.

                    (Note: I’m the original author of brutaldon, which also worked this way, using intercooler.js. But I do count Pinafore as the only SPA I’ve ever truly enjoyed.)