{"slug": "building-textlog-without-javascript", "title": "Building textlog without JavaScript", "summary": "A developer built textlog, a web app that runs React on the server but sends only plain HTML and CSS to the browser, with no JavaScript. The constraint of no client-side JavaScript influenced design decisions, leading to simpler interactions using native HTML features and meaningful URLs.", "body_md": "# Building textlog without JavaScript\n\n> ai;dr — This post has been enhanced with AI.\n\nWhen I started building [textlog](https://textlog.cc), I gave myself a constraint: no JavaScript in the browser.\n\nNot “minimal JavaScript”, or “JavaScript only where necessary” which inevitably turns into a bundle, a router and hydration a few weeks later. I wanted the normal experience of using the site to be plain HTML and CSS.\n\nThe slightly funny thing is that textlog is still a React app. React just never reaches the browser.\n\nA request comes in, we get whatever data we need, React renders the page on the server and we send the resulting HTML. There is no hydration afterwards and no second application waking up in the browser. The HTML arrives and that's the page.\n\nAt first this mostly felt like a technical experiment. I wanted to see how much of a modern web app I could actually build this way. But after working on it for a while, the constraint started influencing almost every decision I made about textlog, including decisions that had very little to do with JavaScript.\n\nThe first thing I noticed was how much HTML already does.\n\nWhen you've been building client-side applications for years, your instinct when you need an interaction is to write some state and an event handler. Without that option, you have to look again at what the browser can already do.\n\nLinks navigate. Forms change things. URLs hold state. Radio buttons are pretty good at being radio buttons. `<details>` opens and closes. Inputs validate themselves. CSS can respond to more state than I remembered.\n\nA poll, for example, doesn't really need JavaScript. It's a form with some radio buttons. You vote, the server receives the vote, and the next page contains the results.\n\nFiltering a feed doesn't need client state either. The filter can just be part of the URL. Pagination is links. Following someone is a form submission. Editing a post is another form.\n\nThere were many moments where my first thought was “this is going to be difficult without JavaScript”, followed a few minutes later by realizing that the boring HTML solution was completely fine.\n\nAnd boring became a feature.\n\nThe harder part was changing how I thought about interactions.\n\nIf you approach the problem as “how can I recreate this SPA interaction without JavaScript?” you usually end up fighting the browser. The more useful question is “what would the web version of this interaction look like?”\n\nSomething doesn't necessarily have to update instantly in place. You can submit a form, redirect, and render the new state. A filter doesn't have to live invisibly inside a component. It can live in the URL. A piece of UI doesn't always need to appear in a modal. Sometimes it can be a page.\n\nThis sounds almost embarrassingly obvious when written down. It's basically how websites worked before we decided every website should behave like a desktop application.\n\nThere are also some nice consequences.\n\nURLs become meaningful again. You can copy them. You can bookmark them. You can open things in another tab. Back and forward work because we're not trying to replace them with our own idea of navigation. Refreshing the page isn't a dangerous operation that might destroy some mysterious pile of client state.\n\nThe server side also becomes pleasantly straightforward. The server already knows who you are, what you can see, what you've read and what the database contains. It doesn't have to expose all of that through an API so another application running in the browser can reconstruct the same state.\n\nIt gets the data, renders the page and sends it.\n\nWhen something changes, the server changes it and renders the new page.\n\nThere are fewer moving pieces and, maybe more importantly, fewer opportunities for those pieces to disagree with each other.\n\nPerformance also became less of a problem simply because there is less happening. There isn't an application bundle to download and parse, there is nothing to hydrate, and there isn't a framework runtime waiting around after the page has loaded. Textlog is mostly text, so the pages themselves are tiny. The browser gets HTML and starts rendering it.\n\nOf course, server round trips still exist. I'm not claiming this is somehow faster than a local client interaction. But I like the trade. Instead of building a complicated client and then spending time making it feel fast, we removed most of the client.\n\nThere are limits, and I've run into them.\n\nSome browser features really do require JavaScript. Push notifications are one example. You need JavaScript to register a service worker and ask the browser for permission.\n\nFor those cases I decided not to turn the constraint into a religion. Textlog has a dedicated place where JavaScript can exist when a browser API genuinely requires it. Enabling notifications can happen on a page that loads JavaScript. That doesn't mean the timeline suddenly needs JavaScript too.\n\nThat distinction has become more important to me than “zero JavaScript” as a slogan.\n\nJavaScript is allowed. It just has to have a reason to be there.\n\nSomething else happened that I didn't expect: the technical constraint started shaping the personality of textlog.\n\nTextlog is supposed to be a quiet place. It's text only. There are no likes or reactions. I'm deliberately trying not to build another interface that constantly asks for your attention.\n\nWhen adding an interaction isn't completely free, you start questioning interactions more.\n\nDo we need this popup? Does this number need to update live? Does this menu need to float over the page? Does this action need an animation? Does the user need to be notified about this immediately?\n\nQuite often the answer is no.\n\nSo the lack of JavaScript stopped feeling like something I had to work around and started feeling like part of the design.\n\nI definitely wouldn't build everything this way. There are applications where a rich client is obviously useful and sometimes essential. But building textlog has made me think we've become a little too quick to put an application runtime in the browser.\n\nA lot of what we build is still people reading things, following links, filling in forms and changing some state on a server.\n\nHTML happens to be extremely good at that.\n\nTextlog is React on the server, HTML in the browser, forms when something needs to change, URLs when something needs to be remembered, and a little JavaScript in the few places where the browser actually demands it.\n\nNone of this is particularly new.\n\nThat's probably my favorite thing about it.\n\nCurious to see how it looks like? [Head over to textlog](https://textlog.cc) and see for yourself!\n\n", "url": "https://wpnews.pro/news/building-textlog-without-javascript", "canonical_source": "https://gist.github.com/stagas/09ad937b493bf8cd3285917279de2488", "published_at": "2026-08-26 20:02:39+00:00", "updated_at": "2026-08-26 20:19:24.077451+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["textlog"], "alternates": {"html": "https://wpnews.pro/news/building-textlog-without-javascript", "markdown": "https://wpnews.pro/news/building-textlog-without-javascript.md", "text": "https://wpnews.pro/news/building-textlog-without-javascript.txt", "jsonld": "https://wpnews.pro/news/building-textlog-without-javascript.jsonld"}}