2025-08-19 11:39:00
bejofo.com
Client-side rendering has become the dominant paradigm for building web apps. But frameworks like React can lead to major JavaScript execution bottlenecks during page load. For many web developers there’s a clear solution for this: Ship less JavaScript to the browser.
Whether websites have to work without any JavaScript at all is a question almost as old as the web itself. By now, the answer is clear: No, they don’t. It’s firmly established that websites should be more than just structured and styled text. JavaScript execution is an integral part of (almost) every browser.
Still, there are benefits to shipping less JavaScript to the Browser. So I wanted to do a little case study and see how far I could take this with my online board gaming website. The answer: I could take it all the way.
My Goal: Fully optional JavaScript
The vast majority of users will experience my (or any other) website with full JavaScript functionality, including fast client side rendering and websocket connections. It is not an option to degrade the experience of these users for a little experiment. So my goal was to make JavaScript execution optional without giving up any of UX benefits for users who have JavaScript enabled.
These days one would call this “Progressive Enhancement,” meaning that just the HTML works on its own and CSS and JavaScript are not mandatory, but provide enhancements like fancy styling and a higher degree of interactivity. And while my gaming site is not usable without CSS, the principle stands.
The site is fairly complex: It has online multiplayer games with real-time updates and a login functionality to save running games, so it’s a good proving ground for this idea.
Look Mom, No JavaScript!
So, how did I do it? Let me walk you through the key components.
Server-Side Rendering
The basis for all of this is that the whole website and game can be rendered on demand on the server. But I can’t rely on Server-Side Rendering alone, or else my page would be transported back to the age of Java Server Pages or PHP Templates. It works well when JavaScript is disabled, but for all other users, I don’t want to give up the quick and efficient interaction after the initial page load that client-side rendering provides.
This is exactly what modern meta-frameworks like Next.js or SvelteKit promise: The combination of initially loading a page that was rendered on the server and (if JavaScript is available) switching to client-side rendering after that. In my case, I am using SvelteKit. For the site to be completely usable without JavaScript, there were some more steps necessary, that I’ll describe in the following paragraphs.
Interactive UI elements: Dropdown Menus
When searching for freely styled dropdown menus that can be implemented without the use of JavaScript, you can still find plenty of hacks like using a hidden checkbox and the :checked
-pseudo selector to style the open and closed states. However, those are no longer required. The HTML standard provides us with the
elements that can be used for exactly this purpose. bejofo.com uses these for the language menu in the top right corner, for example.
Interactive UI elements: Modals
One of the biggest limitations when working without JavaScript is that the ephemeral UI state is limited to a small set of elements provided by the browser, like:
- Is the checkbox checked?
- Is the
element open?
But any other UI state needs to be stored in a cookie, in a URL parameter or on the server, which makes simple things like opening a custom dialog more involved. For the modal dialogs, I decided to go the URL parameter route: Since the login-modal can be opened on almost any page, the “login” parameter can be appended to any URL to open it (like this).
Submitting game moves
Making a move in the game results in a state change on the server, so it needs to be a POST request. This way, the browser can prevent accidental resubmissions. The only way to make a POST request without JavaScript is to use a element. This means all interactive fields on the game board need to be
or tags. For the JavaScript-enabled case, Svelte will intercept the form submission and trigger a websocket-message instead. For sending game moves the interception callback looks something like this, for example:
function enhanceCB({ formData, cancel }) {
let move = formData.get('move')
makeMove(move) // Sends move via websocket
cancel() // Prevents the default POST request
}
...
form action="?/makeMove" use:enhance={enhanceCB}>
Realtime updates (sort of)
So far so good, but how the heck can I replace the live updates that are provided by the websocket? This is impossible, right? Well, no, it turns out we can use the auto-refresh meta-tag for this: . It simply tells the browser to refresh the whole website after a predetermined number of seconds. This feature has been used by live-ticker websites for ages. This of course means there can be a relatively long delay before you can see your opponents move. But for a board game this is sufficient, in my opinion.
Animations
In theory, I could try to replace the JavaScript-powered animations with pure CSS animations, but I decided that’s not worth it for me. Also, I’m not sure how smooth a full page refresh followed by a bunch of CSS animations would look. Still, even if I only animate things for JavaScript users, I need to ensure animations always transition to the final state that the server has already rendered in the HTML. Otherwise, if JavaScript animates from an old state to a new one, a user without JavaScript will be stuck seeing the old state forever.
What I Learned
Okay so what do I take away from all of this? What are the benefits and drawbacks? Why should you or should you not implement this for your website?
Pros:
- The main benefit of a setup that is capable of both server side rendering and client side rendering is the much faster initial page load. We send a complete HTML document to the client that is ready to be displayed. This means no JavaScript needs to be executed and no additional network requests are required before the page is visible to the user. After that, the page is made interactive (“hydrated”) and efficient client side rendering can take over for subsequent navigation events. Using something like SvelteKit can be worth it just for this performance upgrade.
- The whole procedure made my site more resilient: When all state is stored either in a cookie, in the URL or on the server, no state can be lost in case of a crash or if the browser tab is unloaded from memory. This can be especially useful on ressource constrained devices like phones.
- The site has become more usable for JavaScript users: Initially, I did not bother to change the navigation history when the login modal was opened. But since that is required for the JavaScript-free functionality, it is now possible to close the modal by navigating back. The change also enabled me to link directly to the login form, which wasn’t possible before.
- And last but not least, my HTML has become more semantically correct, by using
Elements for changing requests and
elements for dropdown menus. I don’t know if that really improves anything in practice, but it gives me that warm, semantically correct feeling.
Cons:
- All that server side rendering uses resources on the… server side. Especially if the page is fully refreshed in regular intervals like it is when the auto-refresh meta-tag is active. This leads to increased server resource usage.
- All actions that are usually triggered through websockets also need to be routed through form actions when JavaScript is disabled. This makes a lot of additional boilerplate code necessary.
- In general, this approach increases the complexity of the software. There are some additional code-paths and a lot of additional test cases that can break.
- But the biggest drawback is: Having to keep the non-JavaScript case in mind slows down feature development and holds me back when designing new features.
Conclusion
In general, this has been a fun experiment that improved the overall quality and performance of my software. I’m pretty sure that would be the case for most sites that are not just static content already. But for this to work, I had to jump through more hoops than I initially expected. The required effort is simply too big for an audience that probably exists more in theory than in practice. So overall, I can’t really recommend this approach. Still, it’s good to keep in mind that things do not always go smoothly on the client side: Devices will be slow, and browsers (or your own code!) will crash. Not relying on JavaScript too much can help here.
I will monitor if anyone actually uses my website without JavaScript, but I won’t hold my breath. And at some point, I will probably rip out all that additional code, to streamline my code base.
But until then, you can try it out yourself.
Keep your files stored safely and securely with the SanDisk 2TB Extreme Portable SSD. With over 69,505 ratings and an impressive 4.6 out of 5 stars, this product has been purchased over 8K+ times in the past month. At only $129.99, this Amazon’s Choice product is a must-have for secure file storage.
Help keep private content private with the included password protection featuring 256-bit AES hardware encryption. Order now for just $129.99 on Amazon!
Help Power Techcratic’s Future – Scan To Support
If Techcratic’s content and insights have helped you, consider giving back by supporting the platform with crypto. Every contribution makes a difference, whether it’s for high-quality content, server maintenance, or future updates. Techcratic is constantly evolving, and your support helps drive that progress.
As a solo operator who wears all the hats, creating content, managing the tech, and running the site, your support allows me to stay focused on delivering valuable resources. Your support keeps everything running smoothly and enables me to continue creating the content you love. I’m deeply grateful for your support, it truly means the world to me! Thank you!
BITCOIN bc1qlszw7elx2qahjwvaryh0tkgg8y68enw30gpvge Scan the QR code with your crypto wallet app |
DOGECOIN D64GwvvYQxFXYyan3oQCrmWfidf6T3JpBA Scan the QR code with your crypto wallet app |
ETHEREUM 0xe9BC980DF3d985730dA827996B43E4A62CCBAA7a Scan the QR code with your crypto wallet app |
Please read the Privacy and Security Disclaimer on how Techcratic handles your support.
Disclaimer: As an Amazon Associate, Techcratic may earn from qualifying purchases.