Who are these people with JS disabled, and why aren’t things working without it? The former are surely a minority, but even so, with NoScript being the 7th most downloaded Firefox Add-on and other browsers and ways of disabling JS existing, these users aren’t negligible. For the latter - I don’t know. Maybe modern web developers are lazy. Maybe they’re overworked, and pressed on time with too low budgets. Maybe supporting noscript is too hard. Maybe they don’t even know JS can be disabled or why anyone would do it.
I don’t claim to have the answer to these questions, but for some time I’ve had an idea on how to accommodate these users in a way that isn’t too much of a burden on the developer. The following is a short exploration of a method to do that, along with a demo showing it off. It’s something I hope to implement for my Wishy.gift, my side-project, at some point in the future, but more importantly, I hope someone can come up with something better.
A basic component of every single one of these web apps, is to render HTML, which usually reflects some state. Frameworks like React are often used in modern web development to help with the rendering and interactivity. Many of these frameworks have ways of handling the state, and many also allows the state to be stored elsewhere, like in Redux - “A Predictable State Container for JS Apps” that employs the event sourcing pattern.
An event-based state container like Redux is very well suited for our purposes because it aligns perfectly with a kind of interaction that works in the browser both with and without JS - Form submissions! Everything put into the redux store should be serializable, and anything we submit in a form is also so - at least with
enctype="application/x-www-form-urlencoded". Although there is no “right” answer to whether or not you should put all your state into Redux, if we do - and it might certainly be more work - we get a centralised store that can run both client- and server-side.
This store can then act as the baseline or single source of truth for the state that our rendered HTML reflects. Given that React (and other) frameworks support server-side rendering, we can - with some cleverness, foresight, and care - create a single React app that works both with and without JS (with a whole lotta loadin’).
First we create helper components that will abstract away the main difference between having and not having JS. For implementing the classic TodoMVC app, I only needed the Button and Form components, the first one being a wrapper around “do this on click”, and the latter being a more general wrapper for actions relying on more input from the user. Both of these helpers work on the same principle of rendering a form containing the entirety of the action - type and payload - as form elements. When the React app is running client side, the submit events will be prevented, and the actions dispatched client side. Without JS, nothing prevents the event, and a request is made towards the server on whatever route we’re currently at.
Server side, in addition to listening on
GET requests on view routes, we also listen to
POST requests. For all
GET requests we return the initial React app that will be rehydrated for any user with JS. We do the same for any
POST request, but we also make the assumption that the only reason anyone would make a
POST request towards any of these routes, is because they’ve submitted a form. With that assumption, we check if there was indeed a body in the request containing a
type, and potentially a
payloadType too. If we find this, we check if we have some state on the current session, and use that to initialize a Redux store. Then we dispatch the submitted action to the store, fetch the resulting state, update the session state, and use the store to render our app.
- We don’t have access to any JS-events - only forms being submitted, which means that no essential interaction should require things like dragging, double clicking, long clicking, etc.
- We get some extra markup that we have to take into consideration when styling, as everything must be wrapped in a
form- we can’t have standalone buttons with
- The server-side session management can probably be done in a more performant way than how it is in the demo, and in production you might want some more sanitization and validation of the submitted actions.
- There will be an endless stream of refreshes for users without JS if a web app is implemented using this method, but in many cases the alternative is no web app at all.
- Implementing this will most definitively increase the server load, and thereby cost.
- This can surely be implemented using other frameworks than React and Redux. Perhaps this might even be something that could find its way into Sapper or Next.js ?
In short, by leveraging the similarities of form events and an event based state container like Redux, we can dispatch and update state on the server side for users without JS enabled, by making all actions go through forms. This is made easier by making use of some helper components to catch, prevent and dispatch the events client side, and makes it so that there’s little - if any - double work that has to be done to reap the benefits. We are limited in terms of what kind of interactions we can make use of for the most essential functions, and have to take some extra precautions with regards to both styling, security and server cost, but that might be preferred over the app not working at all for users without JS.
Thank you for reading this! I would love to hear your thoughts and ideas too. Join the discussion on Hacker News, or feel free to email me at
daniel at the domain this blog is on.