I don't really think server-side rendering is an important reason why most people don't use web components. Most web applications that use components of any sort are largely SPAs of some variety and don't care one bit about server-side rendering. In my opinion, the main reason web components aren't widely used is that the alternatives are just better. I'm yet to see a strong case being made where web components are favorable for a reason other than purism.
I read a lot of blog posts from both sides in the most recent big "web components vs frameworks" brouhaha a month or so ago. The conclusion I came to was that both sides' arguments were mostly correct but orthogonal to each other, so neither side is ever going to convince the other because they're not really seeking the same goal.
The pro-web-component folks argue that WCs are standardised and will continue to work basically indefinitely regardless of the ebbs and flows of popular libraries, and because it's easier to progressively enhance and therefore more accessible. This is correct, and those points are valuable if what you seek is a way to add resilient islands of interactivity to your document.
The pro-framework folks argue that in their model where the entire UI is a function of state, WCs are incredibly cumbersome, because all you really want from a component is to be a smaller function with which you can compose part of the whole. They are not nearly as concerned with maintaining that component under React vX in five year's time, because the entire system is written in React vX. The components are not maintained or used as individual units unless you are shipping a library. This is also correct, and those points are valuable if what you seek is an effective way to break down complexity in your application UI.
Of course, many (maybe even most?) SPAs should be documents with progressive interactivity—it involves less fighting with the fundamentals of the web (which is, ultimately, still based around documents) and therefore offers better performance and accessibility with less effort. SSR for frameworks is an absurdly complex solution to a problem that doesn't exist if you can just send a complete document right to the browser.
But, likewise, implementing a genuine high-interactivity web application using web components would be far more difficult than using a framework, and I am certain would result in re-implementing some sort of reactive UI/function-of-state paradigm anyway.
Personally I think it's kind of a pointless debate, until and unless we get something that is close enough to <insert popular JS framework here> + SSR built into the browser. I don't know how that would work or whether it's even possible, but at that point the set of standards would actually cover all of the problems everyone is trying to solve and it would be worth arguing whether or not to use them.
> The pro-web-component folks argue that WCs are standardised and will continue to work basically indefinitely regardless of the ebbs and flows of popular libraries, and because it's easier to progressively enhance and therefore more accessible. This is correct, and those points are valuable if what you seek is a way to add resilient islands of interactivity to your document.
I'm not sure that's even that correct. If you write code using browser APIs that are currently standardised, your code will work indefinitely, whether or not you use Web Components, React, or jQuery. Hell, we used to have jQuery-based components with the whole $(...).datepicker() system. Yes, it has its flaws, but it'll still work today if that's what your want from it.
Secondly, I don't think Web Components do work well from a progressive enhancement perspective. Progressive enhancement is starting from the bare minimum (i.e. just HTML and some styles, if that), and progressively adding features to that initial state that don't prevent any of the previously-added features from working. So you start with an HTML form skeleton, and you add some validation attributes, you add an event handler that calls fetch instead of reloading the page, you extend the address input so that it suggests real addresses - but the whole time, the HTML form is still there and it still works.
But without Javascript, Web Components are essentially empty holes that can't do anything. They don't progressively enhance anything. You can to a certain extent wrap existing elements, you can take the isomorphic approach described in this post, etc. But even when you do that, the browser will replace the contents of the element with the component's rendered output when the Javascript comes online, potentially deleting any user inputs if they'd interacted with the previous context. If your aim is true progressive enhancement, you should probably look in a different direction.
In fairness, I don't disagree that these claims are often made by Web Component proponents. But I think they're the wrong claims to be making. Web Components seem to be most useful if you're trying to isolate a complex stateful component written by one person or team and ended it into a complex stressful application written by a different person or team. They're essentially like lightweight iframes. This is really useful, and should be promoted more - but it's also quite a specific use.
> I'm not sure that's even that correct. If you write code using browser APIs that are currently standardised, your code will work indefinitely, whether or not you use Web Components, React, or jQuery.
The most charitable interpretation of this argument is that framework specific component libraries assume for the most part that you're using that specific framework. The documentation for popular React component libraries like shadcn are largely incomprehensible if you're not using React. Libraries like Shoelace (now being renamed to Web Awesome) make no such assumptions. You can just drop a script tag into your page's markup and get started without having to care about (or even be aware of) the fact that Shoelace uses Lit internally.
> But without Javascript, Web Components are essentially empty holes that can't do anything. They don't progressively enhance anything.
This is not true if you're using the new Declarative Shadow DOM API [1]. You literally just add a template tag with a shadowroot mode attribute inside your custom element, and then the component works without JavaScript. When (or if) the JavaScript loads, you simply check for the existence of a server-rendered shadow root using `internals.shadowRoot`. If a shadow root already exists then you don't have to replace anything, and you can attach your event listeners to the pre-existing shadow root (i.e. component hydration).
The only reason I avoid linking to that MDN page is because I think it does a bad job of actually assembling all of the pieces together into a working example. That's why I linked specifically to the component hydration section, which assembles it all into a functioning component complete with an actual class and constructor. The code example in that specific section doesn't appear to use any deprecated or non-standard features. Otherwise, I normally do prefer MDN as an authoritative source of documentation.
The MDN page has the deprecation notice for the `shadowRoot` property.
Yes, the examples on your page are way more comprehensive. Even with compatibility support for old Chrome versions. Unfortunately, that support example will also break your code on other browsers.
People may want to read it anyway, and just fix the problem. It being outdated doesn't make the explanation bad.
What's deprecated is the `shadowroot` attribute on the template element, which the example I linked to does not use. The second line of code in that example is `<template shadowrootmode="open">`.
All of this is mentioned at the very top of the article where it says: "Note that the specification for this feature changed in 2023 (including a rename of shadowroot to shadowrootmode), and the most up to date standardized versions of all parts of the feature landed in Chrome version 124."
> Unfortunately, that support example will also break your code on other browsers.
> You literally just add a template tag with a shadowroot mode attribute inside your custom element, and then the component works without JavaScript.
Wtf is a shadowroot?
I'm of increasing confidence that the entire project to unify document display and application runtime has utterly failed and there's no way (and no benefit) to resuscitate it. We need two different internets: one for interactive applications and one for document browsing.
In the Document Object Model (DOM), every document has a root node, which you can retrieve by calling `getRootNode()` on any node in the document (e.g. `document.getRootNode()` or `document.body.getRootNode()`).
Custom Elements can have their own "shadow" document that's semi-isolated from the parent document, and the root node of that shadow document is called the shadow root.
The idea is to be able to create your own HTML elements (which also have their own hidden DOM). If you enable a setting in Chrome's Devtools (Show user agent shadow DOM) [1], you can actually see the hidden shadow structure of built in HTML Elements: https://www.youtube.com/watch?v=Vzj3jSUbMtI&t=291s
> WCs are standardised and will continue to work basically indefinitely regardless of the ebbs and flows of popular libraries
Help me understand; is this suggesting that older frameworks/libraries are expected to stop working at some point? I'm shipping 10+ year old libraries in production without incident. Really don't understand the "resilience" argument for WCs.
Author here — have you never returned to an old project after months or years and found it difficult to get working again? Maybe a dependency doesn’t work with the current version of your operating system, or you’d like to upgrade some component that now conflicts with another?
I’m not saying avoiding this needs to be a top priority on every project; some things just will not exist without ongoing effort to maintain them, and that’s okay! But for the other things, cutting the dependency surface area to an absolute minimum makes them significantly more resilient.
I mean you don't have to look far, it was just two years ago that React 18 was released, where the way detached nodes are handled changed. That broke integrations with pretty much every legacy plugin that needed to do that, and you had to wrap it specifically for React. If you live fully immersed in the React ecosystem, then maybe it wasn't a problem, but for a lot of projects, it was a very painful upgrade. The change itself wasn't large code wise, but the fact that if you wanted to upgrade React for some reason, you also had to go touch and test all these old pieces of code, really did not do anyone any favors.
react uses those same web standards. It will not stop working either. You may not be able to upgrade to the latest React version assuming there are breaking changes, but the same can be said for the various frameworks people use to create web components.
I might be nitpicking but, the transpiled version of React is the one that uses those standards. If you are thinking of trying to get a 5+ years old project working in a development environment you might effectively be out of luck.
To reduce the argument to a simpler one, this is effectively the difference between using vanilla js to build anything Vs using any abstraction over it that has a number of dependencies you don't control that are effectively a working solution of a dependency hell in a specific moment of time.
Yes, but then you are not comparing apples. Web components projects without a build step and relying on only stable standards are a subset of all web component projects. Same with React projects, though the ratios of without build vs. with build are obviously different.
if JSX is your issue, that means a web component framework like Stencil is just as bad as React. But then again you can use React with htm to avoid jsx if it really bothers you.
A React component only works in React. WCs are self-contained and work like ordinary HTML elements. If in the future you don't use React anymore, you can keep using your WCs without change, but not your React components.
> SSR for frameworks is an absurdly complex solution to a problem that doesn't exist if you can just send a complete document right to the browser.
So, React is just a view library. It's made to map state to DOM changes. Except it's not just a view library, because if you write an app with React, you are writing a React app. You slice and dice your view up and put them into React components. Now, since you want to use your React components on the server and not slice and dice them up again in another view library, you morph that DOM-manipulating lib into a templating engine that generates static HTML. Except you call it "server side rendering", because "templating engine" sounds so 2010 and "static site generation" is also not the right term, because the output isn't a static HTML page, but can change with every page request. Of course, the "React" part of React is completely ditched in SSR, because you don't need to continuously modify the DOM based on your state. React on the server is what PHP templates have been doing forever. Outputting HTML in PHP is "view = f(state)". We've come full circle.
I sometimes wonder if younger web devs even understand that SSR isn't, like, really rendering a page on the server, but basically means "we produce frickin HTML like we've been for the last 30 years, except our tools are now 10x as complex to such a point that we don't understand them anymore", because 90% of our view library does nothing on the server, because, you know, there is no DOM. There is only HTML as a string send over HTTP.
Having said that: Your analysis is completely correct. The discussion really is orthogonal and I'm not sure I see the point in rendering web components on the server, since most web components benefit from interactivity through JS. If I merely encapsulate a bunch of HTML tags, I can ditch the custom element entirely and send the HTML tags down the wire by themselves.
I think when trying to understand SSR, it's worth remembering what actually was happening with older systems like PHP. You say that outputting HTML in PHP is `view = f(state)`, and this is to a certain extent true, but it meant that anything on the client side was then `view = f_client(state_client, f_server(state_server))`, and getting applications to remain sane with anything more than the bare minimum of interactivity was painful. I remember this era, and debugging constant state issues was very much the reason that we ended to moving towards client-side web frameworks: you only have to worry about one `f` and (mostly) one `state`.
In my experience, most of the people exploring SSR are very acutely aware of that era, both the benefits and the negatives, and are trying to build systems that allow developers to retain a consistent overall view function without having to maintain that across multiple codebases in multiple languages. That doesn't just mean rendering HTML, but also ensuring that the frontend and backend agree on how to update that HTML if the state ever changes, or ensuring that progressive enhancement is possible while not requiring the entire site to reload every time I update a form field.
Now I think there's a good argument to be made that many applications don't need this additional complexity - they can continue to be rendered entirely server-side, or entirely client-side, and not need to share state at all. But if you do need state to exist in both the server and the client, modern SSR frameworks are typically a pretty good approach.
I can't comment on react, ssr etc, but one thing that I've been wondering about as a php developer is why even use custom elements at all, let alone web components?
As you say, why not just send normal, raw html down the wire?
I suspect I'm missing something though. Perhaps it's to do with being able to encapsulate/isolate each component's css and js, such that if you mix components across projects (or have different teams working on different components), their css and js have no chance of conflicting. Even if you are a single team, it would still, presumably, allow you to import existing components from external libraries etc...
Well, even in a server-side PHP app, you have some client-side stuff for better interactivity and a smoother experience. Custom elements let you encapsulate this stuff. It's really not necessary, but if you want to reuse or redistribute them, they are pretty nice and self-contained (mostly).
> Most web applications that use components of any sort are largely SPAs
How did you get to that conclusion? Honestly asking because my hunch is that it would be the opposite.
> I'm yet to see a strong case being made where web components are favorable for a reason other than purism.
IMO it's wrong to see web components as a universal solution to all client-side use cases.
Web components are amazing for distributing widgets to third party users. From users authoring HTML in any number of environments to a way to plug modern JS stuff into SSR backend frameworks.
Back in 2016 I worked in library of widgets for interactive education ebooks. People producing the ebooks could barely write HTML so being able to add interactivity with configurable custom tags was extremely easy for them. Currently I'm working in an embeddable and customizable audio player with web components. Typically you'd use iframes which are quite heavy and have serious limitations. In both use cases, web components were the right choice.
Websites built with JavaScript frameworks need SSR because of SEO. Applications don't, so most are SPAs, because they can be. The fully decoupled SPA is a special architecture. Complexity isn't just lessened, a lot of it vanishes. Poof, bye bye headaches. If you can use it, you do, and most web applications can, so they do.
Here's one case that I think is strong: At work some teams use JSF, some use React. We need to deliver components to both. We chose lit and we're very happy.
From my personal experience the lack of SSR is one reason I don’t use web components. But that said there isn’t a particularly persuasive case to use them either, other than it being nice to use standards.
The thing that is lost on me with all of these webcomponents solutions is that they inevitably rely on tagged templates to do the heaviest lifting.
I don't want to write strings, I want to write statically analyzable code. IDEs do some heavy lifting here for visual hints, and there are a few html-in-js linting solutions, but none of them are very compelling.
Static analysis works on strings: source code. Similarly, it can, and does work on the strings in a tagged template literal.
Lit's templates are HTML with bindings. They're very analyzable, including checking for valid structure, associating tags with definitions, type-checking bindings. The tools that do this parse template strings, build an AST, and run analysis passes just like other source analysis tools.
Developers get standard intellisense features like jump-to-definition, hover-over docs, errors on invalid properties and events, etc.
But also... templating is an implementation concern.
To build a web component you don't have to use a tagged-template-based system. There are template systems that use JSX. You can use Preact, or React themselves. And your component users don't ever have to know what template system you chose to implement your component.
> You’d be forgiven for thinking you were looking at a React component! Frankly, I don’t see a fundamental difference between these and frameworks like Svelte or Vue
Regardless of whether you should be forgiven for thinking it’s a react component, it omits it’s biggest strength - JSX is just JavaScript function calls, and thus can be composed as JavaScript and type checked as java/typescript.
This to me is the fundamental difference between React/JSX and other approaches. Any framework that gives up on type checking for the view layer is a non-starter in my books.
Vue, Svelte, web components, and vanilla only execute code that is marked as a callback.
Example:
let x = 0
var counter = computed(() => ... )
In this Vue snippet of an SFC, the callback in the computed executes in reaction to some change without invoking the assignment of x (obviously a trivial case here).
DOM + JavaScript is the same
React invokes the entire component function and all calls and allocations in the path of the component function. The mental model is inverted in a sense.
This is one of the reasons React generally performs more poorly in both speed and space.
Enhance is super cool and the fact that it can be called from basically any server language makes web components really powerful & reusable.
I work on one of the underlying libraries that provide it said portability, Extism, and I love to see how WebAssembly actually bring some of the coolest parts of the web browser into other applications.
Great stuff, but if you are writing your own components just stick to light DOM and do not write code to generate DOM elements for the component to look right.
Let's say you are making a splitter component. Do not do this:
Front-end people keep using the word “isomorphic”.
I don’t think it means what they think it means.
To me, two types of values are isomorphic if they share the same cardinality. The isomorphism between two isomorphic types is a pair of morphisms (functions) that when composed one after the other is the same as doing nothing.
This word is a couple hundred years old, so it long predates front-end development. It has a pretty specific and established meaning in mathematics. It’s not right to use this word when you just mean “shared code”.
Author here! If a group of people use a word and also all understand what others in the group are saying when they use it, then the word does in fact mean what they think it means :)
I have no math background but can read ancient Greek pretty fluently. “Isomorphic” is simply a Greek compound word meaning “same shape.” The reason people use “isomorphic” for server side rendering + hydration is that you can create an application where all the components have the “same shape” in various ways (i.e., the same source code, arguments, templating, etc.)
A technical term having a different meaning in two different fields doesn’t mean that the version with which you’re more familiar is correct and everyone else is wrong. That would be like commenting on an article entitled “Syrian rebel forces take Damascus” to say “National-security people keep using the word ‘forces’. I don’t think it means what they think it means. To me, force equals mass times acceleration.”
That's such a silly analogy it's tantamount to constructing a straw man.
I don't have a background in mathematics either, but I think it's quite clear that the use of "isomorphic" here as a stand in for code sharing is forced, to say the least.
It's the same kind of silliness as using the term "tree shaking" in place of dead code elimination (which is, you know, an established technical term in the same field). In this instance however, instead of using a fancy-sounding word (which I think is a little pretentious), they've gone with an analogy with a less obvious meaning (what are you shaking from the tree? Are the good bits falling off also?).
As much fun as it can be to poke friendly fun at the frontend folks, their problem domain surface area is actually large enough to demand lots of terminology.
I’ve been reflecting a lot on life lately and it’s occurred to me from time to time, especially looking at some of my career work, that I’ve been spending a lot of time on bullshit. Like gosh, I feel like they’ve been talking about the right way to hydrate these components forever and it still feels like such an unnatural solution.