<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://bridgetownrb.com/" version="1.3.1">Bridgetown</generator><link href="https://www.spicyweb.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.spicyweb.dev/" rel="alternate" type="text/html" /><updated>2026-06-02T10:43:33-07:00</updated><id>https://www.spicyweb.dev/feed.xml</id><title type="html">The Spicy Web</title><subtitle>Join a fabulous community of developers learning vanilla web specs like HTTP, HTML, CSS, JavaScript, &amp; Web Components</subtitle><entry><title type="html">Sunsetting The Spicy Web, and What Comes Next</title><link href="https://www.spicyweb.dev/sunsetting-the-spicy-web/" rel="alternate" type="text/html" title="Sunsetting The Spicy Web, and What Comes Next" /><published>2026-04-23T00:00:00-07:00</published><updated>2026-04-23T00:00:00-07:00</updated><id>repo://posts.collection/_posts/2026-04-23-sunsetting-the-spicy-web.md</id><content type="html" xml:base="https://www.spicyweb.dev/sunsetting-the-spicy-web/">&lt;p&gt;This is a post I’ve been putting off for a long time, but it really is time to make a clean break and share with y’all what my plans are and where you can go to keep the dream alive of an HTML-first, indie-friendly, “vanilla” open web.&lt;/p&gt;

&lt;p&gt;When I first launched &lt;strong&gt;The Spicy Web Discord&lt;/strong&gt; in February 2021, the goal was to create a destination where people could go and talk specifically about how to build awesome websites and applications without needing to reach for gargantuan client-side JavaScript frameworks or obtuse build processes. We were shouting to the world that with a few JS “sprinkles” (which I preferred to call spices, hence the name!), you could craft some truly powerful and delightful experiences which also “go with the grain of the web” and don’t lock folks into vendor-specific idioms. We were putting a stake in the ground that modern CSS was getting, like, really &lt;em&gt;really&lt;/em&gt; &lt;strong&gt;really&lt;/strong&gt; good, and the perceived benefits of utility class frameworks Tailwind were far less potent than as advertised. We were making the bold claim that if you were to pick a website framework, it needed to provide you great leeway to get “down to the metal” and access the latest &amp;amp; greatest &lt;em&gt;native&lt;/em&gt; web APIs, rather than get in your way with its own slew of techniques.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We were right about all of these approaches!&lt;/strong&gt; And many more people have caught on in the past five years, with growing popularity of lightweight libraries like htmx, Lit, many flavors of signals, design systems like Web Awesome, and a whole lot more becoming go-to solutions for a wide variety of applications.&lt;/p&gt;

&lt;p&gt;But something else has happened too in the past five years. The so-called &lt;em&gt;agentic revolution&lt;/em&gt; in software engineering has sucked all the oxygen out of the room, and endless debates and controversies around LLMs, agents, generative AI companies, models, cybersecurity, and even the very nature of open source itself threatens to tear the fabric of our industry apart—nay, I would argue it already has.&lt;/p&gt;

&lt;p&gt;But before I continue down that conversational path, let me first get you up to speed on the projects I’ve worked on since The Spicy Web Discord was founded in 2021.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In 2022&lt;/strong&gt;, I launched this website for The Spicy Web, and for a while it was just a place for me to write the occasional blog post, whether a tutorial of sorts or more of a think piece. Most of those did very well—some were widely shared and read thousands of times. &lt;a href=&quot;https://www.spicyweb.dev/the-great-gaslighting-of-the-js-age/&quot;&gt;The Great Gaslighting of the JavaScript Era&lt;/a&gt;, published in 2023, definitely escaped containment and brought a whole lot of new folks into the Discord. (&lt;em&gt;Quick aside:&lt;/em&gt; three years later, I actually believe the JavaScript language/ecosystem is in pretty good shape. ES modules have become the norm, Node/NPM is extremely capable out of the box, esbuild is absolutely brilliant, JavaScript itself has amassed a pretty decent stdlib, and in general you can use JS to write both frontend and full-stack apps in a fairly straightforward &amp;amp; maintainable manner. So congratulations, web!)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In 2023&lt;/strong&gt;, I launched my first (and, alas, only) course for The Spicy Web, called &lt;a href=&quot;https://www.spicyweb.dev/css-nouveau&quot;&gt;CSS Nouveau&lt;/a&gt;. It was a paid subscription product, and it did rather well right at first, but then sales dropped off a cliff in 2024 and never recovered. That was also around the time a lot of other folks I knew who were offering education products saw their sales tank. Gee, thanks AI! 🙄 (And inflation, and Big Tech layoffs, and the U.S. elections, and, and…) So now CSS Nouveau is free, and shall always remain so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also in 2023&lt;/strong&gt;, I felt the need to create more of an “aggregator” news-style blog centered on the nitty-gritty of web development topics, so I designed &amp;amp; launched &lt;a href=&quot;https://thathtml.blog&quot;&gt;That HTML Blog&lt;/a&gt; and eventually established its accompanying newsletter. In hindsight, I might have done a better job of establishing connective tissue between The Spicy Web and That HTML Blog, rather than having them live parallel lives, but at this point that’s actually a good thing as you’ll soon see.&lt;/p&gt;

&lt;p&gt;Which brings us to recent changes. &lt;strong&gt;By the summer of 2025&lt;/strong&gt;, I felt that the particular mission of “The Spicy Web” had run out of gas, and there was a much more urgent need to focus on the massive sea change which had engulfed the developer community. For those of us who are deeply committed to the &lt;em&gt;pro-craft movement&lt;/em&gt;, standing firm on the principles that handcrafted code, art, and communication are &lt;em&gt;vitally important&lt;/em&gt; and that the &lt;strong&gt;slop machines&lt;/strong&gt; (aka “generative AI”) are a net negative for society and serve the interests of a fascist billionaire class not creative workers, it was important to create a destination where such matters could be discussed freely and people could be supported in their AI skepticism and the fight against unethical LLMs. (&lt;strong&gt;Spoiler alert:&lt;/strong&gt; all LLMs as we currently know them are unethical.)&lt;/p&gt;

&lt;p&gt;So I started a brand-new project called the &lt;strong&gt;Human Web Collective (HWC)&lt;/strong&gt;, and renamed The Spicy Web Discord to &lt;a href=&quot;https://discord.gg/CUuYVH7Qa9&quot;&gt;The Human Web Collective Discord&lt;/a&gt;. I also launched a new Reddit-style forum site built with PieFed to encourage more open sharing of links and resulting conversation. While the Discord server has been thriving, the forum never really took off (even though I use it every day to engage with other “Threadiverse” servers running Lemmy and PieFed). I haven’t given up on it completely, but my more immediate focus is to launch a proper “landing page” for the HWC, featuring links to apps and resources which are slop-free and encourage people not to let their brains get scrambled by chatbots and sloppy agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My personal stance is quite clear.&lt;/strong&gt; I will &lt;em&gt;never&lt;/em&gt; use LLMs to write code for me, nor will I ask a chatbot to write or edit any of my prose, or create images, or produce videos, or any other creative endeavor. I stand 100% in opposition to AI slop. This is a core value of mine, &lt;em&gt;one which has already cost me some business&lt;/em&gt; but I’d much rather be “poor” and keep my soul intact than gain the whole world and lose my soul in the process. So I’m dead serious about this, and I know a lot of you out there are as well.&lt;/p&gt;

&lt;p&gt;However, just because the moment is serious doesn’t mean we can’t have some fun. So one of the goals of the HWC is to &lt;em&gt;celebrate&lt;/em&gt; human creativity &amp;amp; the indie web, and perhaps to laugh a bit at the absurdity of modern life. There’s much still to appreciate and explore, both online and off. And so ultimately the Human Web Collective isn’t simply a “negative” push against some ill, but a positive push towards a better world. &lt;strong&gt;I hope you’ll join us in that effort.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OK, so what’s the final tally? I said I was sunsetting The Spicy Web, and that means I’ll be adding a disclaimer to the top of this site saying it’s been archived, and I’ll switch off logins. It’ll just be a static site, frozen in time. &lt;em&gt;Which is fine!&lt;/em&gt; That’s the beauty of a static site, it can live on “forever”.&lt;/p&gt;

&lt;p&gt;And as mentioned, I’ll be putting my attention into all things Human Web Collective, as well as spin back up writing for That HTML Blog to highlight groovy new web APIs as well as the micro-libraries which utilize them to offer awesome DX for vanilla-adjacent projects. So the original mission of The Spicy Web will live on in one form or another, finding a place in corners of the HWC Discord as well as posts on the blog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This feels good.&lt;/strong&gt; I’m certainly bummed that I wasn’t able to keep up the momentum of working on new courses, but I’m confident there is plenty of good material out there now from other sources to help you learn how to build for the web utilizing all that the web itself has to offer. And as for all the threats developers now face to their craft and their livelihoods, &lt;a href=&quot;https://discord.gg/CUuYVH7Qa9&quot;&gt;The Human Web Collective&lt;/a&gt; is here to give you a voice. &lt;em&gt;Coding is dead, long live coding.&lt;/em&gt; 🫡&lt;/p&gt;</content><author><name></name></author><category term="bestpractices" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" /><media:content medium="image" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">I Made a Squircle Button (and of Course It’s a Web Component)</title><link href="https://www.spicyweb.dev/i-built-a-squricle-button/" rel="alternate" type="text/html" title="I Made a Squircle Button (and of Course It’s a Web Component)" /><published>2025-01-26T00:00:00-08:00</published><updated>2025-01-26T00:00:00-08:00</updated><id>repo://posts.collection/_posts/2025-01-26-i-built-a-squricle-button.md</id><content type="html" xml:base="https://www.spicyweb.dev/i-built-a-squricle-button/">&lt;aside&gt;
  &lt;p&gt;&lt;strong&gt;January 30, 2025 Addendum:&lt;/strong&gt; I was further inspired to attempt &lt;em&gt;squirclization&lt;/em&gt; (is that a word?!) of images in a grid of thumbnails, &lt;a href=&quot;https://thathtml.blog/2025/01/squircle-images-on-the-loose/&quot;&gt;which you can take a look at here&lt;/a&gt;. What fun! ☺️&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;Before we get into the nitty-gritty, just what &lt;em&gt;is&lt;/em&gt; a squircle? And how it related to a superellipse?&lt;/p&gt;

&lt;p&gt;Here’s a definition I found from &lt;a href=&quot;https://medium.com/@nikolskayaolia/an-easy-way-to-implement-smooth-shapes-such-as-superellipse-and-squircle-into-a-user-interface-a5ba4e1139ed&quot;&gt;this article by Olga Nikolskaya&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The Superellipse is the name given to a family of shapes and the most popular of them is Squircle, the shape intermediate between circle and square. The appearance of squircle seems optically balanced: no sharpness, no angles…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, unlike typical CSS-based “rounded corners” you see on the web where the point at which the straight side angle starts to curve is very noticeable, a squircle offers a very gradual shift from a side to a corner. This was a major topic of conversation back when Apple first released iOS 7, as &lt;a href=&quot;https://hackernoon.com/apples-icons-have-that-shape-for-a-very-good-reason-720d4e7c8a14&quot;&gt;Mark Stanton at HackerNoon points out&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A ‘secret’ of Apple’s physical products is that they avoid tangency (where a radius meets a line at a single point) and craft their surfaces with what’s called curvature continuity. Once you know how to spot it on products, you’re likely to start seeing it (or more likely the lack of it) all around you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;iOS 7 brought that hardware curvature to their software, but unfortunately that aesthetic never made it over to the web where we still just have a very boring (yet still extremely useful) &lt;code class=&quot;highlighter-rouge&quot;&gt;border-radius&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I got the idea to investigate this topic after noticing (of all things) a Help Scout Beacon button installed on a client app I was working on. I kept looking at the button and wondering why the shape of it seemed unusual to me, and it finally dawned on me that I was looking at a squircle button and not a typical button with CSS rounded corners. (&lt;a href=&quot;https://docs.helpscout.com/article/1250-beacon-jumpstart-guide&quot;&gt;You can see it in action here.&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Upon further investigation, I discovered they were using SVG shapes on the left and right sides of the button to provide the necessary curvature. That’s a pretty interesting technique, but it got me wondering if there might be another approach that, yes, still uses SVG yet not for the “endcaps” but for the entire shape of a button.&lt;/p&gt;

&lt;h2 id=&quot;oops-i-clipped-my-path&quot;&gt;Oops I Clipped My Path&lt;/h2&gt;

&lt;p&gt;A bit of sleuthing on DuckDuckGo turned up &lt;a href=&quot;https://www.simeongriggs.dev/responsive-extendable-squircles-with-svg-and-css&quot;&gt;this fascinating article by Simeon Griggs&lt;/a&gt; (funny enough, he references Olga’s article I mentioned above!). The trick is to define a &lt;code class=&quot;highlighter-rouge&quot;&gt;clipPath&lt;/code&gt; inside of an SVG element, and then reference that using a &lt;code class=&quot;highlighter-rouge&quot;&gt;clip-path&lt;/code&gt; CSS property. From Simeon’s example:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;svg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;width=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;10&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;height=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;10&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;viewBox=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0 0 10 10&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;clipPath&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;squircleClip&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;clipPathUnits=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;objectBoundingBox&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;path&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fill=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;red&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;stroke=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;none&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;d=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;M 0,0.5 C 0,0 0,0 0.5,0 S 1,0 1,0.5 1,1 0.5,1 0,1 0,0.5&quot;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/clipPath&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;.squircle&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;clip-path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url(#squircleClip)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Slap that on a tag and you’ve got yourself a squircle.&lt;/p&gt;

&lt;p&gt;I was excited to try this out on a button, so naturally there was only way I’d want to package this up into a reusable component: &lt;strong&gt;by building a web component&lt;/strong&gt;. And of course, once I got the initial technique all squared (&lt;em&gt;squircled?&lt;/em&gt;) away, I needed to take it just a few steps further.&lt;/p&gt;

