Maybe this is more meaningful for javascript developers. It makes little sense to me.
Does this make sense? Like I said, I might be stretching the analogy, but it’s a bit like quoting except at the module system level (and in practice mostly happening at the build time).
I've edited the post to clarify this a bit.
> It makes little sense to me.
You are not alone and I've been coding JS for the past 20 years.
Well, it makes little technical sense to me but it is not difficult to find where it might makes more sense.
I'm not sure. It felt like we were moving towards dumb backends that sync automatically towards frontends that would contain most logic. Things like https://localfirstweb.dev/ or https://electric-sql.com/ felt like the future
Writing more server code (as quoting/react-server-components are suggesting) will increase the surface area where errors can occur. Limiting that to either just the server/client feels like a much saner approach
I think the same argument applies to RSC. For many use cases, it doesn't make sense. Many organizations and projects do not need SEO or server code specifically for their FE. If the organization has committed to an API service in order to support a range of clients then RSC/react server framework is "pure overhead."
As someone who has been building with React for a decade, RSC was the moment where I felt the complexity vastly outweighed the benefit. I'm in a position where I can argue that SPAs are dramatically simpler to implement compared to RSC/nextjs, which I think would be surprising to outsiders who bemoan SPAs as complex.
I find the "preload then rehydrate" data synchronization model simpler to understand and can turn even the slowest APIs into an app that feels instant: https://starfx.bower.sh/learn#data-strategy-preload-then-ref...
I think that's a slightly different kind of message than "servers are unnecessary, peer-to-peer is the future". You can keep servers dumb, but some kinds of product features demand the servers to do the job. And then if you want to squeeze the most out of the server as it relates to your client app, RSC is one way to look at it.
I feel like the design space of making the server as dumb as possible is not sufficiently explored yet. I’m imagining PWAs that work offline by default, hosted on static hosting, talking to CORS-unlocked PKCE-authenticated APIs, storing their state as dumb files in APIs like dropbox, and doing all of the cross-client p2p syncing and merging client-side inside of a service worker.
It wouldn’t work for all categories of software, but so much productivity software ultimately reduces to a per-user file paradigm instead of a central database (outliners, notes, task managers, image editors, …) that I think a lot of complex web apps could be built this way. They wouldn’t work well on low end android phones, but then most of the products from those categories already don’t work well there, when half their logic is still on the server.
And yes, I know, Apple does not play nice with PWAs, but I still think there’s something there that I wish more people would explore.
It's funny, as I could probably credit Dan with the reason I mostly spend time with Clojure and ClojureScript today. I think ClojureScript/Reagent/Om was mentioned really quickly when he did the "Hot Reloading / Time Traveling" talk back in 2014 sometime, which eventually led me to find Clojure which basically was made for making those things easy for the entire stack, not just frontend.
So thanks Dan for leading me to greener pastures, but a shame you still haven't taken the time to look into any lisp! Reagent+ClojureScript should be (relatively) familiar to you, as long as you get over the parenthesis-avoidance stage most of us go through :)
Common Lisp doesn't have as many opinions about your code, includes several nice features that Clojure skipped, and isn't chained to the JVM.
That's true for sure, and I'm sorry if my comment gave that impression, I'm not trying to say that Clojure is the only lisp, or even the best lisp! Just the one that fit me and my problems the best.
> and isn't chained to the JVM.
Neither is Clojure (the language), as it's available with JavaScript as a host (ClojureScript), LLVM (Jank) or even the Erlang VM (Clojerl). I'm sure there are more out there too, but the Clojure-JVM runtime isn't the only one available.
With that said, lots of people say Common Lisp is another good lisp, and been deployed in production for many decades, so I'm sure it's another great choice if anyone wanna get deeper into lisps. I personally haven't taken the time (yet!) to get to know it on any deeper level, so would be weird to recommend it before another lisp I know much better.
Personally, I think it does a few things better than Common Lisp and many Clojurists came from Common Lisp.
To paraphrase the late, great President John F Kennedy, "We choose to use emacs not because it is easy but because it is hard. Because that goal will serve to organize and measure the best of our energies and skills because that challenge is one we are willing to accept, one we are unwilling to postpone, and one we intend to win!"
Is it really worth the headache and extra complexity?
Caveat: I've used Vue more than any other FE library/framework, but for the last 3 years I worked exclusively with React (via Next—app router). The company I worked (I left in Jan) for jumped on this RSC train as soon as it was released and boy,oh boy was it a mess! We ofteb got direct support from Vercel when our shit will break or not work as expected...
To say the app was slow and behaved unexpected would be an understatement.
Eventually, they had to rewrite everything back as a SPA.
I'm posting because I find the technology interesting. I don't have a goal of convincing you to use or adopt it. I sometimes write about specific aspects that I find appealing but YMMV.
I also hope you understand I wrote this based on my experience with it. ♥
Server rendering is enough for the 80% use case. Browsers are optimized for it. Sprinkle interactivity without the impedance mismatch of jQuery and you're in heaven. No more overengineered state syncing between client and server. Just. Fetch. HTML.
Currently frameworks are trying to shoehorn it into the 2018 React mindset but that's not what I think is needed for them to succeed.
Is that your axiom or do you have any justification? I just don't agree with it.
Why should the server know about a datepicker's internal details?
There's some case where local state is more important (figma, google maps, chat app) and the backend is expected to act as a data source with a domain-specific API. Any mixture between the two and you will make the separation between the two domain (server concerns and client concerns) at the wrong place, making the design more complex (however well you hide it behind the curtains).
Which is exactly why the client state should be transient non-application state like datepickers, dropdowns, text boxes, etc. and it does not belong in the server (essentially what jQuery was used for back in the day, interactive sprinkles). And the other way around.
Perhaps you agree with me but there was something lost in translation?
RSC does a weird split where the engine is the same on both ends instead of a clear separation layer where one does not need to know about each other. I much prefer HTMX approach (samey old backend where you mostly take care of everything in the frontend) and Laravel Livewire approach (where you take care of everything backend side, with optional client side scripting).
No need to reinvent the world to fix a single problem.
I posit that any composable version of sprinkles or the “island architecture” will closely resemble RSC.
Only a small fraction of apps will ever use the full power of the RSC architecture. However, the React team doesn’t build apps, they build primitives for building apps. And good primitives are composable.
What they did wrong tho was to morph React into this mess. RSC should be React Server and live along React DOM and React Native, something almost unrelated to the previous concept of React. But we got this an entangled mess.
So just like it happens in some many cases in our fashion driven industry, we get dragged into using tools we don't agree with, or be busy with yak shaving adding support for other tooling.
Now is it just html ? Yup, Html returning from server is a server component, executed by browser with DOM API (for script tag).
So if you understand Html and JS, you should understand RSC right ?
THe point here is, your html doesn't get rendered on server, your <script> tag doesn't get executed on server. The server returns a program (code is data) to browser to render the document and execute script.
The real trick, is the server understands the html syntax (using string literal), so you got the illusion of isomorphism between 2 computers.
The server acts as "program generator", the client acts as "program executor".
Ok, it's just one piece of the whole.
The hard trick, is how to keep state preserved between re-rendering.
If it used a Scheme implementation like in Halo:CE, then people would have justifiably hated it. If it used something more like MIT-Scheme, people might've been okay with it. But I suspect it would get the same hate that JS gets, since there is a lot of low-quality JS code floating around -- even though JS itself isn't remotely that bad -- and you would instead have a lot of low-quality Scheme code floating around. Damned if you do, damned if you don't.
FWIW, it is feasible to put Scheme/Lisp in a browser today, and it might even be fun. Take a browser that lacks JS (e.g. Dillo, w3m); figure out how to add Firefox's or Chromium's approach to isolating each Javascript instance; and embed a Scheme or Common Lisp implementation (e.g. Chez Scheme [1] or ECL [2]).
You will need to decide what Scheme/Lisp functions/macros to include/exclude. It may seem obvious to exclude file operations, but they did that with JS and now we have IndexedDB; a restricted, virtual file system might be fun to play with. Multithreading might seem like a great thing to include, until you start trying to implement secure multithreading. I would start with whatever cl-isolated [3] allows and expand/contract from there.
And before anyone asks, "would this actually be worth implementing in 2025?", the answer is decidely "yes". You could build powerful desktop applications without the full weight of Electron. You could develop local-network webapps for you and/or your company. But I think it would be a bad idea to deploy this on the open internet without first spending a lot of time securing the browser overall.
Another option is resurrecting Closure [4], which is written entirely in Common Lisp. In that, you could go on a wild tangent and support multiple scripting languages (e.g. Common Lisp via cl-isolated, cl-forth [5], cl-javascript [6], cl-python [7], scheme88 [8], and even Coalton [9]).
[1] <https://old.reddit.com/r/scheme/comments/a3oogf/questions_on...>
[2] With ECL, you could use cl-isolated in addition to OS-level containerization, to achieve more defense-in-depth. Or maybe port cl-isolated to Chez Scheme.
[3] <https://github.com/kanru/cl-isolated/>
[4] <http://closure.common-lisp.dev>
[5] <https://github.com/gmpalter/cl-forth>
[6] <https://marijnhaverbeke.nl/cl-javascript/>
[7] <https://clpython.common-lisp.dev/>
[8] <http://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/s...>
I'm currently working on a project in Clojurescript, and I'm aware of some of the other options for running Lisp in a browser.
fn-mote•1d ago
> I don’t understand LISP well enough to understand [...]
Readers would be better off spending their time on a post with content.
For example, Racket has a webserver that uses continuations. Whether or not that's something you are interested in, at least it has intellectual content.
https://docs.racket-lang.org/continue/index.html
danabramov•1d ago
As I say in the article, I’m not familiar enough with LISP to understand how Electric works, but I’m a bit confused why you say there’s no content. I'm making a connection between the paradigms; if you find it vacuous, I think it would help to focus the criticism on the connection itself (why doesn't it hold?) rather than on me admitting the gaps in my knowledge.
I appreciate the link you’ve shared about Racket. From what I see, their continuations seem server-only, i.e. there’s no way to express client-side code in Racket (beyond HTML). What I’m interested is a more mixed approach where LISP orchestrates both server and client code, which would be a closer analogy to RSC.
judofyr•1d ago
> It marks a piece of code to be treated as data (to be sent to the client).
> This means that whoever imports onClick from the backend code won’t get an actual onClick function—instead, they’ll get '/js/chunk123.js#onClick' or something like that identifying how to load this module. It gives you code-as-data. Eventually this code will make it to the client (as a <script>) and be evaluated there.
The point of quoting in Lisp is that you get the code actually as data: You can introspect it ("how many string literals are there in here"), rewrite it ("unroll every loop once"), serialize it, store it in a database. And more importantly: The code is structured in the same way as any data in a regular program (lists). It's not hard for the developer to do any of these things.
If I get back '/js/chunk123.js#onClick' I simply have a reference, which I can use to invoke it remotely. The code appears to still be sent as bundled JavaScript, evaluated as usual, and then linked together with the reference. There's a small connection to code-as-data in the sense that you need to be able serialize code in order to share it between a server/client, but other than that I don't really see much of a connection.
danabramov•1d ago
>Of course, this is a lot less powerful than quoting because the evaluation strategies are being prescribed by React, and there’s no kind of metaprogramming like transforming the code itself. So maybe it’s still a stretch.
To me, the connection is that I'm able to treat a reference to serialized code as a first-class primitive that can be passed around the server code (and composed via component tag composition), but indeed it's not full-on metaprogramming.
kaoD•1d ago
The crux of Lisp's code-is-data-is-code is that you can manipulate S-expressions since they're a very simple data structure (think metaprogramming). I know you touched upon this in the article but acknowledging it doesn't make it less central to the issue.
I don't see the parallels except a very superficial similarity due to code being "serialized" (for lack of a better term, since that's not even what Lisp does) but that's just (1) a technical detail that glosses over the actual intention/capabilities and (2) not even true if we go down to the technicals.
But that might just be my personal priors, so take it with a grain of salt.
danabramov•1d ago
nothrabannosir•1d ago
danabramov•1d ago
This is indeed close in some ways — make-js-action roughly corresponds to "use server" in RSC (https://overreacted.io/what-does-use-client-do/#use-server).
However, I don't think this actually allows writing client-side logic in LISP too (beyond HTML that submits forms)? The point of RSC is to have the programming model actually span both sides. That's why I brought up Electric in the end, which does do that.