Moving on from React, a year later
kellysutton.com
kellysutton.com
I recognize that I'm not hip or with it, because I really fell out of front end development at the peak of jQuery where it was mostly about fixing browser incompatibilities, but it's nice to see that maybe it went too far in the other direction and we're overdue for a bit of a swing back towards server side.
I'd even go further and say that it's one of the few things that makes working with JSF bearable for me.
I'm still a thousand times more productive with React though, and PrimeReact also helps there.
You joke, but PrimeReact and PrimeVue are some of the best libs out there today, due largely to that heritage. Great company.
Eg we are definitely working backwards from SPAs now. "JavaScript islands" were how we got to SPA frameworks in the first place.
I don't have all the history and references, but anyone working in web in the late 2000s and 2010s can attest to how progressively adding in javascript on server side rendered HTML was already happening, even componentizing the server side code with CSS and JS packaged up was very common.
"The term “component island” was first coined by Etsy’s frontend architect Katie Sylor-Miller in 2019. This idea was then expanded on and documented in this post by Preact creator Jason Miller on August 11, 2020."
My only gripe, is that this leads the ready on an alternate history of how we got here.
cryogenic lab tech: Welcome to the world of tomorrow!
I’ve landed on a project using it.
By default it seems to maintain its own cache in the frontend of all api requests unless you use a non ergonomic feature “useLazy*” where you can give it hint if to use the local cache or not.
It’s completely terrible in a web app! Web is distributed and multi user by default.
End result with the app I’m working on if user a adds an item in one browser session, user b goes to a list view they don’t see the update, they see the redux-query cache.
It’s an over complicated mess for an already solved problem. Have the server send proper cache-control headers and let the browser deal with the caching instead of trying to build fat clients on a web app that should be thin clients.
You are right you don’t need to maintain state on the client but all these fashionable JavaScript frameworks promote doing just that.
It’s an application problem caused as a result of choosing the library, a common recommended library, and how the library and its documentation promotes its usage. The tools used were as the tools recommend. Admittedly, joining the project the wrong tools for the job were selected but these are all the react standard supporting libs.
The application should have been architected not to use redux-query, instead use the browser fetchApi and server side cache control headers as we own the entire stack. Today that’s controversial as ”every body uses” and “this is what the docs recommends”
You're only talking about http endpoint caching. These solutions are providing application data caching. These are not mutually exclusive concepts.
The latter offers things like data sharing/normalization across endpoints, data persistence across browser sessions, fine-grained cache invalidation/manipulation, and a lot of things on top of that.
It's a lot harder to manage a cache, and for most apps I never found it worthwhile. But there may be cases where you do want that level of control, like PWAs or an app optimized to never hit the network unnecessarily.
But it's definitely not replaceable with browser http header caching.
Both react-query (that is tanstack query now) [2] and rtk-query [3] include extensive configurability regarding their caching behaviors. This includes the ability to turn off caching entirely. [4,5]
Your story sounds like a usage error, not a library issue.
[1] https://www.npmjs.com/package/redux-query
[2] https://tanstack.com/query/latest/
[3] https://redux-toolkit.js.org/rtk-query/overview
[4] https://tanstack.com/query/latest/docs/framework/react/guide...
[5] https://redux-toolkit.js.org/rtk-query/usage/cache-behavior
I’m having a real hard time being polite right now. Do we have an education problem because where is this person getting their information that they think redux-query is popular?
Yeah I don't know where the parent comment got this from. Every few weeks I seem to see these low effort posts that basically boil down to "javascript bad", but gets a lot of upvotes. And when you read into it, you see the author often has a poor grasp of js, or its ecosystem, and has set up some unholy abstraction, assuming that's how everyone does it.
Use the right tool for the job lol.
At my work we develop web applications for a mission critical sector (emergency services). We use .net for the backend (we have also used golang in some projects) and Typescript and Angular for the frontend. We follow MVVP patterns, SOLID principles, etc. There are multiple layers, each layer has a well defined single responsibility. Our applications have been quite successful with happy users and great maintainability if I may say so.
There is nothing special about our setup. We could have used Vue.js or react for the frontend. We could have used any platform to build REST endpoints (though personally I like strongly typed languages for implementing business logic and think .net is underrated).
Software engineering might not be as old as say civil engineering, but we've been doing it for decades now and have a rich library of patterns and architecture types to draw from. I've been building software for over 25 years; the technology stack might have changed in that time but the principles are still very much the same or are extensions of the previous ones.
Using a SPA doesn't change the fact that if you want to build good applications, you first have to design them.
Everyone's mileage varies, of course, but this is quite the assumption. It feels like a lack of familiarity/comfort with the frontend or poor tooling.
With typescript and a basic UI library like MUI, this really shouldn't be the case for UI changes. If you're using raw JS and manipulating the DOM, maybe?
On top of this UI developer propellor-heads love to spend time (and money) pontificating on abstract theories about functional programming and homomorphisms and endofunctors so they can render some text inside a <b> tag. It's how we wound up with utterly ridiculous pieces of code like the "hooks" API in React.
The self-consciously cool kids are always going to be more easily swayed by fashion than cave hermits.
Except you’re at a company where everyone has figured this out so now you have 4 competing libraries with their own teams which are all 1/2 done making every engineer less productive cause they all have bugs and slightly different priorities.
I guess the good thing is that comprehending our absurd web frontend creations is beyond current LLM's capabilities.
I've worked in JS, TS, React, and RoR, and yeah I'd say making a change is probably twice as quick in RoR as in React.
The thing is that Rails is an opinionated framework. If you do things that the framework is designed to do, then it's very very easy. If you go off-piste and have to fight the framework to do the thing you want it to do, it gets very hard very quickly.
React is less opinionated (about most things) so the converse is true; it is slower to write stuff in, but there's less constraint.
If we're using jsonapi, GraphQL, Thrift, or any other protocol that's not HTML, we need to do the following:
- Make the change on the server to support the new functionality. Deploy.
- Make the change on the client to adopt the new functionality. Deploy.
- Remove the old functionality from the server (optional)
Because the client is a separate application, it becomes riskier to deploy those changes together. I need to think about it more as a developer.
With server-authored HTML, this separate client + server deploy is not required.
SSR has been a thing in react for a long time.
Obviously unless the team has committed to React SSR + React backend (is this common?)
`aws s3 sync` could have been fine.
Was that the piece of deployment you were curious about? If not feel free to be more specific.
Thanks!
OVERALL: I recommend the Serverless Framework but SSO wasn't supported at the time I started building so we use the CDK for deploying everything. The CDK stacks for the S3 bucket (etc.) and API are separate but that's a matter of factoring for clarity since it would be fine to have one big stack. For similar reasons, I factor the API and API deployment separately, alongside the WWW and WWW deployments. The entire company is a monorepo with a single command at the root that, beyond one-time account bootstrapping (which also uses CloudFormation and CDK), triggers scripts run locally or remotely to execute unit testing, deployment, and acceptance testing in a progressive multi-account deployment (on main). The CICD script installs dependencies, runs tests, identifies itself and uses that to assume account specific roles to deploy into the individual environments. This is all pretty excessive for a startup but seemed like a worthwhile and more secure foundation for actually trustworthy experimentation in delivering solutions into some of people's most sensitive private spaces and visions to go deeper.
Meta-note: I accept that the separated pieces of the infrastructure have timing deltas and that some bugs could arise out of this once we've scaled. This is never not a problem but can have diminishing windows and/or effects with investment. This can be solved either by coding the API to handle current and current-1 contracts or maintaining concurrent current and current-1 lambda version deployments, where divergence occurs. None of that contradicts a single deployment of consistent code. There are deeper solutions with greater financial efficiency but I'll need a wild success scenario before those make business sense to invest in (monoimage k8s, knative, and so on).
LAMBDA: we have built configuration that specifies the build entry points for tree shaking so that our deploy sizes are tiny and that is used by the CDK pipeline too. Our data tables are declared there too so that code and deploy share config. The Lambdas are grouped by event type (i.e. HTTP/ApiGateway, S3, Kinesis, EventBridge, etc.) and our declarations are structured according to generic Lambda defaults, a source-event-specific defaults, and a function specific declaration, allowing overrides at every level. This may sound complicated but it very cleanly organizes the configuration rather compactly, providing incredible clarity, and making scope of effect explicit and consistent.
Everything is written in TypeScript which covers internal compile-time type safety but I have written a layer of middleware for the Lambdas so that they declare their contracts (that uses AJV to validate schema to type equivalence [JSON Schema is far more specific of course]) and entry points and enforces schema compliance (engineering requirements) at all boundaries in and out of the code base (i.e. receipt of API calls in client, receipt of client requests to API, S3 data, database puts/receipts, and so on).
This got long so feel welcome to reach out (see profile) if you want more specific detail about any specific piece.
Yes, you have to keep API compatibility in mind when doing changes, because long-lived sessions might still exist. But has not been that much of a big deal in my experience.
Separate parser is basically what Jinja2 has or other templating engines like Django templates. React could have used any templating engine that already existed.
But compare it to the elegance of SXML and treating HTML as the tree that is it, as structured data, enabling pattern matching on that, avoiding the need for a parser, and all other things that are in the language. Use. Structured. Data. It is so simple.
Mainstream frameworks got a lot to learn still. They are still on the Kindergarden level of treating HTML as a mere string for most parts.
- Other factors can often dwarf the separate deployment overhead, especially given that the deployment steps are done so often it will force the team to make it efficient and get used to it.
- There is no hard requirement for separate deployment in the first place, if we are just talking about react / spa you can deploy it in a single step. (Not saying you should)
The point is: yes there is quite a lot of overhead. From separate deployments, from separate state management and I think the big ones are often having two different programming languages and having to align schema on frontend and backend. It is hard to deny this. But then saying that because of this overhead, these kinds of apps take 2x time to code implies denying there are benefits as well, that under given circumstances can outweigh the overhead.
I think it is more interesting to assume that in some cases the benefits outweigh the cost of the overhead, and in others they do not.
> when compared to a world where that change isn’t risky (server-rendered ERB)
Server rendered html templates a la PHP - which is what ERB is, may be not risky in this smallish project with likely a fairly straightforward UX and seasoned rails devs, but it can be risky in others for sure. I think a lot of the motivation to embrace what react began was a desire to move out of a tangled mess of spaghetti templates, to be able to use means of abstraction and modularity as powerful as regular backend code. Frontend devs want to create and use components. You cannot create a component with html templates, not really. It will be a mess.
Moving from server rendered html to an api + spa architecture has quite an overhead of course. Just like adding a separate backend service. That price should be worth it. And a good developer can judge whether in which context that is the case. But pretending the benefits that motivate such a change just never justify the costs doesn't make sense to me.
The cost of a change is a very, very complex measure. Anyone who has a simple answer to it that is universal, true and useful will win a Nobel Prize. This is because the cost depends on the person who is making it and his peers, on its effects in the future, on the project of which it is a part, on luck and on the larger ecosystem in which it is done. There are just way too many parameters.
In first case FE is productive there is no need to transform data but makes BE a hot mess. Otherwise FE have to maintain layer of code to convert BE data to presentable UI data. It's harder but allows to decouple UI reqs from changes along all stack.
I've contributed several features to VSCode (Typescript) and it was relatively easy. I have completely failed to do the same with Gitlab (Ruby) because the code is so hard to navigate. I did manage to add some features to gitlab-runner but that is written in Go, which again is much easier to follow than Ruby.
I think they are really talking about client-side vs server-side rendering. Server-side is much simpler.
I am a FE with 10 years of experience, and has tried, and tried really really hard and multiple attempts to make HTMX works well.
Doesn’t work. UX is much worse, code discoverability is much worse, slower to code in, everything is messier than just plain React SPA with JSON data. Terrible, terrible DX and UX.
Seriously, there is a reason why despite all the rage in going back to multi page Django/Rails app, very very few people actually take it seriously. You just don’t hear the negativity because most folks just tried it, saw that it is worse, and moved on without writing a blog post.
A few thoughts:
I tried just htmx too, too limiting. Stimulus/Turbo combination is much better.
Use the right tool for the job. Highly interactive app, like an editor? Use react or similar js framework. Mostly page/document website? A backend framework is much faster and easier to develop, and much simpler to test.
Use what you know yada yada.
With all this framework nonsense its hard to forget at the end of the days its all just javascript.
(it may not be the right option in any given case, but it's worth knowing it's there even so)
It worked for us, one of the most visited websites in my country, migrating away from a Vue.js based “modern” frontend stack.
It was a huge success in terms of productivity, developer experience, and cost.
This should exclude roughly 98% of React SPA websites, which ended up being shoddy-made, non accessible.
Yes, this is a fact. HTMX / Unpoly are just lighter solutions and much easier to debug.
Wish the web spec just standardized a mechanism for partial page load/render. It would become the most frequently used developer technology feature of the 22nd century.
I also think HTMX is meant mostly for backend developers to be more productive in frontend.
React et al try to create a different paradigm on top of this. And that makes it like building a desktop UI AND a server app
To me, this is not in anyway better than building something for the browser - pages and html.
And when not, it is due to CMS products available as SaaS like contentful.
In such cases we go for Next.js due to it providing a similar experience with server side rendering and controllers.
In one thing you are right, we don't take seriously development stacks that don't offer either JIT or AOT compilation.
The worst sin a large codebase can do is being inconsistent.
I'm pretty sure HTMX is closer to standard stuff than anything you do in Angular or React.
As someone who's been doing web dev since the 90's and is currently leading a project that's built with HTMX and having onboarded a few younger/junior "react devs", you're not wrong. What I've seen is that there's a whole generation now who just don't know how to do things in a different way than by building a SPA. The difficulty onboarding those devs is always in getting them to unlearn the patterns that React/Angular/etc. have ingrained in them. I've reviewed PRs that were a mess of complicated HTMX attributes, backend logic switching on headers, etc. and pointed out that all of it could be replaced by just, like, using a plain old HTML form submit or `<a href="..."`. I wish I were kidding. They almost always start out massively overusing HTMX out of a fear of triggering the dreaded full page reload. But in a non-SPA world, with a fast backend, full page reloads take milliseconds because there aren't MBs of JS to download and execute and a bunch of client-side state to reconstruct.
> It's also harder to find HTMX dev's when there's so much React devs
For someone who is familiar with basic web technology (like the 20 year old version with HTTTP/HTML and minimal JS/CSS), it only takes 15 minutes or so to learn HTMX (then maybe a few days of building where you pull up the docs occasionally to remember the names of the attributes). The idea of someone calling themselves an "HTMX dev" the same way we have "React devs" is ridiculous.
So look at your requirements, check the framework for libraries and tools (like deployment, hosting, monitoring etc) available. The libraries you use are much more important than the framework.
Do note that although there are plenty of react chart libraries they tend to be quite under-performant. Tying chart rendering with any frontend framework building blocks ends up with bad performance. If you are dealing with >10k data-points that update often I recommend using a plain-js chart library.
deployment, hosting, monitoring etc
Isn’t this backend-related? Generally framework agnostic?
Deployment and hosting is just publishing a Vite/etc bundle, right? What is framework-specific frontend monitoring?
For the most part yes, until you have a very specific requirement like complex drag and drop interactions or a complicated UI component like a calendar view. Also don't underestimate how much work it is to make a good component library from scratch, picking a framework based on the available gui libraries is important. I would say you should pick the gui-library first and then the framework.
> Isn’t this backend-related? Generally framework agnostic?
Depends, sentry.io for example has a pretty good NPM package that integrates well with react giving more detailed errors. Some deployment/hosting solutions integrate better with some frameworks (although it is mostly a concern for SSR or static site generators projects).
If you are starting fresh, I'd consider taking a look at one of the lightweight options like lit [0] or FAST [1] for building your own. (Later this week or this weekend, I'm going to try to document building Web Components with my Butterfloat [2] because so far it is turning out great in my current hobby project and about to the point where I'm going to recommend it to production projects.)
[0] https://lit.dev/
[2] https://worldmaker.net/butterfloat/
(ETA: Also advice I wish was more common in Web Components documentation: You don't have to use the Shadow DOM at all. Much of the complexity and confusion in Web Components is interacting with the Shadow DOM. There's a lot of great advice on why you might want to use the Shadow DOM. There's less great advice that it can be YAGNI and it is so much easier to style Web Components when they don't use the Shadow DOM, especially in your own Web Components for your own FE using a common style template like Bootstrap or Bulma. You can just ignore the Shadow DOM and use the "real" DOM.)
I've yet to find a frontend scripting solution that works well. My current stance is to have the frontend as thin as possible, no matter how it is written. Have your domain logic on the server output Plain-Old Data structures (JSON or similar) and render them with stateless, dumb views, be it a template, Vue, React or whatever.
A clear boundary between the domain logic and view is important. Rails templates that can basically access anything is a terrible idea.
Furthermore, I'd say this: if you can't simply serialize your view-model and send it to the client because something might get exposed or because it's a complex object with embedded logic, your first priority should be fixing that. Only after achieving this should you even think about changing how you handle the front-end.
In addition to the points you have made already, I would like to add that almost every larger project needs a lot of business logic on the backend side anyways. There is no possibility to have this logic in the frontend and there never will be (for security reasons at least). So given these circumstances, we usually already have quite a few people experienced with - and very efficient in - these backend technologies.
So why would I spend a lot of time re-creating all kinds of routing, models, properties, etc. in an SPA? We currently run an Ember SPA for our largest project and 1/3+ of the time spent on it is pure waste because it is simply additional work to facilitate the use of this SPA at all.
In every project I've worked on, the interactive and dynamic elements actually used that are written in JS end up being a lot fewer than originally anticipated. Unless you're building a product where the majority of the functionality relies on instant, realtime updates such as maybe a streaming site with chat, etc., building, maintaining and extending an SPA is a huge overhead that offers little benefit over "dumb", static views with small, isolated interactive components.
This is a huge problem in the ecosystem. People want a landing page, so they reach for some full-stack SPA backend+frontend framework that is hip today, which is absolutely bananas.
Sure, if you're building web app with lots of dynamic and interactive elements, go crazy, because you'll probably need it, lord knows you'll get tangled up in state management no matter how clever you think you are.
But for websites that aren't trying to became the new Google Maps/Figma/Whatever, simple and basic templating patterns won't just be faster to develop, but easier to maintain and a better experience for your visitors to use.
And sure enough, then came the self inflicted workload. "Oh we need to change the router.". A problem you do not have with any popular mature web framework on the backend. "We need to upgrade to NextJS version 149, for feature blablub." Which would come naturally with something like Django or other traditional web framework. "We need to upgrade NodeJS!" ... and so on and on.
While all of what the FE actually did could have been just render static templates and a few lines of script.
Companies choose their own downfall by following the FE hype trains. The result are the shitty web apps, that don't need to be "apps" in the first place, but could simply have been static pages. It is quite a comedy show and I would laugh, if it did not affect me negatively.
It's a really strong selling point that you can use a popular FE framework for all the templates, but at the same time almost completely avoid state management.
Unless you're just building simple landing pages (I think the context is "web apps" here) or something, you might not have removed any state management, you've just moved it elsewhere.
So the question is, where did you move it instead? The backend?
In my experience most of the state management in FE apps is about fetching and handling data from different types of APIs. With inertia you get a very simple abstraction in that each page gets its own little API. Then inertia takes care of feeding the data from that API to the rendered page when you navigate to it.
For that reason the page can be a "dumb" component, and there really isn't much state to manage.
If the app needs modals, dropdowns, forms e.g., you will need to manage the state of those in the browser, which I think is very reasonable.
Obviously there can also be situations where you'd want to have a small part of the page to fetch some data asynchronously, and for those you'd need to use something else - inertia doesn't do everything.
Has your experience been more negative?
I also feel like the learning curve is pretty steep and you can't just have a random, experienced non-Elixir dev join the project and have them fix something. They must be comfortable with Elixir & Phoenix before they get productive. I feel like it is multiple layers of complexity and uncommon patterns.
Other than that, I think it is very cool. I have very strong mixed feelings about the feature hah.
This makes it also very easy to end up with N+1 issues that are not caught in tests (because the tests don't render views), "forgetting" to scope the data (rendering more records than the user should be able to access) or rendering sensitive attributes the user should not have access to.
Even with tests that do include rendering the views, it is very easy to have a test like:
it "renders my posts" do
post0 = create(:post, user: current_user, title: "Foo")
post1 = create(:post, user: current_user, title: "Bar")
response = get "/my-posts"
document = parse_html response.body
expect(document.css("tr td.title").map(&:text))
.to match(["Foo", "Bar"])
end
Now there's so much wrong with this test, but the primary issue is when the view does something like this: <%= render "posts", posts: Post.all %>
The test implies the document should only render the current user's posts, but the view / template actually renders all posts. You don't want to test this kind of logic by inspecting HTML, because it is error-prone and expensive to test this way.What if you want to change the HTML structure? You'll end up rewriting all these tests, and it is VERY EASY to make subtle mistakes when rewriting the tests, resulting in false positives in the future.
Instead, I think you should separate this logic to a model, not to be confused with "activerecord classes" - I'm thinking of a `UserPosts` model, like this:
module UserPosts
extends self
def get_user_posts(user, opts)
user.posts.with_opts_or_something(opts)
end
end
Then you also disable automatic loading of relations (forgot the config name) and, by convention (or enforced in some way), never refer to any model modules or classes in a view (or any method called by the view, like the helpers).Your controller will likely look like this:
class UserPostsController < Whatever::Base
include UserPosts
def index
@posts = get_user_posts(current_user, page: params[:page])
end
end
By keeping the domain logic inside specialized modules per access scope (i'd have separate `Posts`, `PublicPosts`, `AdminPosts`, `ModeratorPosts` modules), you can test the logic in tests per module (including tests what the module SHOULD NOT access). Then, when writing the views and controllers, you don't need to re-test or re-remember to test the "should not" cases - the test cases you're easy to forget.You can also design the modules in a way that they only load (select) the properties that context would be allowed to access. If you have sensitive data (like a user's real name or email), you don't even want to load that property when loading the users from a `PublicPosts` context, since public posts should only render the user's id and public nickname for example. You can also wrap the posts/users in presenters using whatever fancy gems you fancy, as long as it gets the job done.
In an ideal world, I'd lock down Ruby templates to only have access to previously loaded data via instance variables and helpers, nothing else.
It is a bit hard to explain, since I have limited time to address it properly, but I hope I get the point across.
How does your approach contribute to the discussion? You created a new handle named ‘htmxsucks’ solely for the purpose of making this comment! While it’s perfectly acceptable to express negative opinions about a technology and be vocal about them, doing so anonymously in this manner undermines your credibility.
So now we have two posts derailing this.
It's like we are learning all of this again now.
To me, it's a lot simpler. I use forms and links and render HTML templates. That's it.
I don't need to worry about building and maintaining a JSON API just to serve the front-end. I don't need to worry about distinct sets of routes and models. There's just one repo with one build pipeline and test setup. There's no need to worry about front-end state at all, since I'm just using plain-old links and forms.
I'm not claiming that there is never a use for React or its ilk, but I am genuinely finding it hard to understand how one can assert that this simple HTML-based approach is categorically worse in all circumstances.
In my context, the simple approach works. If I were working on something that had a high degree of inherent complexity on the front-end -- maybe a web-based video editor or some kind of WSIWYG productivity tool -- this approach would almost surely fall apart, but that's the point of engineering: Choosing the right tool for the job at hand.
For less frustration when reading such posts that are written, read the author as writing for themselves, reporting the pros and cons for their very particular situation, and see if there's some nuggets to be found in that context.
Don't read them as telling you those same pros and cons apply to your situation -- even if they veer into implying they may :-)
Then I moved to HTMX and I did more in 5 weeks than I did in 5 years.
Why? We still have Vue 2.6 (not even 2.7 with compAPI) projects living and chugging along just fine. Why do you feel the need to keep everything updated to latest or whatever?
For me that's the best of both world, and this architecture has served me well.
We would use the same controller name, and just load different JS for different pages.
It was an odd stack, but it served us pretty well.
The original reason the web took off to dominate the internet was because it had a very, very simple UI.
get the mug: https://swag.htmx.org/products/htmx-sucks-mug
EDIT: lmao, the article doesn't even mention htmx and you registered the username htmxsucks just to leave a negative comment about it?
It may be, that very few people these days actually know how to make a website without reaching for hyped JS frameworks. The thing is, one needs to know more than just JS. One needs to know an actual backend language and learn the framework. Many web developers these days don't have this set of skills, might not even know another language than JS, even though it is not hard to learn.
Not every website needs to be a "web app". Not every website needs to be some SPA. Many are better off without being an SPA. Once you are not building an SPA, many other frameworks become very viable and proven alternatives, among them Django and Rails.
That said, AFAICT, there's just two advantages:
1. Those annoying little UX doohickeys that actually DO want that level of interactivity
2. You can use any of the vast library of existing React components (for me, that's been react_big_calendar)
As a bonus (and, why I rolled my own, since `react_on_rails` doesn't support these), and with a bit of tweaking:
1. It plays nicely with Turbo. I can use Turbo to update my React props and it just... works.
2. You can embed a template as a child of the React component.
2a. This also plays nice with Turbo.
It sounds a little screwy, but so far it's working well, and it feels like it lets me use the right tool for the right job - aka, Rails for everything except the stuff with extremely high interactivity.
Edit: Ach, sorry - it's going to be awhile, when I went to roll my own I did it inside my project, so it's not even close to being available as a gem :/
Years ago I decided to not worry about those concerns and it turns out those concerns are irrelevant. Writing content, really all front end logic, is as fast as you can type on a keyboard irrespective of your tech stack. Just about everything else is over engineered insanity. That was even true back in the IE7/8 days.
It became apparent to me the only thing that mattered is time to refactor. The larger the application is the more true that becomes. The things that help with that the most are type annotations on everything, high execution performance, and fast test automation. Everything else became a distraction or noise that got in the way. The application does what it promises or its defective. It can be modified quickly or good ideas are abandoned for risks or costs.
So I don’t worry about tech stacks and spend too much energy on execution speed.
Maybe with graphql it is different
Rails makes it easy.
Having a flexible API tier makes that more ergonomic.
The point is that the data model (and hence, the API design) applies equally to a web UI or a native application. The issues you're raising also matter for making webpages fast. When you think about the data model design before you make the presentation layer, in almost all cases, the choice of what you render is basically a straightforward transformation.
Well, someone did. The person above them said you can just map your Rails html routes into JSON returning routes (translating pages into API endpoints).
Which obviously gives you a suboptimal API since data access is tied to Rails pages instead of having a flexible, deliberate API.
And once you build and have to maintain the latter along with the former, you are now duplicating work. It's a familiar issue for people who have built SSR apps.
That was me. And no, you misunderstood. I said "controller logic" -- Rails (and any other decent web framework) allows you to map routes to endpoints in the code. In the simplest case, that endpoint could be used for an API response or a webpage, and just render different things. Alternatively, you can even have API routes mapped to a slightly different top-level endpoint, which does the vast majority of its work by calling the same set of downstream methods as the webpage version. Or a thousand other variations on the theme.
In short, assuming that you don't just structure your code as one gigantic function for each route, it's not hard to do, or hard to maintain. You do have to think a little bit about how the controller logic fits in with the API and the webpage structure, and I contend that this is the actual, practical reason that we've seen so much burdensome (over)use of React, GraphQL and the like -- those things are separate teams at many companies, and the "decoupling" theoretically unblocks the teams.
Those that want it easy, use Electron.
This is not 2011 when every website was building an "app" for native experience. If you are not building an Uber or an AirBB, you will probably manage.
I don’t grok this part. Based on my experience with RoR and JS/TS, making changes in TS-land is much easier and quicker than dealing with Ruby.
Is this just a preference/experience thing in the end? For you and your team RoR is faster/easier, so that makes it the better choice. For a different team JS/TS may be faster/easier, so that makes it the better choice?
I should have made the distinction between client-side and server-side JS/TS. Familiarity and expertise will be a main driver of productivity for a team, regardless of language/framework. For us, that's RoR.
Client-side JS however has a multiplying property, where we often need to write more server-side code to accommodate the growth in client-side code. If the client-side code is providing undifferentiated value (e.g. authoring HTML), we now have more pieces in our system to accomplish our goal (author HTML). What's been surprising to me is that you don't need to author HTML client-side to have a reactive experience. With Turbo, Stimulus, and server-rendered HTML, a great experience is possible with fewer layers.
I think the power of fat clients comes when you have different consumers of the same APIs/logic. Like a browser app or two, maybe backend services, a few task queues, etc. Then having a clean separation between client code and business logic becomes super valuable.
In my experience with SPA we got to a point where my team could go weeks, even months, without having to make changes in server code. Whole new features that were fully supported by our existing APIs because they were written as reusable APIs. If you’re having to write a bunch of Rails to support the growing client code, you probably didn’t need a separate client yet.
But when your codebase gets to that point even Rails is just a frontend really. So it’s mostly about which tradeoffs you prefer. Unfortunately I left Rails right around the time Turbo was becoming a proper thing you could use so I don’t have the best feel for what it’s like.
As to rendering, I’ll be honest and say that my code has moved more towards client-side (using ConnectRPC and my framework ojjs.org—-shameless plug :), but I love that you have had success with a different path. Super interesting, and thanks again!
We're building our company on Rails because we think it's the best choice for a young company, since it allows us to respond to customer feedback more quickly. That's what we're optimizing for right now!
1. There isn't a good way to test it. There is nothing in the docs about how to test it.
2. Keeping state in the DOM is dangerous
3. Messaging between Stimulus controllers is painful
4. They disconnect parameters from functions. The functions have to scan through the DOM to find what they need which I think is fundamentally weird
5. Reusability is rare
6. It doesn't try to play nice with the larger JavaScript ecosystem.
I personally prefer Vue.
<div data-parent-id="1234" data-category="Article">
We'd use this data for any number of purposes to build out other little features that'd rely on the attributes.I'm going to cheat and get a list from AI:
1. Difficulty in maintaining and debugging: When state is scattered throughout the DOM, it becomes challenging to track and manage, leading to code that is hard to maintain and debug.
2. Performance issues: Frequently querying the DOM for state information can be more expensive and slower compared to accessing data stored in JavaScript objects or dedicated state management solutions.
3. Lack of a single source of truth: Storing state in the DOM makes it difficult to establish a centralized, authoritative source for application data, which can lead to inconsistencies and errors.
4. Synchronization problems: Keeping DOM elements in sync with a mutable list of data can quickly become complex, especially when dealing with dynamic lists or elements without unique identifiers.
5. State persistence issues: DOM-based state is vulnerable to loss during page refreshes or navigation, which can lead to poor user experiences, especially in single-page applications.
6. Scalability challenges: As applications grow, managing state in the DOM becomes increasingly cumbersome and can result in performance bottlenecks.
7. Difficulty in implementing advanced features: Techniques like time-travel debugging, state snapshots, and easy hydration become more challenging or impossible when state is primarily stored in the DOM.
8. Increased complexity in component communication: Relying on DOM for state can complicate the process of sharing data between components, potentially leading to prop drilling or other anti-patterns.
> Code is not an asset, it’s a liability.
> What is the least amount of code we can write and maintain to deliver value to customers?
regardless of HTMX/Bootstrap/react, the simpler the better.
If Meta cannot even handle React, what hope is there for the rest of us?
Do you have sources on this?
Even today you can see Messenger completely break down if you have to sync for more than ~30 seconds. JS console fills up with errors about your broken state as you start to lose messages. Local storage is out of sync, session state breaks, and it's all right there in the console. It's been like this for years on WebKit. React just has too many bugs for a complex behemoth like Messenger.
React Native is just as bad and basically everyone I know in real life can corroborate lost messages, broken group chats, horrible photo experiences, and memory leaks!
You can just look at Messenger vs WhatsApp and the difference is just night and day in the user experience. The e2ee backends are the same (according to my pcaps, I could be lying), it's purely the front end that changes - and you won't find anyone that says Messenger is better than WhatsApp.
JavaScript and ReactJS are at least 10 times less efficient to write than C++ Dear ImGui. That’s so crazy.
I don’t think that’s an intrinsic quality of a DearImGui type API design. I’m reasonably confident it could produce a professional end-user facing UI. And that it would be perfectly performant and battery efficient.
DOM UI libraries like React have the harder problem of surgical tree updates since they can't just redraw from scratch.
It's where all of their complexity comes from. Not even React's basic primitives like useState would be necessary if you were just going to redraw the whole UI on the next frame.
So I started working to create my own UI framework with some of the DearImGui feel but being a bit more scalable.
Recently I just added basic CSS [1]. Super fun to get it working but it’s been a lot of work combining UI styles. But it’s cool getting 60fps live animations in a 4mb binary. Hopefully CSS can help get it looking good.
1: https://blog.elcritch.net/why-i-settled-on-css-for-theming-i...
Look, you might be right! I still think it’s not the right tool for all situations. But this has been said over and over for at least ~7 maybe 8 years for what I can remember and it’s yet to be true, for better or worse. We’ll see though!
Is this really that impressive if you're prefetching? Surely a cached response is <50ms always. What's the prefetch cache hit rate?
If it's anything like remix/react router prefetch caching, it's also useless on mobile. Depending on your target market, that's a huge difference
Remove the T, it's cleaner.
In real world we are supposed to use that resource(client side compute/storage) to balance the server side and client side processing, not just for performance but for optimal user experience.
In summary, performance is getting better on the high end, but at the same time, large parts of the world are coming online with terrible devices, so median device is not very good. P75 device is totally removed from what web developers use to develop on.
On top of that, there's still a big concern with battery life, spotty connection and apps competing over phone's resources. E.g. on Android, if your website is too heavy, the system will kill stuff running in the background. If your users watch youtube or listen to a podcast and your website make the audio glitch or kill running spotify, that's not a great experience.
Last point is that react-like sites use the phone's resources in a really bad way - phones get more cores (especially e-cores) and special processing units (ai, video, graphics) but JS runs single threaded on the UI thread, competing with everything else, together with expensive JS engine machinery. If you ever profile some SPA on some representative android device, it's just all yellow, bottlenecked on JS.
But if you're building a very client heavy application that does something like Photo editing, CAD, video editing etc I have a hard time seeing a server side generated content to be successful because you need a lot of client side state no matter what.
For example, I have done a lot with maps and it's not possible AFAIK to render the canvas on the server since it's a browser only api. Also I don't really see the benefit since it takes too much computational power to generate graphical things on the server.
I wish I was doing those kinds of apps because that tech seems wonderfully nice to work with. Unfortunately I do client heavy apps mostly so I don't have the benefit from using one. Offline support is... impossible to do good with those kinds of tools.
That's not true. You absolutely can make edge applications faster than anything running server-side. A great example is Linear, it can switch between issues within 100ms (it even supports hjkl keys for navigation!).
But this requires you to be EXTREMELY careful. In case of Linear, they're using a sync engine to replicate the data onto the client side, rather than doing the request/response model.
That's nothing special and definitely easy to achieve with a server-side backed MPA.
It's certainly doable, but not at all straightforward to make it reliable. A local app, on the other hand, does not depend on the network latency and the server load.
Also, does p50 mean median?
Is it more abstract? Is the state model simpler?
I have my issues with react but I don't think a different framework necessarily solves those issues since those issues are explicitly with how you need to handle state changes in JS and how "components" tie into that.
I haven't used it but something like Elixer Pheonix liveview[1] / .NET blazor[2] or skiplabs skip[3] or a well setup apollo client[4].
Nothing prevents you from building that abstraction on react, I don't know about Vue/Svelte.
The idea is though that there are models which should come from your backend, viewmodels which should be in your frontend code. And there should be no other state.
Whether a button is clicked or not has nothing to do with the backend. What the button does when it is clicked also has nothing to do with the backend. All that should theoretically happen when the button is clicked is you should decide what needs to happen. If a thingabob needs to be deleted from the viewmodel, no calls to.the backend. If that thingamabob is linked to the actual model then no frontend should change, the backend should change and a message that it has successded / failed should go to the frontend.
It's when you bring optimistic updates and eventual consistency etc in, then your frontend code becomes absolutely chaotic. You make it a distributed computing problem but then instead of message passing and actors which is a known good way to handle distributed computing you try to synchronise state.
1. https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Auth.html#modul...
2. https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blaz...
Also, the SPA model works great for some apps and badly for others. This blanket ruling of "do x and not y because x worked for us and y didn't" is just bad advice.
It's never simple, and NEVER EVER one size fits all. I'm saying this after about 30 years of doing this job commercially.
I'm sticking to cached static resources, and just sending data over. Not rendering from the server, but not writing single-page apps, either. The more you render from the server, the larger your caches end up. Not doing that for HTML.
Just HTML, CSS, and JavaScript. No tooling. No building. Web Components and fetch(). No frameworks on the client-side. Express still on the server.
I'm trying to optimize for not swapping out dependencies on a several year scale, and it's working out pretty well so far. I removed React because they're not interested in UMD builds anymore, and I'm not going to jump through hoops to make it work on my end, either.
There's nothing fundamentally stopping react apps from being fast like this, but the dependency snowball has a way of hiding accumulated dynamic memory allocations and event listeners. Instead, we should be intentionally grouping allocations and events, keeping them contained to a few well understood pieces of code. Without this, these apps could be ported to native C++ or rust and they would still be nearly as slow because there is no magic language that can make a slop of millions of tiny dynamic allocations fast and low overhead.
This is a natural fit for data-oriented programming in most systems languages, including C++. It's also compatible with numpy and any other dynamic language backends that have a performance-oriented array primitive or lib. The data can be served dynamically from memory, or written to files on a static server like nginx.
this.innerHTML with a string template prefixed with /* html */ so syntax highlighting is applied.
> The more you render from the server, the larger your caches end up. Not doing that for HTML.
Is HTML really that much larger or complex than JSON? Based on the following it seems to be only +10-15% larger (n = 1 dataset obviously).
https://github.com/1cg/html-json-size-comparison
Is the amount of data transfer the main reason to use JSON or do you find Web Components play better with JSON payloads? I could imagine the template string being rendered on the server, fetch()-ed by the client and swapped with this.innerHTML.
Do you manage any amount of cross-component state? It's one use case where JSON would win over "HTML over the wire".
But my personal belief is that web apps are moving towards static sites that are cache-friendly and offline-ready. This for couple of reasons, first is easy integration with CDN. Second is easy integration with webviews for mobile apps.
And easiest way to implement offlineness so far has been through a state management library. Doesn't mean that doing what the article suggests, moving logic to server, is impossible but it's quite a pain. Much easier is to move logic to client and handling it there.
Moreover, the fact there's so many UI components and libraries available for React and so forth, makes developing much faster. Depends on the developer, of course (and I personally prefer Svelte), but is a rather big advantage if you are doing anything complex in UI.
The problem with RTK Query is the strong reliance on the internal cache, which is often in the way, since i need the updated data from the server most of the time, but otherwise it works quite well.
For apps where i control the backend (mostly PHP with Laravel or Symfony), im using Inertia now, which enables me to directly access the entity/model data in the React code, without any API necessary.
This works really well and reduces the React code to the minumim, UI based stuff, where it shines.
There is a Stimulus/Turbo plugin for Symfony as well, but Im not sure if this technology isnt too niche and unproven yet, in contrast with React, which has been around a long time, has tons of docs, and also loads of devs with experience on the market.
IMHO there's exactly two factors when it comes to having success and efficiency with established tech like React or Rails. One is familiarity, the more experience you have, the more you know it, the more you like it, the better you are going to be with it. The other is restraint. The less custom EVERYTHING you make, the easier it will be to maintain and change. If you lack one or god forbid both, you are going to have a bad time. If you have both, probably anything that's not actually bleeding edge will work out well for you.
So many fewer packages to deal with (especially if you want to SSR your app), and my site is way faster. And I get to use JSX which is my favorite templating engine and requires little porting from my React app.
I suggest people really take a look at Alpine.js, you can do everything in it as you can with React and it's a significantly smaller, simpler package that runs right in your browser.
Sure React may be overkill for a personal blog and no one is saying it isn't but I wish people that have ported from React also assess honestly the complexity of the app we're talking about
Facebook is obviously going to have a ton of shit going on all at once, though for the vast majority of sites it's mostly just some form or another of CRUD with perhaps some somewhat complex front end UX.
You can build many web apps without React. To me the secret is with JSX, which makes everything way easier to manage.
Realistically the only issues with using a more minimalist framework instead of React is global state management and persistent components on page navigation. Global state management in Alpine can be done with Alpine.store and persistent components can be implemented with either Hotwired Turbo, Alpine AJAX, or HTMX. Are these things better than React Context/Redux and React Router/Next Router/Some other router? Probably not. But it does work.
I wouldn't recommend this approach for actual businesses, mostly because devs will already know React and thus be more productive. But I think it's genuinely a viable approach for building web apps.
That's just the thing. Businesses chose a specific stack because the campaigning for that stack won out amongst any competing stack, not because that stack most closely aligned with their requirements and needs.
Boring Anecdote Time:
I do contract work. All current frameworks have a velocity for new API/endpoint creation (with DB schema modification) of between 4hrs - 8hrs (spread out over 3 - 4 PRs)[1], assuming all the PRs come back with LGTM.
My home-grown b/end framework cuts this down to between 5min - 20min (one PR only) for the same API/endpoint addition.
However, I only use my home-grown b/end stack for full webapp development, when a client wants a new system, and only when that client is not a technology company, or doesn't have an in-house development team.
That's because most businesses are using what their tech teams mandate, which IME is either Java, C# or (not as common around here) a Python or Node b/end.
Once(!) I had a dev manager agree that velocity for MVP and velocity for maintenance is a higher priority than conforming to any of the current b/end languages, which included but was not limited to C++, Java, Kotlin, Python, Node, React, Ruby, MSSQL, MySQL ... etc.
A few months later, on a call with him he expressed how fast his dev team was iterating on my stack when extending the system. Currently they are stuck on a lot of legacy that has to be extended, but they are definitely thinking about b/end in a new way now.
[1] Assuming the usual workflow of PR for ORM changes, then PR for data object layer + unit tests, then PR for domain object layer + unit tests, then PR for business logic + unit tests.
Facebook is in many ways terrible. it can be hard to work out what to do, it can be glitchy (for example updates can be slow - stuff is presumably cached and takes time to update) and its VERY hard to manage reasonably active FB groups.
https://www.npmjs.com/package/react?activeTab=dependencies
https://www.npmjs.com/package/react-dom?activeTab=dependenci...
If that's too heavy by itself, preact might be easier/simpler than reinventing the wheel.
PREACH. And many site actions can be handle server-side without degrading the user experience.
I sometimes long for the simplicity of something like Windows Forms.
Sure, with the knowledge I have now I could set up a whole MVC model with event based updates to form state, but at some point you're just rewriting the complex libraries that you're trying to avoid.
Good interactive state management, especially when you're tying to combine it with a stateless protocol like HTTP, is very difficult. Every complex library that's out there started off as a "simple way" of doing things.
There's nothing stopping you from doing Windows Forms applications of course, and the modern evolution of ASP.NET does look pretty inviting if you're over all that frontend crap, but don't underestimate the problem libraries like Angular solve. Microsoft tried it with Blazor, and that'll quickly get you a website that's a hundred megabytes in size because it downloads all of .NET into your browser.
I used React at my previous job and it feels like a nightmare thinking about it again. Hotwire is a simpler approach that pays off.
In my view one of the first things you should be doing when working on a web-app is to work out where you can place them, rather than trying to avoid.
For context, the Dart Flutter stack started out with the concept to make web apps faster....
https://pragprog.com/titles/jmnative/hotwire-native-for-rail...
In 2020 I took over the skeleton team of a startup. My main (junior) developer had a bit of Android and Kotlin experience; not a lot. We had no resources to get more developers. I was focusing on building the backend, which barely existed at that time so it was all new code. I picked spring boot and Kotlin for that.
We needed web and IOS apps in addition to Android and the Android app (the only thing that had been built) was a bit of a mess and honestly there wasn't much worth keeping. And with just one developer it was clear the replacement was going to be a web app. The obvious thing would have been to potty train my junior developer on react/typescript and hope that after a few months he would become a bit productive.
Instead I took a wild bet on kotlin-js. I honestly did not expect that would work. But it did. We did a brief research spike. Very successful. My junior developer did all the work. And we continued from there. I always had the plan to at some point just parachute in more developers and re-do it properly. But that never happened and nor is it needed.
Fast forward a few years, we still work with kotlin-js. Over the last four years that became more stable and better supported. It's great. It's a four year old UI code base and we're making lots of changes with confidence all the time. Kind of the gold standard for a good code base. It sure has its issues but it's under control.
Mostly I can't tell the difference whether I'm working on server or frontend code. It's all Kotlin. Kotlin multiplatform ensures we can reuse a lot of code on both. So the default place for code to go is in some multiplatform library. Which then ends up being used on both server and client.
We use a small, obscure UI framework for kotlin-js called fritz2. It emulates a lot of what react does but in a Kotlin friendly way (strongly typed, co-routines). We use tailwind for styling (after some adventures with other stuff) and are slowly converging on maybe adding daisyui to that mix (which is nice).
A lot of stuff in browsers is of course asynchronous and as it turns out, Kotlin's co-routines are awesome for that stuff. We have a lot of long running or recurring stuff happening in background co-routines. Handlers are suspend functions, etc. Integrating existing javascript frameworks is fairly straightforward. We have a few of those. Things like maplibre and a few other things.
The point here is that frontend code doesn't have to be Javascript and it doesn't have to be miserable like many Javascript projects become. Where you run your code and what language you pick for that are two independent choices. And if you have proper well designed code, it should be testable. Just because it runs in a browser is no good reason for that to stop being true.
I wouldn't recommend my choices four years ago. But at this point browsers run a lot more than just Javascript. Getting stuck with that stuff is a choice, not a necessity.
In fact I recently whipped up a front-end UI in a few days using the BAEL stack (Bootstrap Angular Electron). You still need the data layer of course, but this would be true of any UI framework unless you want to go full monolith like Rails (which I still fully believe is a superb solution).
And having done Rails, React, Angular, and a mishmash of other frameworks, libraries, and architectures, I can say that a “pure” Rails app out of the box is impressively good for most use cases, especially now with Turbo.
The way to alleviate those issues is not to bloat the frontend with things that shouldn't be in it nor is it to fragment the code base with never ending "sprinkle" of JS.
The best solution is something like Gleam's Lustre, where you use the right tool for the job, while keeping a coherent code base:
https://blog.nestful.app/p/gleams-lustre-is-frontend-develop...
This is just not going to be true. It’s an AI world and we need to funnel so much high velocity text and dynamic UIs to provide a high fidelity experience.
Investing in static server rendered pages is just a surrender, the long surrender that people desperately seek from the onslaught of JavaScript.
But it won’t happen, it will either be JS or another language, but server side pages is the wrong bet.
Nevertheless, the facts on the ground are the facts on the ground.