&lt;p&gt;And here’s the result! Say hello to &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;sq-button&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;iframe height=&quot;450&quot; style=&quot;width: 100%; margin-block: var(--size-6); display: block;&quot; scrolling=&quot;no&quot; title=&quot;Squircle Web Component Button&quot; src=&quot;https://codepen.io/jaredcwhite/embed/JoPeJar?default-tab=html%2Cresult&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/jaredcwhite/pen/JoPeJar&quot;&gt;
  Squircle Web Component Button&lt;/a&gt; by Jared White (&lt;a href=&quot;https://codepen.io/jaredcwhite&quot;&gt;@jaredcwhite&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;

&lt;p&gt;So yes indeed, we have that appealing shape without any sharpness to the curvature. But on top of that, I added an eye-catching gradient background (thanks &lt;code class=&quot;highlighter-rouge&quot;&gt;oklch&lt;/code&gt;!) to the button—which when you hover over it, &lt;mark&gt;it rotates&lt;/mark&gt;! &lt;strong&gt;Whoa!&lt;/strong&gt; How did I do that?&lt;/p&gt;

&lt;p&gt;That’s all thanks to the magic of &lt;code class=&quot;highlighter-rouge&quot;&gt;@property&lt;/code&gt;, a way of defining &lt;mark&gt;typed&lt;/mark&gt; CSS custom properties. The typing is crucial, because if you were to attempt to define a value like &lt;code class=&quot;highlighter-rouge&quot;&gt;135deg&lt;/code&gt; for a new CSS custom property without using &lt;code class=&quot;highlighter-rouge&quot;&gt;@property&lt;/code&gt;, it wouldn’t be usable for a transition. By typing it as an &lt;strong&gt;angle&lt;/strong&gt; specifically, we gain animation functionality.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://caniuse.com/mdn-css_at-rules_property&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;@property&lt;/code&gt; only recently became available across all browsers&lt;/a&gt;, so bear that in mind—but as a nice bit of progressively-enhanced visual flourish, I’m quite impressed!&lt;/p&gt;

&lt;h2 id=&quot;drop-your-shadows&quot;&gt;Drop Your Shadows&lt;/h2&gt;

&lt;p&gt;I also added a drop shadow to the button. Note that when you’re using the &lt;code class=&quot;highlighter-rouge&quot;&gt;clip-path&lt;/code&gt; technique, you can’t utilize &lt;code class=&quot;highlighter-rouge&quot;&gt;box-shadow&lt;/code&gt; any longer because the clipped element will no longer display the shadow either. &lt;em&gt;derp&lt;/em&gt; 🤪&lt;/p&gt;

&lt;p&gt;Thankfully, &lt;code class=&quot;highlighter-rouge&quot;&gt;filter&lt;/code&gt; is still an option for applying an SVG-based drop shadow to the host element. I made the shadow, the background gradient, and the hover animation duration all customizable using CSS variables, and if I were to spend more time with this I’d make a bunch of other aspects of the component’s appearance customizable as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I really like my squircle button.&lt;/strong&gt; 🥰 I think I might introduce it to an upcoming app I plan to start working on soon, because when you’re so used to the typical “roundrect” buttons on the web, &lt;mark&gt;squircles really do stand out&lt;/mark&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Future Thoughts:&lt;/strong&gt; in a production application, I would of course use &lt;strong&gt;declarative shadow DOM&lt;/strong&gt; to render out these buttons so they don’t purely rely on JavaScript to render. As you might notice, I’m not actually using JavaScript to do anything in the CodePen except to inject HTML &amp;amp; CSS into the shadow DOM. This might also mean I’d break out the SVG into its own external file, as well as the styles (loading them via &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;link&amp;gt;&lt;/code&gt;), so that each declarative shadow DOM template is as minimal as possible. That’d require some performance testing for an optimal approach, as I’m not sure if a delay in loading an external SVG would cause any graphical glitching. That’s why this section is titled &lt;strong&gt;future&lt;/strong&gt; thoughts! &lt;mark&gt;Welcome to how my brain works.&lt;/mark&gt; 😅&lt;/p&gt;</content><author><name></name></author><category term="css" /><category term="webcomponents" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" /><media:content medium="image" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Creative Strategies for Surviving the AI-pocalypse</title><link href="https://www.spicyweb.dev/surviving-the-ai-pocalypse/" rel="alternate" type="text/html" title="Creative Strategies for Surviving the AI-pocalypse" /><published>2025-01-13T00:00:00-08:00</published><updated>2025-01-13T00:00:00-08:00</updated><id>repo://posts.collection/_posts/2025-01-13-surviving-the-ai-pocalypse.md</id><content type="html" xml:base="https://www.spicyweb.dev/surviving-the-ai-pocalypse/">&lt;p&gt;I am very sorry to inform you, but if you are a React / TypeScript frontend engineer a few years into your career, you are ground zero for getting replaced by “AI engineers”. &lt;a href=&quot;https://indieweb.social/@jaredwhite/113819479389377870&quot;&gt;Mark Zuckerberg hath decreed it to be so!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Big Tech is &lt;mark&gt;literally salivating&lt;/mark&gt; at the idea of taking &lt;em&gt;your&lt;/em&gt; job away and replacing it with automation at an industrial scale. Their plan seems to be to hire a bunch of low-wage prompt monkeys to coax AI agents into spitting out “working” React code and JSON APIs, and then handing that work off to a dwindling number of senior engineers for final polish and bug-fixing. And if you thought &lt;a href=&quot;https://www.spicyweb.dev/the-great-gaslighting-of-the-js-age/&quot;&gt;The Great Gaslighting of the JavaScript Era&lt;/a&gt; was finally behind us, oh you ain’t seen nuthin’ yet!&lt;/p&gt;

&lt;p&gt;If you are one of the senior engineers standing idly by while your industry is targeted for mass disruption (not the fun kind), well I can’t say I feel much empathy for you.&lt;/p&gt;

&lt;p&gt;And if you are one of those “prompt engineers” helping to take jobs away from qualified craftspeople, well, I’m genuinely sorry you have been &lt;mark&gt;trapped inside of a capitalist hellscape&lt;/mark&gt; which is more than willing to exploit you.&lt;/p&gt;

&lt;p&gt;But if you are a “mid-level” software developer, one of the “people engineers” whom Mark Zuckerberg claims will be replaced by “AI engineers”, I have some encouragement for you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is a place for you in this world.&lt;/strong&gt; Just probably not in Big Tech. You may need to downsize, move to an area with a lower cost of living, and get &lt;em&gt;real creative&lt;/em&gt; about how you find means of gainful employment.&lt;/p&gt;

&lt;p&gt;Here are a few ideas on how to weather the coming storm. Because trust me on this—&lt;em&gt;it is coming&lt;/em&gt;. Things will get worse before they get better. The Zucks of the world will see to that.&lt;/p&gt;

&lt;h2 id=&quot;local-first-community&quot;&gt;Local-first community&lt;/h2&gt;

&lt;p&gt;The pandemic really did a number on small local tech meetups. Many of them never recovered. It’s been pretty grim here in Portland, Oregon, I can tell you that.&lt;/p&gt;

&lt;p&gt;&lt;mark&gt;But hope is not lost.&lt;/mark&gt; New meetups spring up all the time. You should be putting a good deal of time and effort into finding these meetups and attending them as regularly as possible. And if you can’t find any? Start one! (Or two or three!)&lt;/p&gt;

&lt;p&gt;In short, wherever you are, you MUST find flesh-and-bloom humans and enjoy face-to-face time with them. Maybe there aren’t enough Programmers with a capital P in your local area, but usually you can find creatives in adjacent industries to network with.&lt;/p&gt;

&lt;p&gt;Your ability to survive the coming AI-pocalypse is largely dependent on your ability to network with &lt;mark&gt;real people in real life&lt;/mark&gt;. The Internet alone may have gotten you far, but it won’t get you far enough over the next few years.&lt;/p&gt;

&lt;h2 id=&quot;side-hustle-like-youve-never-hustled-before&quot;&gt;Side-hustle like you’ve never hustled before&lt;/h2&gt;

&lt;aside&gt;
  &lt;p&gt;&lt;strong&gt;January 26, 2025 Update:&lt;/strong&gt; I got a fair degree of pushback regarding this section on Mastodon and Discord. And I should have seen it coming, as a lot of folks are understandably weary of “hustle culture” and think in many ways it’s detrimental to the programming industry. And I get it, I really do. I personally can’t stand all the &lt;em&gt;l33tcode&lt;/em&gt; style competitions either and think “whiteboard interviews” are the very work of the devil. So yeah, I should have worded this section more artfully. As &lt;strong&gt;evie&lt;/strong&gt; in our Discord put it, I could have acknowledged it as necessary without &lt;mark&gt;valorizing it&lt;/mark&gt;. And &lt;a href=&quot;https://mamot.fr/@thibaultamartin/113831537405882001&quot;&gt;Thib on Mastodon&lt;/a&gt; would encourage a focus on soft skills and &lt;mark&gt;extra-professional activities&lt;/mark&gt; outside of programming as a way of standing out. With my mea culpa and these caveats out of the way, here is what I initially wrote:&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;It’s always been true that programmers do well when they have some extra irons in the fire besides whatever they’re tasked to do at their day job. Sometimes there is resentment along these lines however—&lt;em&gt;what, are you expecting me to work 9-5, 5 days a week, coding until my fingers ache, and THEN ALSO launch a SaaS and build a mobile app and release ten open source libraries at 11pm or 6am?!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yes.&lt;/strong&gt; 🤷🏻‍♂️&lt;/p&gt;

&lt;p&gt;I hate to break it to you, but I’ve met with many junior or mid-level programmers over the years for community conversation or coaching, and when I ask them questions such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;So what libraries have you written or contributed to?&lt;/li&gt;
  &lt;li&gt;What’s your blog URL?&lt;/li&gt;
  &lt;li&gt;What meetups are you attending or have started?&lt;/li&gt;
  &lt;li&gt;Got an app idea in the works?&lt;/li&gt;
  &lt;li&gt;Any cool new techniques you’re willing to share?&lt;/li&gt;
  &lt;li&gt;Where are the repos of demos you’ve worked on?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All too often the answers are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ah, well I haven’t done that&lt;/li&gt;
  &lt;li&gt;I don’t have a blog&lt;/li&gt;
  &lt;li&gt;I haven’t been to any meetups in ages&lt;/li&gt;
  &lt;li&gt;I don’t have any app ideas&lt;/li&gt;
  &lt;li&gt;Nothing comes to mind&lt;/li&gt;
  &lt;li&gt;Oops, I don’t have any good demos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This essay isn’t a deep-dive into time management techniques and productivity tips to help you prioritize these things, but I will just flat out say you &lt;em&gt;must&lt;/em&gt; be spending a &lt;mark&gt;significant portion of your time&lt;/mark&gt; focusing on some or all of these initiatives.&lt;/p&gt;

&lt;p&gt;And in case you’re wondering, I’m happy to &lt;em&gt;personally help you&lt;/em&gt; on the blogging front at least—&lt;a href=&quot;https://www.bridgetownrb.com&quot;&gt;I maintain a blogging engine&lt;/a&gt; (and more besides!) and have been passionate about web publishing even longer than I’ve been passionate about web development. I’ve found the &lt;em&gt;blogosphere&lt;/em&gt; to be a pretty welcoming place over the years, and it could be said there’s an exciting resurgence in this area as IndieWeb ideas gain traction once more.&lt;/p&gt;

&lt;h2 id=&quot;look-beyond-the-tech-industry&quot;&gt;Look beyond the Tech Industry&lt;/h2&gt;

&lt;p&gt;It is a misnomer that the best place to work for a programmer is the “tech industry”. I’ve spent the bulk of my career &lt;em&gt;not&lt;/em&gt; working for tech companies!&lt;/p&gt;

&lt;p&gt;Yes, the pay isn’t as great, generally speaking, but the flip side is &lt;mark&gt;I’ve had plenty of work on my plate&lt;/mark&gt; during several major downturns (including the most recent one!) which adversely affected many of my fellow developers. I’ve developed Web sites and applications for a diverse range of companies and organizations over the decades. I’ve worked with art gallery administrators, non-profit communicators, small business owners, educators, publishers, musicians, and consultancies operating in the public sector—to name but a few!&lt;/p&gt;

&lt;p&gt;It’s true that it’s become more of a challenge to discover this type of work in recent years, but over the long term I feel like &lt;mark&gt;it’s a much less risky proposition&lt;/mark&gt; than sticking it out at FAANG-like Silicon Valley-style corporations.&lt;/p&gt;

&lt;p&gt;Not only that, but there’s much less likelihood that “AI” will even be relevant in any of the work you’re doing. Thus far I’ve managed to get by just fine with zero, and I mean &lt;em&gt;ZERO&lt;/em&gt; usage of &lt;em&gt;any&lt;/em&gt; AI coding tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No Copilot.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No Claude.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No Supermaven.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;None of any of it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And I’m going to keep on noping the frack right out of touching any of these tools, because they are detrimental and devastating to my personal wellbeing and the future of my professional craft.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Just. Say. No.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;create-new-industries&quot;&gt;Create new industries&lt;/h2&gt;

&lt;p&gt;Ultimately we programmers are going to have to &lt;mark&gt;take matters into our own hands&lt;/mark&gt;: building the tools, services, social networks (hello Fediverse! 👋), and markets we wish to see in the world. After all, the tech industry we have today was directly built on top of the backs of the &lt;em&gt;workers&lt;/em&gt;—the people truly writing code and &lt;em&gt;caring deeply&lt;/em&gt; about the quality and the caliber of the code (and the end-user products) they write.&lt;/p&gt;

&lt;p&gt;That’s us, and we need to &lt;strong&gt;push back&lt;/strong&gt; and push back hard on low-quality industrial automation of our profession. Big Tech CEOs should be &lt;mark&gt;shamed &amp;amp; shunned&lt;/mark&gt; at every opportunity whenever they make such ridiculous and insulting grandiose claims that “most code” will be written by “AI engineers” rather than “people engineers”.&lt;/p&gt;

&lt;p&gt;I was talking with a fellow prognosticator just recently about this topic, and they claimed not to be seeing any push back in trend data. Supposedly, programmers everywhere are simply embracing LLMs and all of these AI tools.&lt;/p&gt;

&lt;p&gt;I have two answers to that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If true, this will the be the death knell of our industry. We are sowing the seeds of our own destruction.&lt;/li&gt;
  &lt;li&gt;&lt;mark&gt;I don’t actually believe this is true!&lt;/mark&gt; I disagree with their assertion—I see an active, healthy, and &lt;em&gt;growing&lt;/em&gt; backlash against so-called “artificial intelligence” being thrust in our faces which isn’t actually intelligent at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Listen, there’s a reason the WGA (Writers Guild of America) worked hard to ensure &lt;a href=&quot;https://www.thewrap.com/writers-rooms-wga-strike/&quot;&gt;their writers’ rooms&lt;/a&gt; were protected—how can seasoned professional writers with award-winning credits to their name &lt;em&gt;become&lt;/em&gt; seasoned professionals in the first place if the opportunities to join writers’ rooms as newcomers and learn from the best simply…vanish?&lt;/p&gt;

&lt;p&gt;Similarly, how can junior developers become senior developers if the talent development pipeline is utterly upended? Our industry &lt;em&gt;already has a lousy track record&lt;/em&gt; when it comes to hiring and nurturing new talent, and I fear it’s about to get much, much worse.&lt;/p&gt;

&lt;p&gt;So what can we do about it?&lt;/p&gt;

&lt;h2 id=&quot;join-the-revolution&quot;&gt;Join the revolution!&lt;/h2&gt;

&lt;p&gt;Refuse to use these “AI” tools. Refuse to work for companies which won’t care for their workers and won’t &lt;em&gt;train&lt;/em&gt; new workers to become mid-level and eventually senior programmers. Refuse to use products and services sold to you by capitalist pigs.&lt;/p&gt;

&lt;p&gt;&lt;mark&gt;Recognize the moment we are in.&lt;/mark&gt; &lt;strong&gt;This is war.&lt;/strong&gt; I didn’t ask for it. I don’t want it. But “war must be, while we defend our lives against a destroyer who would devour all; but I do not love the bright sword for its sharpness, nor the arrow for its swiftness, nor the warrior for his glory. I love only that which they defend.” –&lt;a href=&quot;https://en.wikiquote.org/wiki/The_Two_Towers#The_Window_on_the_West&quot;&gt;J. R. R. Tolkein&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do support &lt;a href=&quot;https://blog.joinmastodon.org/2025/01/the-people-should-own-the-town-square/&quot;&gt;indie social web initiatives&lt;/a&gt;, do support fellow programmers striking out to build new products and services which &lt;mark&gt;respect users&lt;/mark&gt; and &lt;mark&gt;respect the art &amp;amp; craft of programming&lt;/mark&gt;. Be a part of local &amp;amp; online communities which have a &lt;em&gt;zero-tolerance policy&lt;/em&gt; against the intellectual theft inherent in commercial AI training data.&lt;/p&gt;

&lt;p&gt;Remember, when we band together, when we stand strong and express our values loudly and firmly, &lt;em&gt;we have much more power&lt;/em&gt; than we realize. We don’t have to accept the proclamations of Big Tech CEOs as fact! &lt;a href=&quot;https://indieweb.social/@jaredwhite/110118586852695268&quot;&gt;Nothing is “inevitable”.&lt;/a&gt; We can tell them that if they won’t support their workers, if they won’t protect civil rights (especially those of marginalized communities), if they won’t back down from promoting the shameless strip mining of the Open Web, &lt;mark&gt;they can go pound sand&lt;/mark&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be a part of the revolution.&lt;/strong&gt; The revolution always has and will always start with &lt;em&gt;you&lt;/em&gt;.&lt;/p&gt;</content><author><name></name></author><category term="bestpractices" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" /><media:content medium="image" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">SolidJS is Not the Future</title><link href="https://www.spicyweb.dev/oh-not-again-please-no-not-again/" rel="alternate" type="text/html" title="SolidJS is Not the Future" /><published>2024-10-01T00:00:00-07:00</published><updated>2024-10-01T00:00:00-07:00</updated><id>repo://posts.collection/_posts/2024-10-01-oh-not-again-please-no-not-again.md</id><content type="html" xml:base="https://www.spicyweb.dev/oh-not-again-please-no-not-again/">&lt;p&gt;&lt;strong&gt;Use the Platform™&lt;/strong&gt; has become an amusing rallying cry which is essentially more of a &lt;em&gt;philosophical&lt;/em&gt; argument than a technical one.&lt;/p&gt;

&lt;p&gt;Which is why I’m going to avoid virtually all technical jargon in this article, because I’m not here to refute &lt;a href=&quot;https://dev.to/ryansolid/web-components-are-not-the-future-48bh&quot;&gt;Ryan Carniato&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’m here to tell you how much I &lt;em&gt;resent&lt;/em&gt; we’re even having this conversation in the first place.&lt;/p&gt;

&lt;p&gt;Ready for some real talk? Good. Let’s &lt;em&gt;goooooo&lt;/em&gt;…&lt;/p&gt;
&lt;h2 id=&quot;bad-web-standards-are-always-better-than-good-userland-solutions&quot;&gt;Bad Web Standards Are Always Better Than Good “Userland” Solutions&lt;/h2&gt;

&lt;p&gt;This is the first point I want to make, which is that if I can do something with a native “vanilla” web API, and that API is “less good” than a clever API written by a clever developer in userland, I will invariably go with the vanilla API.&lt;/p&gt;

&lt;p&gt;Because in 10 years, we’ll have gotten used to that bad API, we’ll know how to deal with it, and &lt;strong&gt;we’ve moved on&lt;/strong&gt;. Whereas in 10 years, the added support and cognitive burden of understanding and supporting that broken/outdated/deprecated/refuted dependency which was once thought to have been a clever API by a clever developer &lt;em&gt;just isn’t fucking worth it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In other words, I’m sorry Ryan Carniato, but I won’t be using today’s SolidJS in 10 years, whereas I &lt;em&gt;will&lt;/em&gt; be use today’s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements&quot;&gt;custom elements&lt;/a&gt; in 10 years. &lt;strong&gt;That’s just a fact.&lt;/strong&gt; And I know it’s a fact because of all the other vanilla web APIs I use today in the same way I used them 10, 20, even ~30 years ago.&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;&lt;strong&gt;Case in point:&lt;/strong&gt; there’s an effort going on right now to add Signals to the web. Even if the eventual vanilla API for signals is “less good” than the Preact Signals library, I will &lt;em&gt;without hesitation&lt;/em&gt; switch all my future work to using the native API. That’s no offense to the great work done in userland on Preact Signals. It’s just the logical approach you take when you Use the Platform™.&lt;/p&gt;
&lt;/aside&gt;

&lt;h2 id=&quot;teaching-people-how-to-write-less-good-vanilla-web-apps-is-more-valuable-than-teaching-frameworks&quot;&gt;Teaching People How to Write Less-Good Vanilla Web Apps is More Valuable than Teaching Frameworks&lt;/h2&gt;

&lt;p&gt;Again, if you brought a junior developer to me who is seeking education, and asked me if I thought they’d be better off writing applications which are “less good” (in whatever metric you care to measure…let’s say &lt;em&gt;slick UI polish&lt;/em&gt; for the sake of example) using vanilla APIs or writing “more good” (aka slicker) applications in the &lt;em&gt;frontend framework du jour&lt;/em&gt;, I’d say the vanilla route is &lt;strong&gt;much more valuable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because those are skills which are going to be applicable to all projects, in all categories, for all users, in the years and decades to come. Whereas all the skills you learn in a particular frontend framework are by and large specific to that particular framework at this particular moment in time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Furthermore, you can still apply many of the vanilla skills you once picked up now that you’re working in a framework in order to solve thorny problems no one else understands, whereas the framework stans are often rather lost without their framework.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I know this because for years now I have seen, over and over and over again, people missing out on great vanilla solutions to common problems because they never heard about them or don’t grok how they work…because of all the noise of userland JS solutions. &lt;strong&gt;I recommend every framework stan spend a few hours every month poring over MDN and other resources to learn what’s new for the web platform.&lt;/strong&gt; That kind of platform-level developer knowledge over time is worth its weight in gold.&lt;/p&gt;

&lt;p&gt;(Also, truth is that framework-y web apps regularly offer &lt;em&gt;worse&lt;/em&gt; UX anyways, exhibiting severe performance and reliability issues. But I’m not even here to make that argument. &lt;a href=&quot;https://infrequently.org/series/reckoning/&quot;&gt;Go talk to Alex Russell&lt;/a&gt;.)&lt;/p&gt;

&lt;h2 id=&quot;your-framework-is-incompatible-with-my-stack&quot;&gt;Your Framework is Incompatible With My Stack&lt;/h2&gt;

&lt;p&gt;This is perhaps the most damning argument I’ll be making today. The idea that I have to switch my entire stack over to &lt;em&gt;your&lt;/em&gt; metaframework in order to make the most of your frontend framework is &lt;strong&gt;fucking bullshit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let me make myself &lt;em&gt;quite clear&lt;/em&gt; here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I’m not going to adopt Next.js just so I can use React.&lt;/li&gt;
  &lt;li&gt;I’m not going to adopt Nuxt just so I can use Vue.&lt;/li&gt;
  &lt;li&gt;I’m not going to adopt SvelteKit just so I can use Svelte.&lt;/li&gt;
  &lt;li&gt;I’m not going to adopt SolidStart just so I can use Solid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s really only one JS metaframework I can think of that’s achieved some popularity in recent years and for good reason, and that’s &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;. And the thing which makes Astro rather awesome is precisely &lt;em&gt;because&lt;/em&gt; you can start with real vanilla HTML, CSS, and JavaScript—and then go beyond that only when and where you need to.&lt;/p&gt;

&lt;p&gt;Use React! But just on that one page where needed. Use Vue! But just on that one page where needed. And so on and so forth.&lt;/p&gt;

&lt;p&gt;And guess what? Astro is a great place to get started using web components as well! Heck, you can even server-render Lit web components. &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/lit/&quot;&gt;Yeah, seriously&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;actually-web-components-are-components&quot;&gt;Actually, Web Components Are Components&lt;/h2&gt;

&lt;p&gt;I will now annoy many of my friends who do advocate for web components, because I reject the framing that “web components aren’t components” and that you can’t really replace the idea of framework components with web components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I simply don’t agree with that argument at all.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You absolutely &lt;em&gt;can&lt;/em&gt; build an entire web application out of components and those components can be custom elements and those custom elements can be called web components.&lt;/p&gt;

&lt;p&gt;No, I’m not going to get into the nitty-gritty technical details here because that’s not what this article is about. You’ll either trust me on this, or not. That’s entirely up to you.&lt;/p&gt;

&lt;p&gt;But it really, really, &lt;em&gt;really&lt;/em&gt; bothers me that even while praising web components, we must for whatever reason feel the need to show deference to userland JS frameworks. Like, somehow, their concepts of what a “component” is and how “components” should work are so immensely important, we gotta give credit where credit is due.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nope.&lt;/em&gt; 😝&lt;/p&gt;

&lt;p&gt;If React, Vue, Angular, Svelte, Solid, etc. all simply “poofed” out of existence tomorrow and all we had to build web applications with were vanilla APIs &amp;amp; web components, a tiny handful of tiny userland libraries for a healthy splash of DX, and good ol’ fashioned server-side web framework stacks in a variety of server-side languages which have been trucking along just fine for the past 20+ years, &lt;strong&gt;that would be perfectly fine with me&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I simply don’t respect these frontend frameworks. Never have. &lt;em&gt;Never will.&lt;/em&gt; I actually think—on the whole—today’s web is far worse off because of them.&lt;/p&gt;

&lt;p&gt;But you do you.&lt;/p&gt;

&lt;h2 id=&quot;this-entire-discussion-is-pointless-and-stupid&quot;&gt;This Entire Discussion is Pointless and Stupid&lt;/h2&gt;

&lt;p&gt;I will now end this blog post with the rant of all rants. You’ll probably want to unfollow me on all the socials. Good for you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I hate this conversation, I resent having to talk about all of this YET AGAIN, I think the JavaScript ecosystem is full of donkey-doo, this is all stupid, and talking about if web components should be web…components…AARGH it makes me want to gouge my eyes out.&lt;/strong&gt; 😖&lt;/p&gt;

&lt;p&gt;It’s time to touch grass, y’all. Go walk through a meadow by a lake. Enjoy a cuppa with your friends. Watch a movie. Write a poem. Throw a ball. Bake a cake.&lt;/p&gt;

&lt;p&gt;Anything—&lt;em&gt;anything&lt;/em&gt;—&lt;strong&gt;ANYTHING&lt;/strong&gt; but this. 😭😭&lt;/p&gt;

&lt;p&gt;We are literally shitting on the World-Wide Web as a healthy platform for reasonable people to build nice things on by endlessly arguing points &lt;em&gt;which were already moot years ago&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Ryan Carniato, and a hundred clever developers like him, can write 1,000 more articles on DEV.to saying things like “the promise of Web Components is so alluring and so dangerous” and &lt;em&gt;none of it actually matters&lt;/em&gt;. None of it.&lt;/p&gt;

&lt;p&gt;But maybe that’s just me. I wake up every morning and ask myself if I even still want to work in this industry…for these very reasons! (checks watch) Oh, is it time for another Tailwind vs. Vanilla CSS debate again? Should we argue about if Safari is, indeed, the New IE™?&lt;/p&gt;

&lt;p&gt;I DON’T FUCKING CARE!!!&lt;/p&gt;

&lt;p&gt;Meanwhile, on Planet Earth, real people are having real problems. Generative AI is shredding any hope of creative people to make an honest day’s living, climate change is wiping out parts of modern civilization, political parties are being held hostage by radical extremists who hate democracy, housing prices are going through the roof, car culture (even with EVs!) is polluting urban environments, VC-fueled crony capitalism is enshittifying basic digital goods and services on a daily basis, and the bottom is falling out of sustainable open source due to maintainer burnout and corporate consolidation &amp;amp; entrenchment.&lt;/p&gt;

&lt;p&gt;But nooooo, let’s &lt;em&gt;not&lt;/em&gt; focus our precious waking hours on addressing these existential human issues, and instead let’s talk again about FUCKING WEB COMPONENTS !!??!?!! 🤬&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I’m done y’all.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’m done.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;im-done&amp;gt;So long, and thanks for all the fish.&amp;lt;/im-done&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;P.S. &lt;a href=&quot;https://justaspec.buzzsprout.com&quot;&gt;Subscribe to my podcast&lt;/a&gt;. It’s like ranting in a blog post, but there’s two of us and it’s way more fun! 😁&lt;/p&gt;</content><author><name></name></author><category term="bestpractices" /><category term="architecture" /><category term="webcomponents" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" /><media:content medium="image" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Action Web Components Which Span the Server-Client Divide</title><link href="https://www.spicyweb.dev/action-web-components/" rel="alternate" type="text/html" title="Action Web Components Which Span the Server-Client Divide" /><published>2024-08-11T00:00:00-07:00</published><updated>2024-08-11T00:00:00-07:00</updated><id>repo://posts.collection/_posts/2024-08-11-action-web-components.md</id><content type="html" xml:base="https://www.spicyweb.dev/action-web-components/">&lt;p&gt;(&lt;strong&gt;Note:&lt;/strong&gt; this content was originally intended for a new course in JavaScript programming, but with my course development on hold for the time being, it’s being released to the public for free. &lt;em&gt;Enjoy!&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;I want to preface this article with a little reminder of something which often gets overlooked in discussions like these:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There’s only one web API&lt;/strong&gt; for executing JavaScript code when a &lt;em&gt;particular&lt;/em&gt; tag appears on a page, and that’s the &lt;strong&gt;Custom Elements API&lt;/strong&gt;. Yes, we’re talking about &lt;strong&gt;web components&lt;/strong&gt;—but forget everything you know about “components” for this discussion because we’re going to dive into a &lt;mark&gt;very different pattern&lt;/mark&gt; than you might be accustomed to.&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If you just want to see the cool demo and then circle back around to the content, &lt;a href=&quot;https://action-web-components.fly.dev&quot;&gt;check this out!&lt;/a&gt;&lt;/p&gt;
&lt;/aside&gt;

&lt;h2 id=&quot;first-a-little-backstory&quot;&gt;First, a Little Backstory&lt;/h2&gt;

&lt;p&gt;In the early 2000s, when I was but a wee lad learning how to build web applications as a greenhorn PHP developer, I happened across a fantastic little library called &lt;a href=&quot;https://web.archive.org/web/20051214030343/http://www.xajaxproject.org/&quot;&gt;Xajax&lt;/a&gt; (snapshot from 2005).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Xajax&lt;/strong&gt; brought the still-new concept of &lt;a href=&quot;https://en.wikipedia.org/wiki/Ajax_%28programming%29&quot;&gt;Ajax programming&lt;/a&gt; to PHP &amp;amp; JavaScript in an exciting way. You could write simple PHP functions on the backend, and then easily call those functions from your frontend. The response of a function could return various &lt;mark&gt;commands&lt;/mark&gt;, things like “append this HTML to that element” or “delete those elements”. You could even write your own custom commands.&lt;/p&gt;

&lt;p style=&quot;text-align:center&quot;&gt;&lt;img src=&quot;/images/leanardo-pointing.gif&quot; alt=&quot;Meme of Leanardo DiCaprio pointing at a TV&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now before you start gesturing wildly and shouting how that’s just like [&lt;em&gt;insert modern HTML-first library here&lt;/em&gt;], trust me—we’re getting there! 😄&lt;/p&gt;

&lt;p&gt;In short order, I ended up taking over maintenance of &lt;strong&gt;Xajax&lt;/strong&gt;—my very first experience with open source collaboration—and used it for a few years (even integrating it into my short-lived PHP fullstack web framework!) until I eventually gave into public peer pressure and learned &lt;strong&gt;Ruby on Rails&lt;/strong&gt;. (I kid, I kid…but you have to realize in the late 2000s, RoR was the new hotness and PHP seemed old and busted.)&lt;/p&gt;

&lt;p&gt;Fast forward to 2019, when I heard about a fancy new library for Rails called &lt;a href=&quot;https://docs.stimulusreflex.com&quot;&gt;StimulusReflex&lt;/a&gt;. It extended the functionality of the &lt;a href=&quot;https://stimulus.hotwired.dev&quot;&gt;Stimulus&lt;/a&gt; library provided by Rails and utilized asynchronous web sockets via an interrelated dependency called &lt;a href=&quot;https://cableready.stimulusreflex.com/&quot;&gt;CableReady&lt;/a&gt; to enable dynamic UI updates pushed from the backend to the frontend. CableReady lets your backend “functions” return various &lt;mark&gt;operations&lt;/mark&gt;, things like “append this HTML to that element” or “delete those elements”. You can even write your own custom operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yes, you can now point at the screen just like Leanardo.&lt;/strong&gt; 😆&lt;/p&gt;

&lt;p style=&quot;text-align:center&quot;&gt;&lt;img src=&quot;/images/leanardo-pointing.gif&quot; alt=&quot;Meme of Leanardo DiCaprio pointing at a TV&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I got heavily involved in the StimulusReflex community on Discord, utilizing the library in new Rails application development throughout 2020 and 2021, and even contributed some functionality for what seemed to be an exciting future update to CableReady called &lt;strong&gt;CableCar&lt;/strong&gt;. This would let you return operations from any standard HTTP request/response, not just async functions running via web socket connections. My journey back to the “Xajax” paradigm was nearly complete…&lt;/p&gt;

&lt;p&gt;…until Rails’ “new magic” was unveiled in the form of &lt;a href=&quot;https://turbo.hotwired.dev&quot;&gt;Turbo&lt;/a&gt; as part of the launch of HEY, Basecamp’s new email app.&lt;/p&gt;

&lt;p&gt;Turbo at heart was an evolution (aka Turbo Drive) of the previous &lt;strong&gt;TurboLinks&lt;/strong&gt; library which itself was a spiritual successor to &lt;strong&gt;PJAX&lt;/strong&gt; (a worthy pattern all in itself, but one we don’t have time to delve into here). In addition to Turbo Drive and Turbo Frames, it came with a paradigm called &lt;mark&gt;Turbo Streams&lt;/mark&gt;. Turbo Streams enables your backend responses or async background jobs to return various &lt;mark&gt;actions&lt;/mark&gt;, things like “append this HTML to that element” or “delete those elements”. You can even write your own custom actions—not a sanctioned concept at the start but one which was eventually merged into the library due to high demand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There we go again!&lt;/strong&gt; 🤣&lt;/p&gt;

&lt;p style=&quot;text-align:center&quot;&gt;&lt;img src=&quot;/images/leanardo-pointing.gif&quot; alt=&quot;Meme of Leanardo DiCaprio pointing at a TV&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Xajax &lt;mark&gt;Commands&lt;/mark&gt;. CableReady &lt;mark&gt;Operations&lt;/mark&gt;. Turbo &lt;mark&gt;Actions&lt;/mark&gt;. Turns out these are all variations on a theme: letting your backend’s HTML rendering pipeline update your frontend dynamically, rather than needing your frontend to take on the responsibility of updating its own UI (which requires an order of magnitude more frontend complexity and introduces performance headaches due to requiring large bundles of JS code to execute in order to generate DOM and run business logic).&lt;/p&gt;

&lt;p&gt;I’ll also note that &lt;a href=&quot;https://alpinejs.dev&quot;&gt;Alpine.js&lt;/a&gt;’s Directives and &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt;’s Attributes—while different in the sense they both offer an API that is “pull-based” rather than “push-based” (&lt;mark&gt;* wut&lt;/mark&gt;)—offer a similar overall developer experience by putting HTTP and HTML at the center of the UI programming model.&lt;/p&gt;

&lt;aside&gt;

  &lt;p&gt;* The difference between push-based and pull-based: &lt;strong&gt;push-based&lt;/strong&gt; is when the response back from the server directly dictates which actions to take to update UI, whereas &lt;strong&gt;pull-based&lt;/strong&gt; specifies those UI update steps in HTML first, &lt;em&gt;before&lt;/em&gt; making a request to the server for relevant data. I’ll state for the record I personally prefer push-based mechanisms—hence this article—but YMMV!&lt;/p&gt;

&lt;/aside&gt;

&lt;h2 id=&quot;actionsbut-vanilla&quot;&gt;Actions…But Vanilla?&lt;/h2&gt;

&lt;p&gt;So we’ve presented these various libraries which can translate various backend “actions” (we’ll go with that generic term from here on) to client-side interactivity. But in keeping with the spirit of &lt;strong&gt;The Spicy Web&lt;/strong&gt;, it’s time to muse on the following question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How might we write our own actions using nothing but native JavaScript APIs?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have a handful of initial feature requests to consider:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;These actions should be HTML-centric and easy to generate using any backend framework or toolchain.&lt;/li&gt;
  &lt;li&gt;These actions should be easily processed as part of any request/response cycle or async mechanisms (Server-Sent Events or Web Sockets) without special casing action-specific requirements.&lt;/li&gt;
  &lt;li&gt;These actions should be easy to write and adapt for various application needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first point has me leaning towards wanting to use &lt;strong&gt;HTML&lt;/strong&gt; as the transport format rather than JSON. JSON necessitates jumping through a lot more hoops on the frontend to do anything useful with the incoming data, whereas HTML can be read, parsed, and injected into a web page with virtually no client-side smarts required.&lt;/p&gt;

&lt;p&gt;The second point means we shouldn’t assume anything about the “context” in which actions are delivered to the client. In other words, we shouldn’t &lt;em&gt;combine&lt;/em&gt; action execution with any particular function or pattern that was specifically tailored to transport processing.&lt;/p&gt;

&lt;p&gt;And the third point means action “handlers” should be standalone, via some sort of fully modular and atomic API which isn’t tied to any singular application shape or build dependency.&lt;/p&gt;

&lt;p&gt;So…where does that leave us? If HTML is our transport mechanism and we need actions to be universally applicable, it sounds like we’re saying we should define actions using HTML tags.&lt;/p&gt;

&lt;p&gt;Which brings us back around to what I said at the top of the article:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There’s only one web API&lt;/strong&gt; for executing JavaScript code when a &lt;em&gt;particular&lt;/em&gt; tag appears on a page, and that’s the &lt;strong&gt;Custom Elements API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Anything else is much more fiddly. We &lt;em&gt;could&lt;/em&gt; try to use a &lt;code class=&quot;highlighter-rouge&quot;&gt;MutationObserver&lt;/code&gt;, as does Stimulus and many other libraries looking for special attributes contained within HTML, but that’s a much harder prospect. We &lt;em&gt;could&lt;/em&gt; require some kind of JavaScript code to call a function explicitly in order to process a chunk of incoming HTML, but that would require a tighter coupling between action processing and whatever code is pulling down HTML in the first place.&lt;/p&gt;

&lt;p&gt;Here’s the great thing about web component technology: we &lt;em&gt;know&lt;/em&gt; it will work anywhere, anytime. Literally the moment a custom HTML tag shows up in some DOM somewhere, our client-side &lt;code class=&quot;highlighter-rouge&quot;&gt;connectedCallback&lt;/code&gt; function will execute. This turns out to be the perfect mechanism for what I am calling &lt;strong&gt;Action Web Components&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Similar to how an &lt;strong&gt;HTML Web Component&lt;/strong&gt; in popular parlance is a custom element which wraps standard HTML and applies interactivity to those child elements when rendered on a page, an &lt;strong&gt;Action Web Component&lt;/strong&gt; performs a &lt;mark&gt;one-time operation&lt;/mark&gt; when it’s rendered on the page and then (not always but typically) removes itself from the DOM upon completion. These actions don’t themselves have any visual appearance—they aren’t content, but commands.&lt;/p&gt;

&lt;p&gt;It’s telling that &lt;a href=&quot;https://turbo.hotwired.dev/handbook/streams&quot;&gt;Turbo Stream Actions&lt;/a&gt; are built using the custom elements API. In that case, there’s one tag—&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;turbo-stream&amp;gt;&lt;/code&gt;—which is responsible for reading in attributes and using that data to determine which action to execute. We’re however going to go a step further in our quest to stay close-to-the-metal: we’ll stick with a simple 1:1 &lt;strong&gt;action type == tag name&lt;/strong&gt; nomenclature, using an &lt;code class=&quot;highlighter-rouge&quot;&gt;ac&lt;/code&gt; prefix.&lt;/p&gt;

&lt;p&gt;Let’s explore our first example.&lt;/p&gt;

&lt;h2 id=&quot;example-action-class-toggle&quot;&gt;Example Action: Class Toggle&lt;/h2&gt;

&lt;p&gt;Let’s say in response to a particular event on the frontend (say, a button click), you want to add a class to an element on the page with a certain ID.&lt;/p&gt;

&lt;p&gt;Here’s how that would look in HTML, the idea being that this could be directly rendered out in a response via a fragment (or it could live embedded in a larger template partial).:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;ac-toggle-class&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;target=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;checkmark-12345&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;classname=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;success&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/ac-toggle-class&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and this is how we would define the action in JavaScript as a web component:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;customElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ac-toggle-class&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ActionWebComponent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;connectedCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;force&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;force&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;undefined&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;force&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;force&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;force&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;classList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;classname&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;force&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Our &lt;code class=&quot;highlighter-rouge&quot;&gt;ActionWebComponent&lt;/code&gt; base class is &lt;em&gt;extremely&lt;/em&gt; minimal, merely providing a few convenience methods such as using &lt;code class=&quot;highlighter-rouge&quot;&gt;this.target&lt;/code&gt; to refer to the DOM element with the ID matching the tag’s &lt;code class=&quot;highlighter-rouge&quot;&gt;target&lt;/code&gt; attribute. As you can see here, we get the CSS class via the &lt;code class=&quot;highlighter-rouge&quot;&gt;classname&lt;/code&gt; attribute and then either toggle it by default or force it on or off via the presence of &lt;code class=&quot;highlighter-rouge&quot;&gt;force=&quot;true&quot;&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;force=&quot;false&quot;&lt;/code&gt; attributes. The custom element removes itself as soon as it runs, leaving behind no trace in the DOM upon completion.&lt;/p&gt;

&lt;p&gt;And…that’s it! It seems almost too simple to be true. Literally any place you might write &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;ac-toggle-class&amp;gt;&lt;/code&gt; in your codebase, your HTML now has the power to affect your UI.&lt;/p&gt;

&lt;p&gt;There are numerous other such actions we could imagine…actions such as updating some HTML element with new children, scrolling to an element, focusing on an element, redirecting to another page after a short delay, or displaying a message in an alert.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thankfully, you don’t need to imagine. I’ve already done the hard work for you!&lt;/strong&gt; 🤯&lt;/p&gt;

&lt;h2 id=&quot;astro--alpinejs--action-web-components--&quot;&gt;Astro + Alpine.js + Action Web Components = 😍&lt;/h2&gt;

&lt;p&gt;I’ve put together a demo repo of a web application using the &lt;a href=&quot;https://astro.build&quot;&gt;Astro framework&lt;/a&gt;. It uses &lt;a href=&quot;https://alpinejs.dev&quot;&gt;Alpine.js&lt;/a&gt; as an easy way to handle JavaScript events like clicks in order to fetch HTML fragments, and &lt;a href=&quot;https://picocss.com&quot;&gt;Pico&lt;/a&gt; for good default styling. From there, we sprinkle in our Action Web Components to give Astro &lt;strong&gt;server → client superpowers&lt;/strong&gt;. Advanced UI interactivity in mere lines of code? &lt;em&gt;You betcha.&lt;/em&gt;&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;&lt;nobr&gt;&lt;a href=&quot;https://action-web-components.fly.dev&quot; class=&quot;button is-spaced is-red&quot;&gt;Demo&lt;/a&gt; &lt;a href=&quot;https://codeberg.org/thespicyweb/action-web-components&quot; class=&quot;button is-spaced is-purple&quot;&gt;Repo&lt;/a&gt;&lt;/nobr&gt;&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;&lt;strong&gt;Quick aside:&lt;/strong&gt; I intentionally wanted to kick it old school, so this Astro config is just a simple Node web server. Yep, server-side rendering (SSR) at its finest. Nothing platform-specific, no whizbang serverless function magic. I noticed Astro works well in this configuration within a Docker container deployed to Fly, so I decided to go that route. The automated setup was a breeze. If you’re interested in learning more, check out &lt;a href=&quot;https://docs.astro.build/en/guides/deploy/flyio/&quot;&gt;Deploy your Astro Site to Fly.io&lt;/a&gt;.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;Here are the actions I’ve written for this demo. You can find all of these in &lt;a href=&quot;https://codeberg.org/thespicyweb/action-web-components/src/branch/main/frontend/javascript/actions.js&quot;&gt;this JS file in the repo&lt;/a&gt;. And again, in case it’s not obvious—there’s &lt;em&gt;absolutely nothing&lt;/em&gt; specific to Astro or Alpine.js about how Action Web Components work. I’m just using those tools for some helpful smarts to build the demo site, but you could just as easily go with a hand-rolled &lt;mark&gt;zero dependency&lt;/mark&gt; static &lt;code class=&quot;highlighter-rouge&quot;&gt;.html&lt;/code&gt; file and PHP backend. &lt;em&gt;Party like it’s 1999.&lt;/em&gt; 🕺🏽&lt;/p&gt;

&lt;h3 id=&quot;ac-yellow-fade-aka-the-yellow-fade-technique&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ac-yellow-fade&lt;/code&gt; aka the “Yellow Fade Technique”&lt;/h3&gt;

&lt;p&gt;This one’s an oldie but goodie. When adding a new item on a page in a list or a grid or something to that effect, you temporarily highlight the new item. A yellow fade is still cool IMO, but there are many other design techniques you could use to draw the user’s attention to the new item. Perhaps a more generic take on this could be named &lt;code class=&quot;highlighter-rouge&quot;&gt;ac-highlight&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;ac-toggle-class-aka-switch-a-class-on-and-off&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ac-toggle-class&lt;/code&gt; aka Switch a class on and off&lt;/h3&gt;

&lt;p&gt;Use this one to toggle a CSS class on a particular element.&lt;/p&gt;

&lt;h3 id=&quot;ac-portal-aka-render-content-at-the-bottom-of-body&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ac-portal&lt;/code&gt; aka Render content at the bottom of &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;body&amp;gt;&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;This may be less useful now in the age of &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; which doesn’t require portal techniques, but there are still times when you may want to inject some content at the very end of &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;body&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;ac-scroll-to-aka-scroll-the-viewport-to-an-element&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ac-scroll-to&lt;/code&gt; aka Scroll the viewport to an element&lt;/h3&gt;

&lt;p&gt;Maybe you have an Edit button which triggers an inline editing form…but what if the user can’t see nearly any part of the form because it’s below the viewport? You’ll likely want to scroll down to the form so it’s mostly or completely visible onscreen. This will do just that!&lt;/p&gt;

&lt;h2 id=&quot;ac-focus-aka-focus-on-a-particular-form-field&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ac-focus&lt;/code&gt; aka Focus on a particular form field&lt;/h2&gt;

&lt;p&gt;Perhaps in tandem with the scroll action, this lets you focus on an interactive element upon the user performing some task. BTW, in real-world usage I’ve also found adding a timeout of, say, 200ms is best if you’re also scrolling.&lt;/p&gt;

&lt;h2 id=&quot;ac-children-aka-replace-a-part-of-the-dom-with-a-new-html-fragment&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ac-children&lt;/code&gt; aka Replace a part of the DOM with a new HTML fragment&lt;/h2&gt;

&lt;p&gt;This is a pretty common action, and something developers need to do often when programming using HTML-first techniques. If you’ve already used libraries like Turbo or htmx, this will feel entirely familiar!&lt;/p&gt;

&lt;h2 id=&quot;ac-redirect-to-aka-redirect-to-another-url-after-a-short-delay&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ac-redirect-to&lt;/code&gt; aka Redirect to another URL after a short delay&lt;/h2&gt;

&lt;p&gt;You’d be surprised how handy this action can be after various form submissions. I tend to use this technique a lot. (An immediate redirect doesn’t make much sense since you can trigger that server-side with an HTTP 302 status, but using a delay means you can notify the user of something, then after a few seconds take them to their next destination.)&lt;/p&gt;

&lt;h2 id=&quot;ac-message-dialog-aka-an-attempt-at-a-better-alert&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ac-message-dialog&lt;/code&gt; aka An attempt at a better &lt;code class=&quot;highlighter-rouge&quot;&gt;alert()&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;There are times when you need to display critical information to the user. I thought it would be helpful to design a simple dialog (using &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; of course!) and make that an action, but it’d probably also be helpful to write a custom JavaScript async function to do this so client-side code could also trigger such a dialog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guess what?&lt;/strong&gt; There were a couple of additional actions I thought about writing for this demo, but didn’t…an exercise left for the reader! They were:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Toast Notification&lt;/strong&gt;: showing a notification after a form save is a very common pattern…but notification components as well as the toast stack can also get very complicated. My recommendation: &lt;a href=&quot;https://shoelace.style/components/alert#toast-notifications&quot;&gt;just use &lt;del&gt;Shoelace&lt;/del&gt; Web Awesome!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attribute Swaps&lt;/strong&gt;: a pattern I increasingly like is an action which will swap out one or more attributes of an element, typically a web component. This is a pretty powerful combo, as you can write a web component which manages internal state and re-renders DOM accordingly, and with an attribute action you can just pass some new state along to the web component and let it handle the re-render. With an action like this, as well as the children action mentioned above, I almost &lt;em&gt;never&lt;/em&gt; feel the need to reach for full-blown DOM morphing! (Which is technique I happen to think borders on anti-pattern…)&lt;/p&gt;

&lt;p&gt;Can you think of some additional actions you might like to reach for? (Maybe an &lt;code class=&quot;highlighter-rouge&quot;&gt;ac-delete&lt;/code&gt; for removing a DOM node?)&lt;/p&gt;

&lt;h2 id=&quot;a-word-on-progressive-enhancement&quot;&gt;A Word on Progressive Enhancement&lt;/h2&gt;

&lt;p&gt;Inevitably when discussions arise of updating UI in a manner which requires JavaScript—even if HTML remains at the center of the approach—you’ll hear the common refrain: &lt;em&gt;but what about progressive enhancement?&lt;/em&gt; And for good reason: our industry has fallen down hard when it comes to the best practice that you should be writing web applications in a multi-layered fashion: &lt;em&gt;first&lt;/em&gt; &lt;strong&gt;HTML&lt;/strong&gt;, &lt;em&gt;then&lt;/em&gt; &lt;strong&gt;CSS&lt;/strong&gt;, &lt;em&gt;then&lt;/em&gt; &lt;strong&gt;JavaScript&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, some folks tend to overcorrect on this issue, and present progressive enhancement as first HTML, then CSS, then…well, you probably shouldn’t use JavaScript for anything but the &lt;em&gt;slightest&lt;/em&gt; of polishing techniques which are totally optional. 🧐&lt;/p&gt;

&lt;p&gt;I’m here to make a bold statement: &lt;strong&gt;progressive enhancement does not mean a complete JavaScript-optional experience&lt;/strong&gt;. And before you start throwing tomatoes at me, I’ll simply crib from &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement&quot;&gt;the MDN documentation on this topic&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Progressive enhancement&lt;/strong&gt; is a design philosophy that provides a baseline of essential content and functionality to as many users as possible, while delivering the best possible experience only to users of the most modern browsers that can run all the required code.&lt;/p&gt;

  &lt;p&gt;The word &lt;em&gt;progressive&lt;/em&gt; in &lt;em&gt;progressive enhancement&lt;/em&gt; means creating a design that achieves a simpler-but-still-usable experience for users of older browsers and devices with limited capabilities, while at the same time being a design that &lt;strong&gt;progresses the user experience up&lt;/strong&gt; to a more-compelling, fully-featured experience for users of newer browsers and devices with richer capabilities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The most important phrase in this definition: “baseline of essential content and functionality”. And that’s up to you as the application developer…what is the baseline of essential content and functionality you want to make sure is available to the widest possible set of users?&lt;/p&gt;

&lt;p&gt;In practical terms, it might mean that some key features of your application work fine without JavaScript, whereas other less-vital features do require JavaScript. Or perhaps you offer a simpler HTML form UX to baseline users and a much-improved interactive HTML+JS form UX to high-end users. See also: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation&quot;&gt;Graceful Degradation&lt;/a&gt;.&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;Plus I’d argue certain applications &lt;em&gt;are impossible to develop&lt;/em&gt; without significant JavaScript code. Imagine Adobe Photoshop on the web, but without JavaScript. &lt;mark&gt;Sorry folks, that’s just not a thing.&lt;/mark&gt; 😄&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;The reason this is all so important to understand is as soon as you start introducing techniques like &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt;, server-sent “actions”, etc., you immediately leave behind the world of of &lt;strong&gt;no-JS, HTML-only&lt;/strong&gt; interactions. Which isn’t a bad thing as we’ve just covered, but you should understand the tradeoffs. Action web components, libraries like Alpine or htmx or Turbo, etc., are great for building &lt;em&gt;on top of&lt;/em&gt; considerate baseline experiences, but they shouldn’t comprise &lt;em&gt;your only experiences&lt;/em&gt; or you will inevitably run into the sorts of issues progressive enhancement is intended to address. &lt;strong&gt;Construct your application architecture accordingly.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion-html-first-programming-works&quot;&gt;Conclusion: HTML-First Programming &lt;mark&gt;Works&lt;/mark&gt;&lt;/h2&gt;

&lt;p&gt;The reason so many people have fallen in love (again?) with &lt;mark&gt;HTML-first programming techniques&lt;/mark&gt; in web development is because the levels of complexity, performance issues, accessibility concerns, and fragile build tooling required to build with JavaScript-first techniques (aka the React apps of the world) has proven to be wildly against “the grain of the web” for all but the most specific of application types. It may still be true that “nobody ever got fired for using React” (a riff off the old canard about IBM), but at the same time, “you probably don’t need React” is &lt;strong&gt;increasingly true for most web projects&lt;/strong&gt;. (&lt;a href=&quot;https://www.zachleat.com/web/react-criticism/&quot;&gt;Just ask Zach…he knows!&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Which opens the door to all sorts of other interesting techniques and tools, some of which harken back to years past when the web was still understood to be founded on server-first, HTML-first principals. Today, we have the chance to &lt;strong&gt;embrace the best that the web platform has to offer&lt;/strong&gt; all while &lt;em&gt;continuing to evolve&lt;/em&gt; the types of architectures best suited to a dizzying array of application types.&lt;/p&gt;

&lt;p&gt;I personally think &lt;strong&gt;Action Web Components&lt;/strong&gt; offers an intriguing paradigm which is compatible with a near-infinite number of client/server topologies. It’s totally framework-independent, and save for a wee bit of client-side JavaScript, it’s totally language-independent as well. Which suits me, &lt;a href=&quot;https://www.fullstackruby.dev&quot;&gt;as someone who still primarily writes server applications in Ruby&lt;/a&gt;, just fine. &lt;mark&gt;Give it a try&lt;/mark&gt;, and let me know what you think in &lt;a href=&quot;https://discord.gg/CUuYVH7Qa9&quot;&gt;The Spicy Web Discord&lt;/a&gt;!&lt;/p&gt;</content><author><name></name></author><category term="html" /><category term="webcomponents" /><category term="buildless" /><category term="architecture" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" /><media:content medium="image" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Building Courseware I Can Understand: The Launch of CSS Nouveau</title><link href="https://www.spicyweb.dev/building-courseware-i-understand/" rel="alternate" type="text/html" title="Building Courseware I Can Understand: The Launch of CSS Nouveau" /><published>2023-10-23T00:00:00-07:00</published><updated>2023-10-23T00:00:00-07:00</updated><id>repo://posts.collection/_posts/2023-10-23-building-courseware-i-understand.md</id><content type="html" xml:base="https://www.spicyweb.dev/building-courseware-i-understand/">&lt;p&gt;I’ll breeze through the marketing-speak quickly so as to get to the heart of the matter. I have a new course out, the first one for &lt;strong&gt;THE SPICY WEB&lt;/strong&gt;, called &lt;a href=&quot;/css-nouveau&quot;&gt;CSS Nouveau&lt;/a&gt;. This is my very latest, up-to-date, &lt;mark&gt;pedal-to-the-metal&lt;/mark&gt; thinking on how to build up a 100% vanilla CSS architecture from scratch, from &lt;em&gt;design tokens&lt;/em&gt; to &lt;em&gt;global stylesheets&lt;/em&gt; to &lt;em&gt;encapsulated components&lt;/em&gt;. When people tell me “vanilla CSS doesn’t scale” or “best practices don’t actually work” (which I agree with, but in a completely different way than they probably do!) or “only a framework will let you build maintainable software” blah blah blah, &lt;a href=&quot;/css-nouveau&quot;&gt;CSS Nouveau&lt;/a&gt; is &lt;mark&gt;my whole-hearted rebuttal&lt;/mark&gt; to that line of thinking. It’s designed to get you from &lt;mark&gt;zero-to-hero&lt;/mark&gt;—even if you don’t already have a ton of CSS design expertise.&lt;/p&gt;

&lt;p&gt;So &lt;a href=&quot;/css-nouveau&quot;&gt;check it out&lt;/a&gt; and hopefully you’ll take advantage of the $59 deal for six months or $10.99 a month. Yes, this isn’t just a single course—this is the first entry in &lt;strong&gt;a whole array of courses&lt;/strong&gt;! Which brings me to the much larger story…&lt;/p&gt;

&lt;h2 id=&quot;dont-ask-me-to-take-a-course&quot;&gt;Don’t Ask Me to Take a Course&lt;/h2&gt;

&lt;p&gt;For many, many, &lt;em&gt;many&lt;/em&gt; years, I kept banging my head against the proverbial brick wall. You see, I knew without a doubt I &lt;em&gt;wanted&lt;/em&gt; to get into the education space, dearly. After literally decades of working in the &lt;code class=&quot;highlighter-rouge&quot;&gt;#WebDev&lt;/code&gt; industry, I felt like there were at least a few topics I might feel comfortable enough to touch upon from a position of freelance-driven experience across numerous client projects, “there be dragons here” cautionary tales, and genuine enthusiasm over new advances in the state-of-the-art.&lt;/p&gt;

&lt;p&gt;However, there was just one problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I hate taking courses.&lt;/strong&gt; 😭&lt;/p&gt;

&lt;p&gt;Courses suck:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Courses force you to pay lots of money up front. Often hundreds and hundreds of dollars. Listen, I’ll admit it. I’m a cheap bastard. But for good reason—&lt;strong&gt;I work for myself&lt;/strong&gt;. And I don’t have a mega-huge discretionary budget to spend on educational pursuits. I don’t go to nearly any major tech conferences for the same reason. I take one look at the starting ticket price and…well…some months I can either do that or eat nothing but ramen noodles for a few weeks. And let me tell you—living in Portland, I sure like my fancy takeout an awful lot. 😅&lt;/li&gt;
  &lt;li&gt;Even on the chance a course doesn’t cost so much or is part of a nice subscription, they usually require you to watch &lt;mark&gt;videos&lt;/mark&gt;. And here’s where things get weird: &lt;strong&gt;I really don’t like watching videos about technical topics.&lt;/strong&gt; 😬 Now don’t get me wrong—I like watching videos where people show off a cool demo they made, or talk about some upcoming framework or product features, or banter about on a panel regarding a particular industry topic. That’s all fine and dandy—and I hope to put more effort into this space myself going forward (hence the &lt;a href=&quot;/videos&quot;&gt;shiny new Videos page&lt;/a&gt;!). But ask me to &lt;em&gt;learn&lt;/em&gt; anything substantial via a series of videos spanning hours which touch on tons of code and specs and concepts and funky details? &lt;strong&gt;Ugh.&lt;/strong&gt; I ain’t ever doing that. (For the most part, I don’t read books about technical topics either…)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this, &lt;em&gt;dear Reader&lt;/em&gt;, is why the years rolled on by and I never managed to get a course off the ground. If you don’t intend to charge much for it, and you don’t want to produce a ton of videos, what do you do?&lt;/p&gt;

&lt;h2 id=&quot;leaning-into-your-strengths&quot;&gt;Leaning Into Your Strengths&lt;/h2&gt;

&lt;p&gt;From a very early age—I’m talking about the internet’s age, as well as my age!—I have received many compliments as a &lt;em&gt;writer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And for some odd reason, I always had a hard time believing them. Not because I thought I was a &lt;em&gt;bad&lt;/em&gt; writer, but because I just assumed “anybody” could hop on the internet and &lt;strong&gt;write&lt;/strong&gt;. Anyone could start a blog, anyone could publish a website. Who am I to think I’m anything special?&lt;/p&gt;

&lt;p&gt;It’s taken literally decades for me to get over my “imposter syndrome” and acknowledge to myself that, actually, &lt;em&gt;I’m a pretty damn good writer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Which is how I eventually arrived at my &lt;strong&gt;Aha!&lt;/strong&gt; 💡 moment:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;How do &lt;em&gt;I&lt;/em&gt; like consuming and learning about intricate technical information? &lt;strong&gt;Blogs. Podcasts.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;What do &lt;em&gt;I&lt;/em&gt; like &lt;mark&gt;producing myself&lt;/mark&gt; as a content creator? &lt;strong&gt;Blogs. Podcasts.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the ending writes itself. The solution to my dilemma was crystal-clear. &lt;strong&gt;The best course is one which looks very much like a series of blog posts and podcast episodes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And so that’s exactly what I’ve done.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/css-nouveau&quot;&gt;CSS Nouveau&lt;/a&gt; isn’t just a “course”. It’s a &lt;em&gt;series&lt;/em&gt;. And a &lt;em&gt;series&lt;/em&gt; will have multiple &lt;em&gt;seasons&lt;/em&gt;. (This first season is actually titled &lt;mark&gt;“Vanilla Has Never Tasted So Hot”&lt;/mark&gt;.) And each &lt;em&gt;season&lt;/em&gt; will have multiple &lt;em&gt;episodes&lt;/em&gt;. And an &lt;em&gt;episode&lt;/em&gt; is structured much like how I’d write an in-depth blog post, and I literally record myself speaking the whole episode as well which you can follow along with or download to listen to at your leisure—just like a podcast.&lt;/p&gt;

&lt;p&gt;Once I had determined that’s how I wanted to structure the courses I create, the next question was &lt;mark&gt;how to deliver them&lt;/mark&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-birth-of-intuitive&quot;&gt;The Birth of Intuitive+&lt;/h2&gt;

&lt;p&gt;I’ll be talking more about this down the road a bit on my &lt;a href=&quot;https://jaredwhite.com&quot;&gt;personal blog&lt;/a&gt;—and I have a little already—but the gist of it is that I’ve been wanting to offer a subscription content platform in earnest for quite some time. I toyed with Patreon a few years ago, but somehow the idea of merely being beholden to some other content platform never appealed to me. (I think I’ve just always had a bent towards DIY on the web. Own your own content. That sort of thing.)&lt;/p&gt;

&lt;p&gt;Again, the &lt;strong&gt;Aha!&lt;/strong&gt; 💡 moment was coming to the realization that I shouldn’t be dividing my efforts trying to build a subscription platform for &lt;strong&gt;THE SPICY WEB&lt;/strong&gt; and another one for my “personal” podcast &amp;amp; newsletter and another one for some other project down the road. &lt;em&gt;Are you getting it yet? These aren’t three different subscription platforms, they’re one platform! And I call it…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://plus.intuitivefuture.com&quot;&gt;Intuitive+&lt;/a&gt;. 😁&lt;/p&gt;

&lt;p&gt;Once you’re a member, you gain access to the “Jared Multimedia Universe” (JMU). Just kidding, I’m never calling it that. But seriously, the idea is to unify my efforts right out of the starting gate, and then slowly expand outward from there—not just additional projects I embark on (and believe me, I have a &lt;em&gt;slew&lt;/em&gt; of efforts just waiting in the wings), but bringing other creators on-board with like-minded values and goals. Nothing would make me happier than for &lt;strong&gt;Intuitive+&lt;/strong&gt; to become an indie powerhouse with tons of shows, websites, and educational materials on a variety of “open web”-themed topics.&lt;/p&gt;

&lt;p&gt;&lt;mark&gt;But I’m getting way ahead of myself.&lt;/mark&gt; Today, the principal &lt;em&gt;raison d’être&lt;/em&gt; for the membership service is to power &lt;strong&gt;THE SPICY WEB&lt;/strong&gt;, and I’m very excited that day has finally arrived.&lt;/p&gt;

&lt;h2 id=&quot;cool-so-whats-next&quot;&gt;Cool. So What’s Next?&lt;/h2&gt;

&lt;p&gt;Obviously, the near-term goal I have is to release the few remaining bits of “Vanilla Has Never Tasted So Hot” (the final two episodes will be released on &lt;strong&gt;November 6&lt;/strong&gt; &amp;amp; &lt;strong&gt;November 13&lt;/strong&gt; respectively), and then start immediately on Season 2. In addition, I’ll be starting work on a new series about how to build legit frontend UI using vanilla web components (touching on concepts like state management, the client/server divide, progressive enhancement, performance, optimistic UI vs. waiting for request/response round-trips, and all of those sorts of concerns).&lt;/p&gt;

&lt;p&gt;I could be wrong, but I think producing and releasing new material will be an order-of-magnitude “easier” than this first course, because the courseware and membership services have already been built. There are plenty of ways to improve it all of course, but I’m fairly satisfied with the “bones” at this point. (BTW, the process of putting that all together pretty much from scratch deserves a whole series of posts in and of itself!)&lt;/p&gt;

&lt;p&gt;All right, enough of my bloviating. I’m &lt;em&gt;extremely grateful&lt;/em&gt; for all the support folks have shown me through the Discord and on social media (&lt;a href=&quot;https://indieweb.social/@jaredwhite&quot;&gt;Mastodon&lt;/a&gt;), and I might not have ever gotten to this point if it weren’t for a few encouraging voices in particular. I can’t possibly thank everybody by name, but please know that &lt;mark&gt;I’m deeply appreciative&lt;/mark&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: &lt;a href=&quot;/css-nouveau&quot;&gt;CSS Nouveau. It’s out. Check it out.&lt;/a&gt; 🥳&lt;/p&gt;</content><author><name></name></author><category term="architecture" /><category term="bestpractices" /><category term="css" /><category term="courses" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" /><media:content medium="image" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Yeah, But is Tailwind a Cult Tho?</title><link href="https://www.spicyweb.dev/tailwind-is-a-cult/" rel="alternate" type="text/html" title="Yeah, But is Tailwind a Cult Tho?" /><published>2023-09-18T00:00:00-07:00</published><updated>2023-09-18T00:00:00-07:00</updated><id>repo://posts.collection/_posts/2023-09-18-tailwind-is-a-cult.md</id><content type="html" xml:base="https://www.spicyweb.dev/tailwind-is-a-cult/">&lt;p&gt;I’ve been a part of “cults” before, whether that’s being a total Apple fanboy or a total Ruby fanboy or—perhaps more recently—a total &lt;a href=&quot;/tags/webcomponents&quot;&gt;web components&lt;/a&gt; fanboy.&lt;/p&gt;

&lt;p&gt;So as the seemingly never-ending debate over Tailwind CSS continues to rage on, some haters of Tailwind hurl accusations that Tailwind is a “cult” and überfans are responsible for perpetuating it. Even pro-Tailwind promoters are getting in on the act, as &lt;a href=&quot;https://phoenixonrails.com/blog/why-im-in-the-tailwind-cult&quot;&gt;a recent blog author states&lt;/a&gt;: “I’m in a cult, but hey, I’m happy, and this Kool-Aid tastes delicious.”&lt;/p&gt;

&lt;p&gt;Now I’ve made a name for myself I suppose as &lt;a href=&quot;https://www.spicyweb.dev/tags/tailwind&quot;&gt;a frequent critic of Tailwind&lt;/a&gt;, so you might think I’d point to this and say “Haha, you said it, you’re in a cult! &lt;em&gt;Pwned!&lt;/em&gt;” But I think we need to be careful here. Lots of people get excited about all sorts of technologies and tools. If we actually believe that a fan community has crossed a line, we need to be careful to point it out in a reasonable manner and back that up with hard evidence.&lt;/p&gt;

&lt;p&gt;So here you go. I will now endeavor to present hard evidence in the form of YouTube comments on &lt;a href=&quot;https://www.youtube.com/watch?v=CLkxRnRQtDE&quot;&gt;Adam Wathan’s keynote at the recent Tailwind Connect conference&lt;/a&gt;. I simply scrolled through the list of comments and pulled out anything that seemed, er, rather in the direction of sounding cult-like. Does this prove Tailwind is a cult? Well, you be the judge!*&lt;/p&gt;

&lt;p&gt;(* Actually, I will also be the judge but I will hide my commentary behind disclosures. Feel free to ignore! 😉)&lt;/p&gt;

&lt;h2 id=&quot;shoutouts-from-the-fans-cult-members&quot;&gt;Shoutouts from the &lt;del&gt;Fans&lt;/del&gt; Cult Members?&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Everyone that can afford buying Tailwind UI should do it. These people deserve that and more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Always a little concerning when folks encourage “everyone” to funnel money into the mothership…&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;nothing but love for the tailwind team and the extended community - you both revolutionised styling and allowed me to go from struggling with maintanance to getting creative with ease. I salute you, and am excited for what else is in store! Thank you from the bottom of my heart&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;“Bottom of my heart” sort of feels like it should be reserved for “you saved my puppy” or “my grandmother can now walk again thanks to your therapy treatments” rather than, er, utility classes, but that’s just me.&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;Steve is so smart when it comes to design. Dude is literally a genius. By far one of the best designers on the web right now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Nice use of the word literally. 🤡&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;The greatest thing about Tailwind for me as a beginner was I didn’t have to discover what were the best ways to achieve some things, Tailwind had already done the hard task of figuring out browser compatibility issues and on top the most amazing tech documentation I have seen anywhere. Also not to forget that I haven’t had to deal with any bugs whatsoever. You guys are amazing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Perhaps chalk this one up to framework honeymoon glow, but it’s perhaps unwise to cast any software tool as having “the best documentation ever” and “no bugs ever” and “figured out all compatibility issues”. You’re just setting yourself up for disappointment…&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;Tailwind is hands down the best thing that ever happened to css. Thank you 👏&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Let’s see, some additions to CSS in recent years:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Grid&lt;/li&gt;
    &lt;li&gt;CSS Variables&lt;/li&gt;
    &lt;li&gt;Logical Properties&lt;/li&gt;
    &lt;li&gt;Light/Dark Mode&lt;/li&gt;
    &lt;li&gt;Container Queries&lt;/li&gt;
    &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;:has&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;Shadow DOM&lt;/li&gt;
    &lt;li&gt;Comparison functions like &lt;code class=&quot;highlighter-rouge&quot;&gt;clamp&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;HDR Color&lt;/li&gt;
    &lt;li&gt;Nesting (!!)&lt;/li&gt;
    &lt;li&gt;Cascade Layers&lt;/li&gt;
    &lt;li&gt;Future SPA-like behavior in MPAs with ViewTransition API&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Eh, but nope, Tailwind is the &lt;em&gt;best thing that ever happened to CSS&lt;/em&gt;. 😝 (Perhaps it’s worth mentioning that Tailwind would do absolutely ZERO of these things without actual CSS functioning?! After all, Tailwind is essentially just a bespoke DSL… 😃)&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;Adam Watham is turning into Gigachad with all the self-improvement and I’m all for it!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Any discussion of any programmer’s personal appearance (especially women and queer/non-binary folks, but cishet men too) makes me extremely uncomfortable. Adam lost weight and grew muscles, awesome. Remind me again &lt;em&gt;how that’s in any way relevant to a CSS framework??&lt;/em&gt;&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;Tailwind team is the definition of the perfection&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Hmm… 🤔&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;Glad to see they’re striving for the ultimate DX.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Doesn’t that describe, like, every software library tho? 😅 “Naw, I’m striving for the most mid DX. Download today and experience the mediocre!”&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;Just wanna start using Tailwind CSS. Can’t wait to get into it.&lt;br /&gt;
Just love it already 🥰💯&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;This technology is so good, I’m 100% in love and I haven’t even used it yet!&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;“My vision for what I want to help people with Tailwind which is build CUSTOM stuff” -Adam&lt;br /&gt;
that’s why I use tailwind since his first release, I never use css frameworks, no bootstrap, no anything, until I discover tailwind! because I’d always want CUSTOM stuff, thank you Adam!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Huh? 🤔&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;Always a joy to listen to Adam&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Always a joy!&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;After i tried tailwind i couldnt stop using it, i use it for everything to the point where I feel like im forgetting vanilla css syntax 😂&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Whoa, this one needs a warning label! I haven’t see anything this scary in a long time! 😱&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;after an year of raw CSS i switched to tailwind css and felt like heaven&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;🌈🦄&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;Adam and the team, you’re all just awesome. Thank you.. Adam and the team, you’re all just awesome. Thank you..&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;&lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
Adam and the team, you&apos;re all just awesome. Thank you..
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;I think Tailwind should just become the new css standard, like even when am in vanilla css, I check tailwind for documentation on the css property!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Duuude, wait until I show you &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS&quot;&gt;MDN&lt;/a&gt;. 🤯&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;Great product glad to be alive and see the revolution of css hahah 😂 to tailwind&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;I’m just so thankful to have lived to see the day…the day when…Tailwind.&lt;/p&gt;
&lt;/details&gt;

&lt;blockquote&gt;
  &lt;p&gt;The single most revolutionising  architecture ever created …&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Hmm… 🤔&lt;/summary&gt;
  &lt;p&gt;Single! Most! Revolutionising! (&lt;em&gt;sic&lt;/em&gt;) Architecture! Ever! Created!&lt;/p&gt;
&lt;/details&gt;

&lt;hr /&gt;

&lt;p&gt;OK, I’m calling it.*&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Tailwind is a cult.&lt;/strong&gt; 👈&lt;/p&gt;

&lt;p&gt;(* Also I’m sick of scrolling through all those creepy YouTube comments. Yeeesh!)&lt;/p&gt;

&lt;style&gt;
main details {
  margin-block-end: var(--size-6);
}

main details &gt; summary {
  cursor: pointer;
  font-size: smaller;
  font-weight: bold;
  color: var(--gray-7);
}

main details[open] &gt; summary {
  margin-block-end: var(--size-1);
}
&lt;/style&gt;</content><author><name></name></author><category term="tailwind" /><category term="css" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" /><media:content medium="image" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Enhance vs. Lit vs. WebC…or, How to Server-Render a Web Component</title><link href="https://www.spicyweb.dev/web-components-ssr-node/" rel="alternate" type="text/html" title="Enhance vs. Lit vs. WebC…or, How to Server-Render a Web Component" /><published>2023-08-13T00:00:00-07:00</published><updated>2023-08-13T00:00:00-07:00</updated><id>repo://posts.collection/_posts/2023-08-13-web-components-ssr-node.md</id><content type="html" xml:base="https://www.spicyweb.dev/web-components-ssr-node/">&lt;p&gt;One thing I’d like to get off my chest right away.&lt;/p&gt;

&lt;p&gt;It was &lt;em&gt;always&lt;/em&gt; possible to render custom elements on the server. Any server.&lt;/p&gt;

&lt;p&gt;Setting any “web component-y JavaScript-y” bits aside, you could always output something like this &lt;mark&gt;directly in your HTML&lt;/mark&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;my-custom-element&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;my-custom-header&amp;gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Wee!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/he&amp;gt;&amp;lt;/my-custom-header&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;my-custom-button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;variant=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;button&amp;gt;&lt;/span&gt;A button!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&amp;lt;/my-custom-button&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/my-custom-element&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then you could style all of those custom elements however you like:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;my-custom-element&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;lightyellow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;my-custom-header&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding-bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin-bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;border-bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rebeccapurple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;my-custom-header&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;my-custom-button&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;gray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.75rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;inherit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;my-custom-button&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;variant&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;navy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Look Ma! Custom elements! Rendered by a server! JavaScript totally optional! 😲&lt;/p&gt;

&lt;p&gt;OK OK, I know what you’re thinking. I’m leaving out some key details…&lt;/p&gt;

&lt;h2 id=&quot;component-centric-view-architecture-and-shadow-dom&quot;&gt;Component-Centric View Architecture and Shadow DOM&lt;/h2&gt;

&lt;p&gt;What people &lt;em&gt;usually&lt;/em&gt; mean when they ask if they can SSR their web components is this: hey, can I take the code I’ve already written for client-side web components using all of the web component APIs (especially shadow DOM), and basically take an on-the-fly snapshot of that render tree and stick it in my output HTML (whether for a static site or a dynamic server)?&lt;/p&gt;

&lt;p&gt;And so we’ve seen various projects, most notably Lit, tackle this very problem and provide a mechanism to render web components on the server, with an optional hydration step on the client to take those server-rendered components and make them interactive after initial load.&lt;/p&gt;

&lt;p&gt;Yet, this sort of “isomorphic” architecture where client and server code is essentially identical has increasingly fallen out of step with what many developers actually want. Components which &lt;em&gt;only&lt;/em&gt; run on the server are making a big splash across a variety of ecosystems, with &lt;a href=&quot;https://nextjs.org/docs/getting-started/react-essentials&quot;&gt;even React/Next.js getting in on the action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I like using the term “&lt;strong&gt;split components&lt;/strong&gt;” which I first heard about from &lt;a href=&quot;https://markojs.com/docs/class-components/#split-components&quot;&gt;the Marko project&lt;/a&gt;. A split component essentially divides each component into two: a server-only portion, and a client-only portion. Sometimes you can pull some or all of the server logic over to the client as well for full isomorphism, but oftentimes you simply don’t need to.&lt;/p&gt;

&lt;p&gt;I’m going to make a bold statement to kick off our explorations of web component SSR’ing today:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The future of web components is &lt;mark&gt;split components&lt;/mark&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Or to put it another way, the future of web components is HTML-first, server-first—a mantra I’ve been preaching for a long time.&lt;/p&gt;

&lt;p&gt;But this means we have a lot to do. The web components spec historically has been pretty squarely focused on client-side APIs which potentially make the most sense in a world of client-driven app architecture, namely &lt;mark&gt;SPAs&lt;/mark&gt;. It pains me that as many frameworks are deep in the throes of migrating away from &lt;em&gt;SPA-as-default&lt;/em&gt; across our industry—&lt;em&gt;thank god!&lt;/em&gt;—web components feel behind the times. But there are promising signs ahead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Declarative Shadow DOM&lt;/strong&gt; lets us server-render the internals—the guts if you will—of web components and ship those directly to the browser. Yes, Firefox still needs a simple polyfill (&lt;em&gt;2024 update: not any longer! DSD is supported by Firefox!&lt;/em&gt;) which is really no biggie, and for Chrome and Safari you can display your server-rendered components &lt;em&gt;including shadow DOM&lt;/em&gt; with &lt;strong&gt;zero JavaScript&lt;/strong&gt;. Pretty darn cool.&lt;/p&gt;

&lt;p&gt;Furthermore, there are ongoing efforts to spec other parts of the web component APIs for a world of declarative markup. In the future we might gain declarative custom element registration and declarative ElementInternals functionality and even declarative reactive templates using DOM parts. People who work on this stuff at the spec and browser implementation level now understand the gravity of the situation. If a major API is &lt;em&gt;only&lt;/em&gt; available to client-side JavaScript, it’s less likely to work well in an HTML-first, server-first landscape.&lt;/p&gt;

&lt;p&gt;But…we’re not here to dream about the future. We’re here to see what we can do &lt;strong&gt;right here, right now&lt;/strong&gt;. And so on with the show!&lt;/p&gt;

&lt;h2 id=&quot;theres-node-business-like-shadow-business&quot;&gt;There’s Node Business Like Sh(ad)ow Business&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Groan.&lt;/em&gt; 😅&lt;/p&gt;

&lt;p&gt;I’m going to demonstrate three different component formats which you can use today in any Node project inside of any potential template rendering environment. These are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/enhance-dev/enhance-ssr&quot;&gt;Enhance&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://lit.dev/docs/ssr/overview/&quot;&gt;Lit&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/11ty/webc&quot;&gt;WebC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of the two, Enhance and WebC are closer in terms of general concept. Both start with a “0kb JS” mindset and use terminology of “expansion”—aka, your top-level HTML templates include custom element tags, which then are “expanded” with the full rendered components…and those components might expand other components, and so on and so forth.&lt;/p&gt;

&lt;p&gt;Lit’s certainly a bit different and, as a popular library for web component authors, is a well-known quantity. Like I said, Lit is typically used for “isomorphic” components (aka the code you SSR and the code you load client-side is virtually identical). However, there’s actually no hard-and-fast reason you &lt;em&gt;have&lt;/em&gt; to do it this way. And so I demonstrate that Lit too could be used to author “split components”—although you’re fairly “on your own” in term of practical applications of it.&lt;/p&gt;

&lt;p&gt;Starting in alphabetical order, let’s first look at Enhance.&lt;/p&gt;

&lt;h2 id=&quot;region-3-move-down-5-zoom-in-300-enhance&quot;&gt;Region 3. Move Down 5. Zoom In 300%. Enhance!&lt;/h2&gt;

&lt;p style=&quot;text-align:center&quot;&gt;&lt;a href=&quot;https://thespicyweb.github.io/wc-ssr-demos/enhance.html&quot; class=&quot;button is-red&quot;&gt;Demo&lt;/a&gt; &lt;a href=&quot;https://github.com/thespicyweb/wc-ssr-demos/tree/main/enhance-ssr&quot; class=&quot;button is-spaced is-purple&quot;&gt;Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enhance made a splash a while back as &lt;a href=&quot;https://enhance.dev&quot;&gt;a fullstack framework&lt;/a&gt; you can use to build entire web applications, all centered around the concept of server-rendering custom elements. What’s particularly nice about how Enhance is architected is that the piece which deals directly with component SSR is a standalone library anyone can grab off the shelf and use.&lt;/p&gt;

&lt;p&gt;Here’s an example of an Enhance component:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attrs&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;easy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attrs&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`
    &amp;lt;style&amp;gt;
      :host {
        display: block;
        padding: .5rem 1rem;
        border: 3px double orange;
      }

      h2 {
        color: maroon;
      }
    &amp;lt;/style&amp;gt;

    &amp;lt;h2&amp;gt;Hello &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&amp;lt;/h2&amp;gt;

    &amp;lt;p&amp;gt;Easy as &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;easy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;. &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;&amp;lt;/p&amp;gt;
  `&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here’s a (simplified) example of rendering it in a main server process:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyElement&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./my-element.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;enhance&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@enhance/ssr&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;enhance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;elements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;my-element&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;easy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`
  &amp;lt;my-element hello=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; easy=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;easy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;gt;Boom done.&amp;lt;/my-element&amp;gt;
`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can look at the full example code in the repo link above.&lt;/p&gt;

&lt;p&gt;What I like about this is its conceptual simplicity. There’s not a lot you need to learn. If you know the basics of HTML, CSS, JavaScript, and Node, you can use this to DIY your own web framework.&lt;/p&gt;

&lt;p&gt;Enhance also provides some nice style scoping features right out of the box. By default, Enhance components &lt;em&gt;do not&lt;/em&gt; use shadow DOM. You can certainly opt-into it though, but if you don’t, you can still use some of the same nomenclature in your component styles like &lt;code class=&quot;highlighter-rouge&quot;&gt;:host&lt;/code&gt;, and all of the selectors will end up prefixed with the tag name automatically. In other words:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;:host&lt;/code&gt; becomes &lt;code class=&quot;highlighter-rouge&quot;&gt;my-element&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;p&lt;/code&gt; becomes &lt;code class=&quot;highlighter-rouge&quot;&gt;my-element p&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;.cls&lt;/code&gt; becomes &lt;code class=&quot;highlighter-rouge&quot;&gt;my-element .cls&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and so on. Neat-o!&lt;/p&gt;

&lt;p&gt;Because Enhance components are server-only components from the start, the way you add your client-side web component code is simply by adding a script tag to your component template. So for example, I have an &lt;code class=&quot;highlighter-rouge&quot;&gt;s-dom.js&lt;/code&gt; Enhance component which uses a combination of Declarative Shadow DOM and “light DOM” content, which is fantastic, and my client component is simply embedded at the bottom of the template like so:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SDomElement&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;HTMLElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;customElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;s-dom&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;connectedCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shadowRoot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;para&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;border&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;1px dashed gray&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;para&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;0.5em&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Enhance is smart enough to collect all of the scripts in the entire render tree and include them only once at the bottom of &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. (And components styles are also collected and included in &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;While there’s much to recommend here, there are also some things I don’t like about Enhance which I’ll cover shortly. I also had to patch Enhance so that I could get at its rendered &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; content separately from the &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;body&amp;gt;&lt;/code&gt; content. I think Enhance is used to the idea it’s always rendering an entire HTML page template. &lt;a href=&quot;https://github.com/enhance-dev/enhance-ssr/issues/51&quot;&gt;I filed an issue on GitHub&lt;/a&gt;, so we’ll see if this is a use case they’d like to support going forward.&lt;/p&gt;

&lt;h2 id=&quot;this-component-is-lit-lit-af&quot;&gt;This Component is Lit. Lit AF.&lt;/h2&gt;

&lt;p&gt;Yes, it’s mandatory to engage in juvenile humor when talking about &lt;a href=&quot;https://lit.dev&quot;&gt;Lit&lt;/a&gt;. &lt;em&gt;Listen, I don’t make the rules!&lt;/em&gt;&lt;/p&gt;

&lt;p style=&quot;text-align:center&quot;&gt;&lt;a href=&quot;https://thespicyweb.github.io/wc-ssr-demos/lit.html&quot; class=&quot;button is-red&quot;&gt;Demo&lt;/a&gt; &lt;a href=&quot;https://github.com/thespicyweb/wc-ssr-demos/tree/main/lit-ssr&quot; class=&quot;button is-spaced is-purple&quot;&gt;Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So for our Lit example, things are a wee bit different than Enhance. Lit always defaults to using shadow DOM, and while you can switch it off, stuff sort of breaks because slotted content is no longer a thing (and scoped styles are more of a pain). So I just left it all as is. I’m a huge shadow DOM fan so that doesn’t bother me, but for folks who have an aversion to it or it doesn’t play well with the rest of their HTML/CSS architecture, it might prove a hurdle.&lt;/p&gt;

&lt;p&gt;Here’s a component in Lit, similar to the first Enhance example:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LitElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;lit&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyElement&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LitElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;styles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`
    :host {
      display: block;
      padding: .5rem 1rem;
      border: 3px double orange;
    }

    h2 {
      color: maroon;
    }
  `&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;properties&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;easy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;easy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`
      &amp;lt;h2&amp;gt;Hello &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&amp;lt;/h2&amp;gt;

      &amp;lt;p&amp;gt;Easy as &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;easy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;. &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;&amp;lt;/p&amp;gt;
    `&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;customElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;my-element&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pretty standard fare if you’re used to the Lit API. The question is, how easily can you server-render this component? Well, you be the judge:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@lit-labs/ssr&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;collectResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@lit-labs/ssr/lib/render-result.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;lit&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./my-element.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;easy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;collectResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`
  &amp;lt;my-element hello=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; .easy=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;easy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;gt;Boom done.&amp;lt;/my-element&amp;gt;
`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In a streaming scenario, they recommend working with &lt;code class=&quot;highlighter-rouge&quot;&gt;RenderResultReadable&lt;/code&gt; so the output response can flush at various intervals to the browser, but in simpler contexts this seems to work just fine.&lt;/p&gt;

&lt;p&gt;The split component mechanism is a bit wonky, but still doable. I ended up adding a separate template as a static property of the component, and then including that in a separate render of JS assets:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// in s-dom.js&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;clientComponent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`
  &amp;lt;script type=&quot;module&quot;&amp;gt;
    class SDomElement extends HTMLElement {
      static {
        customElements.define(&quot;s-dom&quot;, this)
      }

      connectedCallback() {
        const para = this.shadowRoot.querySelector(&quot;p&quot;)
        para.style.border = &quot;1px dashed gray&quot;
        para.style.padding = &quot;0.5em&quot;
      }
    }
  &amp;lt;/script&amp;gt;
`&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// and then later:&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jsResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;collectResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SDom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;clientComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So…this is doable, but it probably just makes more sense to have a separate &lt;code class=&quot;highlighter-rouge&quot;&gt;.js&lt;/code&gt; file for your client component and include that as part of whatever frontend build/bundling pipeline you might have. Maybe you even adopt &lt;code class=&quot;highlighter-rouge&quot;&gt;.server.js&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;.client.js&lt;/code&gt; conventions, and for components where you want the same one running in both places…&lt;code class=&quot;highlighter-rouge&quot;&gt;.iso.js&lt;/code&gt;? &lt;code class=&quot;highlighter-rouge&quot;&gt;.hybrid.js&lt;/code&gt;? Hmm.&lt;/p&gt;

&lt;p&gt;(It should be noted the Enhance example doesn’t offer an isomorphic possibility, only split components. So if you think you’ll actually need to write lots of isomorphic components, Lit becomes awfully attractive…although &lt;a href=&quot;https://lit.dev/docs/ssr/authoring/&quot;&gt;the number of caveats&lt;/a&gt; you need to keep all in your head is quite daunting.)&lt;/p&gt;

&lt;h2 id=&quot;webc-lets-you-render-components-for-the-web-see&quot;&gt;WebC Lets You Render Components for the Web, See?&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Yeah, see? You can now render components on the server, see? Yeah!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=wIxnW_nB4Lk&quot;&gt;Like Johnny Rocco&lt;/a&gt;, I “want more” when it comes to options for custom elements SSR, and so we arrive at our third and final demonstration which is WebC.&lt;/p&gt;

&lt;p style=&quot;text-align:center&quot;&gt;&lt;a href=&quot;https://thespicyweb.github.io/wc-ssr-demos/webc.html&quot; class=&quot;button is-red&quot;&gt;Demo&lt;/a&gt; &lt;a href=&quot;https://github.com/thespicyweb/wc-ssr-demos/tree/main/webc-ssr&quot; class=&quot;button is-spaced is-purple&quot;&gt;Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WebC grew out of the &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; project, and like Enhance you can simply spin up an Eleventy site and install the WebC plugin and you’re off to the races with a full framework (albeit mostly oriented towards static sites).&lt;/p&gt;

&lt;p&gt;For our purposes, WebC can be used standalone without any major gotchas and the API is straightforward (although once again I must admit it took me a bit of time to stumble onto the right formula).&lt;/p&gt;

&lt;p&gt;It’s worth explaining up front that WebC is quite different from Enhance and Lit in terms of the syntax of writing a component. WebC doesn’t use JavaScript files like the other two. WebC provides its own HTML-based SFC (Single-File Component) format which looks a lot more like Vue or Svelte. JavaScript can be &lt;mark&gt;embedded&lt;/mark&gt; into component files, whether it’s code to run on the server or code to include as scripts for the client.&lt;/p&gt;

&lt;p&gt;Here’s an example of a WebC component:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;webc:setup&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;style &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;webc:scoped=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-element&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;:host&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;.5rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;orange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;h2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;maroon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Hello &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;text=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Easy as &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;text=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;join(easy)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;. &lt;span class=&quot;nt&quot;&gt;&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here’s how to render it. We’ll use a page entrypoint to include the element and then render that entrypoint from our Node process:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;my-element&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hello=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;easy=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;easy&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Boom done.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/my-element&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;WebC&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@11ty/webc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;page&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;WebC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineComponents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./webc-ssr/components/*.webc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setInputPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./webc-ssr/page.webc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setBundlerMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;easy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;WebC happily provides you with separate HTML, collected styles, and collected scripts which you can then include inline on your page or hand off to some other frontend process.&lt;/p&gt;

&lt;p&gt;WebC also provides a “light DOM” scoping mechanism similar to Enhance, except that it uses &lt;mark&gt;hashed class names&lt;/mark&gt; by default and you have to manually specify a human-readable class name (and then it’s a class not the actual tag name as the selector prefix). It’s fine, but I’d prefer true scoping at the element level. Certainly shadow DOM can also be used in WebC for style scoping.&lt;/p&gt;

&lt;p&gt;Unfortunately, the interactions between WebC server-level slots and shadow DOM slots are &lt;strong&gt;painful&lt;/strong&gt;, far more than in Enhance. Within your Declarative Shadow DOM template, you have to add a &lt;code class=&quot;highlighter-rouge&quot;&gt;webc:keep&lt;/code&gt; attribute to maintain proper semantics in the output HTML. And for “light DOM” tags which need to preserve a &lt;code class=&quot;highlighter-rouge&quot;&gt;slot&lt;/code&gt; attribute and avoid processing by WebC, you have to add something like &lt;code class=&quot;highlighter-rouge&quot;&gt;@attributes=&quot;({ slot: &apos;hello&apos;})&quot;&lt;/code&gt;. Again, it really took some time to figure this all out! There’s &lt;a href=&quot;https://github.com/11ty/webc/issues/162&quot;&gt;an issue to track this&lt;/a&gt; if you’re curious to see if the WebC folks can come up with a simpler solution.&lt;/p&gt;

&lt;p&gt;It also took me a beat to figure out how to process incoming component state in JavaScript (aka to join the incoming array into a string) in my component template. Providing a function in &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;script webc:setup&amp;gt;&lt;/code&gt; to use in the HTML portion is pretty obvious in hindsight, but I fiddled around with a lot of other approaches before stumbling across this one.&lt;/p&gt;

&lt;p&gt;In spite of these gotchas, I really appreciate WebC’s aesthetic, from its API design to the HTML template syntax. Others may disagree, but I truly enjoy authoring HTML &lt;em&gt;as HTML&lt;/em&gt;, and I’ll talk much more about this in a moment. (Just be aware that, like Enhance and unlike Lit, WebC doesn’t offer isomorphic components, only split components.)&lt;/p&gt;

&lt;aside&gt;

  &lt;p&gt;By the way, in all of these examples I also wanted to experiment with pulling in a few shared styles into a shadow DOM template. Doing so wasn’t hard, but it feels a bit skeevy executing low-level file system commands and simply injecting CSS as text. Maybe I’m missing something, but otherwise, it’s an area of improvement these frameworks could look into down the road.&lt;/p&gt;

&lt;/aside&gt;

&lt;h2 id=&quot;enhance-vs-lit-vs-webc-who-wins&quot;&gt;Enhance vs. Lit vs. WebC: Who Wins?&lt;/h2&gt;

&lt;p&gt;The short answer: &lt;em&gt;none of them&lt;/em&gt;. 🤡&lt;/p&gt;

&lt;p&gt;So here’s a thing &lt;strong&gt;which really annoys me&lt;/strong&gt; about the Node ecosystem. There’s no community protocol or meta-library (that I’m aware of at least) which lets you easily render templates from any server configuration.&lt;/p&gt;

&lt;p&gt;In Ruby, we have &lt;a href=&quot;https://github.com/rtomayko/tilt&quot;&gt;Tilt&lt;/a&gt;. Tilt lets you render templates in a wide variety of languages, both out of the box or via third-party plugins. And by way of example, I actually created my own template language a while back as an offshoot of ERB, called &lt;a href=&quot;https://serbea.dev&quot;&gt;Serbea&lt;/a&gt;. Because Serbea is available through Tilt, you can render Serbea templates from any Ruby web framework easily. Adding support for Rails was &lt;a href=&quot;https://github.com/bridgetownrb/serbea/blob/cbe2e02b6625b5a7d4c5499f068b0918dd8f5cdf/serbea-rails/lib/serbea-rails.rb#L28&quot;&gt;mere lines of code&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Now this does break down a bit when talking about real server components. Ruby frameworks have to supply their own partial/component render methods to use with Tilt. We also have work to do here it would seem.&lt;/p&gt;

&lt;p&gt;Nevertheless, in an ideal world, there’s be “Nilt” (Node…Interface (for) Loading Templates? Hey, that tracks!), and Enhance and Lit and WebC and a dozen other flavors of this stuff would all just plug into “Nilt” and a variety of web frameworks would know how to use “Nilt” for views and you’d be up and running in no time. &lt;mark&gt;Choose your flavor.&lt;/mark&gt;&lt;/p&gt;

&lt;p&gt;Instead…if you’re going to use Enhance you might as well use Enhance (the framework). And if you’re going to use WebC you might as well use Eleventy. And if you’re going to use Lit…well, good luck with that. There’s no “Next.js but for Lit” (yet). &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/lit/&quot;&gt;Maybe use Astro?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Speaking of Astro, it &lt;em&gt;also&lt;/em&gt; comes with a shiny-new server component format which you &lt;em&gt;could&lt;/em&gt; use alongside custom elements. But it only works in Astro! 😭)&lt;/p&gt;

&lt;p&gt;I think it’s vitally important we keep talking about and emphasizing the need for interop, hence the reason I wanted to write this article. And here’s where I really get on my soapbox, so buckle up folks!&lt;/p&gt;

&lt;p&gt;&lt;mark&gt;The promise of web components&lt;/mark&gt; in browsers is that you get a true &lt;em&gt;write once, run anywhere&lt;/em&gt; API. I can write my card or my color picker or my audio player or my site header or my business dashboard &lt;strong&gt;once&lt;/strong&gt;, and utilize those components anywhere. I don’t need to tear down the whole world and rebuild from scratch because I’m using Vue, no wait React, no wait Svelte, no wait Solid, no wait Qwik, no wait…&lt;/p&gt;

&lt;p&gt;And because web component APIs use &lt;mark&gt;declarative HTML standards&lt;/mark&gt; as their launching pad, in theory my web server could be anything—&lt;em&gt;as it should be.&lt;/em&gt; Render web components from Ruby! From Python! From Java! From Elixir! From whatever the heck &lt;mark&gt;you and only you&lt;/mark&gt; like!&lt;/p&gt;

&lt;p&gt;What we truly need is a sense of &lt;em&gt;server-side interop&lt;/em&gt;, a vision of a component format where you could squint and see how server components could be authored in one environment and ported over to another environment without too much hassle.&lt;/p&gt;

&lt;p&gt;And that brings me to my actual &lt;strong&gt;clear winner&lt;/strong&gt; in all of this. &lt;em&gt;Drumroll, please!&lt;/em&gt; 🥁&lt;/p&gt;

&lt;p&gt;&lt;mark&gt;&lt;strong&gt;It’s WebC.&lt;/strong&gt;&lt;/mark&gt;&lt;/p&gt;

&lt;h2 id=&quot;webc-an-enchanting-path-forward&quot;&gt;WebC: an Enchanting Path Forward&lt;/h2&gt;

&lt;p&gt;WebC is the only tool I’m aware of on the market today which lets you author web components &lt;em&gt;as HTML&lt;/em&gt; from the very start. The &lt;code class=&quot;highlighter-rouge&quot;&gt;.webc&lt;/code&gt; extension of a component highlights that while this isn’t pure HTML, it certainly isn’t JavaScript either. And that’s the Achilles’ heel of technologies like Enhance or Lit. It’s HTML-in-JS, CSS-in-JS…nothing but &lt;code class=&quot;highlighter-rouge&quot;&gt;.js&lt;/code&gt; files as far as the eye can see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I really hate writing HTML in JavaScript files.&lt;/strong&gt; 😬&lt;/p&gt;

&lt;p&gt;It’s not how the web was architected. The browser downloads HTML first—&lt;em&gt;then&lt;/em&gt; processes any styles or scripts which are either contained inline or linked to externally. As someone with nearly 30 years of experience working on the web, I instinctively think HTML-first when I think about how to structure my page, structure my layout, structure my components. The content always comes first, &lt;em&gt;then&lt;/em&gt; presentation, &lt;em&gt;then&lt;/em&gt; behavior. HTML, &lt;em&gt;then&lt;/em&gt; CSS, &lt;em&gt;then&lt;/em&gt; JavaScript.&lt;/p&gt;

&lt;p style=&quot;text-align:center&quot;&gt;&lt;img src=&quot;/images/this-is-the-way.gif&quot; alt=&quot;This is the way GIF from The Madalorian TV show&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And past eras of tooling understood this. Most “old-school” HTML template languages start with pure HTML-as-string-data and let you sprinkle in your code bits. Heck, PHP is essentially an entire language that was built to be HTML-first! The reason you have to include &lt;code class=&quot;highlighter-rouge&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;&lt;/code&gt; at the top of every PHP code file is because otherwise it’s just a text template! (&lt;a href=&quot;https://www.php.net/manual/en/intro-whatis.php&quot;&gt;Look at this opening example in PHP’s documentation if you don’t believe me…&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;modern evolution of this concept&lt;/strong&gt; is a component format which actually parses your HTML server-side to turn it into the server equivalent of a DOM tree, and then walks the tree searching for special attributes, directives, handlebars, etc. to apply processing. This is slower and more effort than basic string manipulation/RegEx-fu like the older template formats, but it provides opportunities for better syntax, fewer bugs and security holes, and tighter integration between the HTML markup and the underlying server programming language.&lt;/p&gt;

&lt;p&gt;WebC takes full advantage of this concept. When you write &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;span @text=&quot;msg&quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;/code&gt; instead of something like &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;span&amp;gt;&amp;lt;?php echo $msg; ?&amp;gt;&amp;lt;/span&amp;gt;&lt;/code&gt;, you’re moving from &lt;mark&gt;old-and-busted&lt;/mark&gt; “inject string output here in text file” to &lt;mark&gt;new-hotness&lt;/mark&gt; “when you process this dynamic attribute, swap the execution output into this node’s contents”. In other words, you’re writing a &lt;em&gt;declarative&lt;/em&gt; template, but it’s translating that into &lt;em&gt;imperative&lt;/em&gt; manipulations of the DOM, just like it would on the client in a component format there.&lt;/p&gt;

&lt;p&gt;And while WebC only runs in a JavaScript server context, you could imagine applying these same concepts and using the same component format nearly unchanged elsewhere. Yeah, that’s right. Imagine, say, a “WebR” that’s a Ruby port of WebC. The above WebC example could look like this:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;webc:setup&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;application/ruby&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Easy as &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;text=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;join(easy)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;. &lt;span class=&quot;nt&quot;&gt;&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s what’s astounding about this hypothetical example. I rewrote the script block which executes on the server as Ruby code, &lt;em&gt;but the HTML template didn’t have to change&lt;/em&gt;. &lt;strong&gt;At all.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Very little would have to change when you think about it. In server-rendered applications, most logic lives elsewhere. Controllers or routes pull content from databases and handle requests, models or entities encapsulate records, and you can easily write functions or PO(X)Os (Plain Old Ruby / JavaScript / Python / etc. Objects) to mange all sorts of business logic. The view layer only has to provide a base level of smarts to take a data structure defined elsewhere and translate it into markup.&lt;/p&gt;

&lt;p&gt;It’s only in the so-called “modern” world of SPAs where components have fast expanded like a virus to take over the bulk of application architecture. You’re fetching data from APIs and handling forms and validating data and executing business logic &lt;em&gt;all from view-layer components&lt;/em&gt;. It’s nothing but another form of &lt;a href=&quot;https://en.wikipedia.org/wiki/Big_ball_of_mud&quot;&gt;big ball of mud&lt;/a&gt; software architecture.&lt;/p&gt;

&lt;p&gt;We have a real opportunity to &lt;mark&gt;reset expectations and reassert best practices&lt;/mark&gt; and codebase health, and I’m &lt;em&gt;very&lt;/em&gt; excited about this. I can easily see a future where:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A variety of SSR web frameworks across a variety of languages…&lt;/li&gt;
  &lt;li&gt;…Use something like WebC as a common server component format…&lt;/li&gt;
  &lt;li&gt;…And then WebC-friendly client libraries help bridge the gap between “split components” and possibly handle WebC-style templates client-side as well for true isomorphism.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way, you don’t really need to learn the “Enhance” way or the “Lit” way or the “Astro” way or the this way or the that way to just write a freakin’ web component. 🤪&lt;/p&gt;

&lt;p&gt;Put another way, we desperately need a common language around thinking of web components as &lt;strong&gt;fullstack components&lt;/strong&gt;. Custom elements aren’t merely a way to spin up some JavaScript and attach it to a node in the browser. They form &lt;em&gt;the baseplate of the view layer&lt;/em&gt; of your web application.&lt;/p&gt;

&lt;p&gt;I have to say, I really do appreciate what the Enhance folks are doing. Because they too see a world of fullstack custom elements-as-view-layer and are making that a reality today in the Enhance framework. It’s good stuff.&lt;/p&gt;

&lt;p&gt;But then again, I keep getting tripped up by the JavaScript-centric nature of it. I can never truly get onboard with a universal web component format which is unable ever to be rendered by the infinite number of other web servers out there &lt;strong&gt;which aren’t Node/Deno/Edge/etc.&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And so I find myself &lt;mark&gt;extremely enthused by WebC&lt;/mark&gt;. I think Zach Leatherman struck gold when working on this format. And even if we end up with further permutations down the road (and admittedly it’s an area of intense experimentation for me), WebC is the technology to take seriously today.&lt;/p&gt;

&lt;aside&gt;

  &lt;p&gt;&lt;em&gt;What do you think?&lt;/em&gt; Do you envision yourself writing server components using WebC or one of the other technologies here? Are you looking to SSR web components you’ve already written? What would you like to see Node environments and other servers do to add comprehensive support for custom elements? I’d love to hear your thoughts via &lt;a href=&quot;mailto:jared@whitefusion.studio&quot;&gt;email&lt;/a&gt;, &lt;a href=&quot;https://indieweb.social/@jaredwhite&quot;&gt;Mastodon&lt;/a&gt;, or in &lt;a href=&quot;https://discord.gg/CUuYVH7Qa9&quot;&gt;Discord&lt;/a&gt;. Cheers!&lt;/p&gt;

&lt;/aside&gt;</content><author><name></name></author><category term="html" /><category term="webcomponents" /><category term="node" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" /><media:content medium="image" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">My Journey Away from the JAMstack</title><link href="https://www.spicyweb.dev/farewell-jamstack/" rel="alternate" type="text/html" title="My Journey Away from the JAMstack" /><published>2023-07-31T00:00:00-07:00</published><updated>2023-07-31T00:00:00-07:00</updated><id>repo://posts.collection/_posts/2023-07-31-farewell-jamstack.md</id><content type="html" xml:base="https://www.spicyweb.dev/farewell-jamstack/">&lt;p&gt;Before I give you my side of the story, I’d like to point you to &lt;a href=&quot;https://remotesynthesis.com/blog/goodbye-jamstack/&quot;&gt;Brian Rinaldi’s comprehensive take on the demise of Jamstack&lt;/a&gt; (or as I still prefer to call it, JAMstack) for some much-needed context on what’s been going down. He asks “is Jamstack officially finished?” and this article is essentially my reply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; the answer is &lt;em&gt;yes&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As for the reason why, we must point our finger straight at &lt;a href=&quot;https://netlify.com&quot;&gt;Netlify&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Listen, I get it. Running a successful and hopefully profitable hosting company with investors breathing down your neck is hard. I don’t begrudge them for having to pivot to enterprise cloud mumbo-jumbo in order to reel in the big bucks and justify their valuation.&lt;/p&gt;

&lt;p&gt;But I can’t help but feel duped…like so much of the other “enshittification” we’ve been dealing with in tech over the last few years. &lt;strong&gt;The cycle repeats itself&lt;/strong&gt;: we invest our hard-earned time and sometimes money to build on top of friendly, seemingly benign platforms—only to see those platforms wriggle out from under us and morph into something entirely different (and for our purposes, much worse).&lt;/p&gt;

&lt;p&gt;Gather around folks, and listen to my story of my first experience with the JAMstack. I’ll also explain why prior to this news I’d already moved on from the it and from Netlify, and what instead I believe the web dev industry should be heading towards as a “default” stack.&lt;/p&gt;

&lt;h2 id=&quot;the-year-was-2015&quot;&gt;The Year Was 2015&lt;/h2&gt;

&lt;p&gt;I had just come off a lengthy stint trying to build and promote a paid, tablet-first CMS. With a failed startup behind me, as well as a number of WordPress sites I simply &lt;em&gt;hated&lt;/em&gt; to administer because they were so buggy and insecure and expensive, I was getting desperate. Due to my experience as a Ruby on Rails developer, I even tried reaching for some Rails-based CMSes, but finding a slam-dunk improvement over WordPress was far from straightforward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And then I stumbled upon Jekyll.&lt;/strong&gt; 😍&lt;/p&gt;

&lt;p&gt;To this day, I have no clue why it had initially taken me so long to discover Jekyll. Jekyll was integrated into GitHub (powering their Pages product) and was built with Ruby! I do remember hearing more and more about “static site generators” (aka SSGs) as I was winding down production of my own CMS, and I filed that thought away as a possible way to salvage some of the work I’d done.&lt;/p&gt;

&lt;p&gt;Eventually I &lt;em&gt;finally&lt;/em&gt; gave Jekyll a real try, and I was floored. Here was an amazing developer-friendly tool where I could just to take a bunch of simple HTML / Markdown, CSS, JavaScript, and image files, run a single command, and BOOM: get a website trivially easy to deploy anywhere. I even grokked the Liquid template syntax without issue, because I was already familiar with both Shopify and my own CMS which had used Liquid.&lt;/p&gt;

&lt;p&gt;The only real head-scratcher was the content authoring side of the equation—I couldn’t expect my clients to learn how to input Markdown into GitHub—but with my experience having already built authoring interfaces, I figured it wouldn’t be hard to put together a simple Rails editor app that could work with Markdown and GitHub under the hood.&lt;/p&gt;

&lt;p&gt;I dogfooded Jekyll first for my own personal website at &lt;a href=&quot;https://jaredwhite.com&quot;&gt;jaredwhite.com&lt;/a&gt;, relaunching it in February 2016. (It’s since been resigned many times and is now built with &lt;a href=&quot;https://www.bridgetownrb.com&quot;&gt;Bridgetown&lt;/a&gt; instead…but it remains a static site!) From there, I worked on a variety of projects for myself and for clients. To this very day, some of those sites are still on the web humming along without issue (&lt;a href=&quot;https://www.sengoidelc.com/&quot;&gt;here’s one of my favorites&lt;/a&gt;) because, hey, &lt;em&gt;static sites are awesome!&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;along-came-aerobatic-netlify&quot;&gt;Along Came &lt;del&gt;Aerobatic&lt;/del&gt; Netlify&lt;/h2&gt;

&lt;p&gt;When I first got into this brand-new world of modern SSGs, the gold standard for hosting Ruby-powered web applications was Heroku. I was quite familiar with Heroku and had used it on a number of projects. But Heroku had nothing to offer me when it came to SSGs. Heroku was engineered around a model of dynamic web servers and databases, not build-once-and-cache-on-a-CDN-forever deployments.&lt;/p&gt;

&lt;p&gt;I suppose I could have just used GitHub Pages, but at that time I was primarily using Bitbucket for hosting my projects and those of my clients. So it was perfect timing that, right when I needed to figure this all out, along came &lt;a href=&quot;https://web.archive.org/web/20151029053713/http://www.aerobatic.com:80/blog/websites-for-bitbucket-powered-by-aerobatic.html&quot;&gt;Aerobatic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Aerobatic was basically Heroku but for static sites hosted on Bitbucket (it was literally an add-on for that platform). &lt;strong&gt;Perfect!&lt;/strong&gt; I could easily get that “push via Git and automatically deploy” workflow going with no other setup required. And the deployed sites were fast, secure, and cheap as hell to operate indefinitely.&lt;/p&gt;

&lt;p&gt;For my client’s content editors, I usually just spun up a cheap VPS on Digital Ocean. They didn’t need to be high-powered at all, because those servers weren’t promoted to the public or accessed by more than one person really. And because all the content was stored in a Git repo, I didn’t even need to wrestle with a database!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However, my love affair with Aerobatic didn’t last long. Because another shiny offering soon emerged: Netlify.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The best way I can describe Netlify when I first started using it is “like Aerobatic…except better”. I’d encountered a few technical difficulties getting Aerobatic sites up and running, and Netlify was a noticeable  improvement. Builds were fast, deploys were rock-solid, and &lt;em&gt;It. Just. Worked.&lt;/em&gt; Plus it supported both GitHub &amp;amp; Bitbucket, so either way I was golden. I forget when they added the Forms feature, but it was pretty early on and that also proved a huge advantage.&lt;/p&gt;

&lt;p&gt;One issue many of us encountered when trying to market these amazing new “static site” to potential customers was the term &lt;em&gt;static&lt;/em&gt;. Calling a website “static” for many people implied a site which rarely changed and couldn’t accommodate any dynamic, interactive functionality. &lt;em&gt;Boring.&lt;/em&gt; For instance, surely you couldn’t run your e-commerce site as a “static” site because e-commerce is anything but static!&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;JAMstack&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-jamstack-is-here-to-solve-all-our-problems-right&quot;&gt;The JAMstack is Here to Solve All Our Problems (Right?)&lt;/h2&gt;

&lt;p&gt;Netlify’s marketing sleight of hand in &lt;a href=&quot;https://web.archive.org/web/20170315085227/https://jamstack.org/&quot;&gt;inventing and promoting the JAMstack&lt;/a&gt; was sheer brilliance. Instead of calling these builds “static sites” and these tools “static site generators”, we could say we’re building JAMstack sites (to compete with LAMPstack sites I suppose) and using some hot new JAMstack frameworks. The JAM stood for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;JavaScript&lt;/li&gt;
  &lt;li&gt;APIs&lt;/li&gt;
  &lt;li&gt;Markup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And lest anyone get confused (because as we’ll soon discover EVERYONE eventually got very confused), the JavaScript of the J in JAM referred to &lt;em&gt;client-side JS&lt;/em&gt;, not server-side. The whole point of JAMstack was that the tool building out the markup etc. could be written in anything, and just as importantly the &lt;em&gt;APIs used by the client-side JS could be written in anything&lt;/em&gt;. After all, both Jekyll and Rails are Ruby-based tools, and I happily used both as part of my JAMstack deployments.￼&lt;/p&gt;

&lt;p&gt;As time went on, a major appeal of the JAMstack was that it allowed a decoupling of the frontend from the backend, which is why Netlify and other hosts like it later on proved extremely popular with frontend developers. Ironically, in today’s world where SSR (server-side rendering) and progressive enhancement is now top of mind for many web developers, it’s positively &lt;em&gt;wild&lt;/em&gt; to turn back the clock and realize that JAMstack architecture arose during the height of the &lt;strong&gt;SPA&lt;/strong&gt; movement. You can even see it in all the marketing materials—your app “shell” could be statically deployed, and then your fancy-pants client-side app could take over and call APIs from all over the web. And a number of JAMstack sites were literally that. Disable JavaScript and what do you get? Maybe a simple header and footer if you’re lucky. &lt;em&gt;Everything else is blank.&lt;/em&gt; &lt;strong&gt;Whoops!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, that was never &lt;em&gt;my&lt;/em&gt; JAMstack. I had intuitively understood that the exciting promise of JAM was in a sense the reverse acronym of MAJ: Markup, &lt;em&gt;then&lt;/em&gt; APIs, &lt;em&gt;then&lt;/em&gt; JavaScript. In other words, build as much as you can with static HTML (via templates, Markdown, etc.), &lt;em&gt;then&lt;/em&gt; identify what you might need for some dynamic server interactions—which maybe you’d just write yourself as a Rails app or whatever—&lt;em&gt;then&lt;/em&gt; write only the JavaScript you absolutely need to access those APIs (understanding that maybe certain dynamic pages would just get fetched directly from the server if need be).&lt;/p&gt;

&lt;p&gt;In other words, &lt;strong&gt;progressive enhancement&lt;/strong&gt;. 😂&lt;/p&gt;

&lt;p&gt;But somehow that story got lost in the fray, and JAMstack eventually gave rise to a rebranded “Jamstack” with the major value prop being something rather entirely different: you could now build entire websites out of &lt;em&gt;JavaScript libraries&lt;/em&gt; (aka React, or maybe Vue or Angular or Svelte) and &lt;em&gt;JavaScript frameworks&lt;/em&gt; (aka Next.js, Gatsby, Nuxt, SvelteKit, etc.). And, whoa, look at this! You don’t need servers ever again! You can just write &lt;em&gt;serverless functions&lt;/em&gt; to go along with your frontends! Fullstack, server-first web development is dead, long live frontend + serverless!&lt;/p&gt;

&lt;p&gt;(Coinciding with this sea change, Jekyll began a long, slow, painful decline into irrelevance, due to the inexplicable failure of GitHub’s leadership to support its proper development and promotion as well as an unforgivable neglect of the Pages platform. It remains one of my greatest frustrations in 25+ years of web development…so much so that I forked Jekyll in 2020 and created &lt;a href=&quot;https://www.bridgetownrb.com&quot;&gt;Bridgetown&lt;/a&gt;. But I digress…)&lt;/p&gt;

&lt;p&gt;Along with this “second generation” Jamstack mindset shift came &lt;em&gt;an order of magnitude&lt;/em&gt; more build complexity. Instead of a straightforward CLI kicking off simple transformations to go from Markdown -&amp;gt; HTML plus concatenate some (S)CSS files together or whatever, you’d get multi-minute long builds and GBs of &lt;code class=&quot;highlighter-rouge&quot;&gt;node_modules&lt;/code&gt; and poorly-written tutorials on DEV.to about how to send emails from Gatsby functions and which distributed “web-scale” databases of the day are the coolest and crazy CLI tool churn and all sorts of other headaches. Things which used to take hours or days to accomplish in standard Rails or Laravel or Django apps—most of this stuff isn’t rocket science, folks—now took weeks or months! &lt;em&gt;Progress!&lt;/em&gt; 🤪&lt;/p&gt;

&lt;p&gt;This quick march of slapdash B.S. web technology under the guise of making our lives easier was &lt;em&gt;one hell of a whiplash&lt;/em&gt;, and I really hadn’t seen it coming when I first entered this space. Instead of JAMstack saving us all from the horrors of WordPress, we were crushed under the weight of Jamstack! Janky SPAs, countless immature buggy frameworks, and NPM ecosystem insanity—along with the multi-headed hydra that is modern React.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.spicyweb.dev/the-great-gaslighting-of-the-js-age/&quot;&gt;It got so bad I wrote about it.&lt;/a&gt; (Warning: if you think &lt;em&gt;this&lt;/em&gt; article has taken on a dour tone, avoid reading that spicy take! 🌶️)&lt;/p&gt;

&lt;h2 id=&quot;netlify-isnt-to-blameexcept-netlify-is-to-blame-&quot;&gt;Netlify isn’t to blame…except Netlify is to blame 😬&lt;/h2&gt;

&lt;p&gt;We can’t put &lt;em&gt;all&lt;/em&gt; the fault squarely on Netlify, in the sense that people mistakenly thought they needed to build their blogs with Gatsby and their business dashboards with Next.js because that’s what all the “techfluencers” and VC-backed tool vendors told them.&lt;/p&gt;

&lt;p&gt;Yet I &lt;strong&gt;do&lt;/strong&gt; blame Netlify, because &lt;em&gt;they’re the people who invented the term JAMstack!&lt;/em&gt; Netlify proved more than happy to come along for the ride and oblige as one of the top hosting platforms of choice for this new ecosystem. They &lt;em&gt;could&lt;/em&gt; have come out in favor of saner architectures and better support for languages other than JavaScript (believe me, I went around and around with them about their lack of interest in supporting Ruby-based server applications &lt;strong&gt;even as their own platform used Ruby under the hood!&lt;/strong&gt;). They &lt;em&gt;could&lt;/em&gt; have warned us of the dangers of complicated API spaghetti code and microservices. After all, why should Netlify care if your static site calls out to 20 different APIs or your own monolithic API you wrote in a battle-hardened, “boring” server framework? This bizarro-world focus on “serverless functions” and later “edge functions” never made a lick of sense to me (unless Netlify really thought they could somehow significantly profit off of function usage…again, a prospect which makes little sense to me).&lt;/p&gt;

&lt;p&gt;Ultimately the failure of Jamstack to live up to the promise of its original JAMstack incarnation, and the industry’s manic pendulum swing into unmaintainable architectures and vendor lock-in, led me to abandon Netlify as a hosting platform of choice and look instead to more reasonable options. At this moment in time, that choice for me is &lt;a href=&quot;https://www.render.com&quot;&gt;Render&lt;/a&gt;. Render gives you the best of all worlds: deploy a static site to a CDN, deploy a server API written in any framework you want—even Rails!—deploy a Docker container…anything you need, &lt;strong&gt;BOOM&lt;/strong&gt;, done. Want to use PostgreSQL? &lt;em&gt;Check.&lt;/em&gt; Need Redis? &lt;em&gt;Check.&lt;/em&gt; Would you like simultaneous deploys of all these services at once? &lt;em&gt;Check.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have no business arrangement with Render, and I assure you this isn’t a sponsored post. I just really like their service. And if Render does become &lt;em&gt;enshittified&lt;/em&gt; down the road, I’ll be super bummed and look for yet another alternative. (I sure hope that won’t become necessary!)&lt;/p&gt;

&lt;p&gt;What saddens me is Netlify &lt;em&gt;could&lt;/em&gt; have grown into a Render and meaningfully competed with Heroku—except they didn’t. They took a different road, and I would argue &lt;strong&gt;they failed&lt;/strong&gt;. Hey, hindsight is 20/20…but honestly this was &lt;em&gt;so&lt;/em&gt; easy to predict. 🤷🏻‍♂️&lt;/p&gt;

&lt;p&gt;I’m sad to see Jamstack die, but the thing is: &lt;em&gt;most&lt;/em&gt; web applications deployed by most individuals and small teams only need modest server offerings and maybe a static site. Again, it’s not rocket science. Most projects never need to become “web scale” and most web architectural complexity is completely unnecessary. You can spin up a Node.js API with Fastify, or a Ruby Roda API, or, heck, some PHP, stick it on a decent cloud server somewhere, and &lt;em&gt;that’s totally fine&lt;/em&gt;. Boring technology is &lt;strong&gt;great&lt;/strong&gt;. And servers are in fact totally awesome, as more and more developers are thankfully coming to discover (again).&lt;/p&gt;

&lt;h2 id=&quot;the-legacy-of-netlify&quot;&gt;The Legacy of Netlify&lt;/h2&gt;

&lt;p&gt;What Netlify gave us originally was a vision of how to deploy HTML-first websites easily via git commits and pushes, just like Heroku had done for dynamic applications. All we need now is a modern Netlify/Heroku mashup that’s cheap, stable, and doesn’t need to reinvent the damn wheel every year.&lt;/p&gt;

&lt;p&gt;What do we call this, now that Jamstack is dead? I don’t know.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I vote for KISSstack. (Keep It Simple, Silly.)&lt;/strong&gt; 😋&lt;/p&gt;

&lt;p&gt;But seriously, I think it’s vitally important to remember that simple websites and more complex web applications all sit on a spectrum, and a good web host will be able to identify your individual needs and provision builds and runtimes accordingly—no matter what the particular service offerings might be. (&lt;a href=&quot;https://www.spicyweb.dev/web-app-and-web-site-should-be-friends/&quot;&gt;I wrote about this too.&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;From my vantage point, the &lt;em&gt;only&lt;/em&gt; goal I care about is to make building &amp;amp; deploying sites dramatically easier for individuals and small teams. (&lt;a href=&quot;https://imgflip.com/memetemplate/117794993/I-dont-think-about-you-at-all-Mad-Men&quot;&gt;Sorry Big Co. Enterprises, I don’t think about you at all.&lt;/a&gt;) And while it’s a real shame that Netlify is no longer in a position to usher in this future for us, I’m optimistic we’ll see &lt;a href=&quot;https://www.render.com&quot;&gt;Render&lt;/a&gt;, &lt;a href=&quot;https://fly.io&quot;&gt;Fly.io&lt;/a&gt;, and other companies down the road pick up the slack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Somebody&lt;/strong&gt; has to.￼&lt;/p&gt;</content><author><name></name></author><category term="architecture" /><category term="bestpractices" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" /><media:content medium="image" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">New Projects Galore! A Blog and a Podcast</title><link href="https://www.spicyweb.dev/new-projects-galore/" rel="alternate" type="text/html" title="New Projects Galore! A Blog and a Podcast" /><published>2023-07-18T00:00:00-07:00</published><updated>2023-07-18T00:00:00-07:00</updated><id>repo://posts.collection/_posts/2023-07-18-new-projects-galore.md</id><content type="html" xml:base="https://www.spicyweb.dev/new-projects-galore/">&lt;p&gt;Hello everybody! It’s been a minute since posting here, so I wanted to get you all caught up on the thrilling projects which I’ve recently launched alongside &lt;strong&gt;The Spicy Web&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First, I’ve started a new aggregator-style link blog called &lt;a href=&quot;https://thathtml.blog&quot;&gt;That HTML Blog&lt;/a&gt; where I’ll be posting daily to share links to wonderful resources around the web about the world of standards-adjacent web development. Please be sure to sign up for the email digest and add the newsfeed to your RSS reader of choice!&lt;/p&gt;

&lt;p&gt;Secondly, my good friend Ayush and I are now publishing bi-weekly episodes of the &lt;a href=&quot;https://justaspec.show&quot;&gt;Just a Spec&lt;/a&gt; Podcast, each one focused on a particular protocol or specification that makes the internet tick (both the web and even beyond). I hope you will subscribe in your podcast player of choice, as well as follow &lt;a href=&quot;https://intuitivefuture.com/@justaspec&quot;&gt;@justaspec@intuitivefuture.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What about this site though? I’m in the process of redesigning it a bit to focus more on original video and course-style content, and in fact with the launch of &lt;a href=&quot;https://thathtml.blog&quot;&gt;That HTML Blog&lt;/a&gt;, it makes that process even more clear. I hope to have more news about the release date of my “new CSS” course next month.&lt;/p&gt;

&lt;p&gt;But in the meantime, enjoy the links above, and of course feel free to hop in the Discord and let me know what you think! ✌️&lt;/p&gt;</content><author><name></name></author><category term="html" /><category term="css" /><category term="javascript" /><category term="architecture" /><category term="bestpractices" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" /><media:content medium="image" url="https://www.spicyweb.dev/images/spicy-web-avatar-light.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>