<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex</title>
    <description>The latest articles on DEV Community by Alex (@derstruct).</description>
    <link>https://dev.to/derstruct</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3323219%2Ffb853a63-ac17-44bd-846b-b787e9011188.png</url>
      <title>DEV Community: Alex</title>
      <link>https://dev.to/derstruct</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/derstruct"/>
    <language>en</language>
    <item>
      <title>Go devs just got Superpowers</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Mon, 29 Sep 2025 20:36:15 +0000</pubDate>
      <link>https://dev.to/derstruct/go-devs-just-got-superpowers-2lb3</link>
      <guid>https://dev.to/derstruct/go-devs-just-got-superpowers-2lb3</guid>
      <description>&lt;p&gt;HTMX proved there’s demand for different approaches. It’s simple and clever, but limited. Control logic ends up distributed between HTML attributes and back-end handlers. There’s no reactive state to support complex UI flows, and your application isn’t truly “live” - it still needs client-side code for updates without user interaction.&lt;/p&gt;

&lt;p&gt;I took it to another dimension: centralized control in Go, flexible routing system, reactivity, and coordinated updates across the UI. The power of React, but without its confusion, npm baggage, and with some unique features.&lt;/p&gt;

&lt;p&gt;To show what this looks like in practice, let’s build a dynamic, reactive dashboard app — written entirely in Go. Along the way, you’ll see how each core piece of the framework fits together: from live HTML updates and event handling to state management, concurrency control, and navigation. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7l7jc73xeqzfyvcwsn54.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7l7jc73xeqzfyvcwsn54.gif" alt=" " width="760" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some code is omitted, full version is available &lt;a href="//doors.dev/tutorial/"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;dev.to does not support &lt;code&gt;templ&lt;/code&gt; highlighting, so I'll paste screenshots from vim (by the way).&lt;/li&gt;
&lt;li&gt;For demo purposes, I deliberately omitted error handling to reduce LOC; that's not how it should be done!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a deep dive that supplies you with enough knowledge to begin building yourself. If you just want the big picture, scroll through, check out the GIFs, and read the sections above them if something looks interesting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. The First Page
&lt;/h2&gt;

&lt;h3&gt;
  
  
  General Page Template
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;./page_template.templ&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This app has one page with multiple path variants, so a separate template isn’t needed.&lt;/p&gt;

&lt;p&gt;Still, it's nice to have concerns separated.&lt;/p&gt;

&lt;h4&gt;
  
  
  Page Interface
&lt;/h4&gt;

&lt;p&gt;The page must provide &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt; content to the template:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70nqs0dgpwp47j7amrx6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70nqs0dgpwp47j7amrx6.png" alt=" " width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Template
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgsptoar7lb4om9rqo8k7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgsptoar7lb4om9rqo8k7.png" alt=" " width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two notes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We include the framework’s assets; that’s crucial.&lt;/li&gt;
&lt;li&gt;Instead of just &lt;code&gt;&amp;lt;link rel="stylesheet" href="..."&amp;gt;&lt;/code&gt;, we used &lt;code&gt;doors.ImportExternalStyle&lt;/code&gt;, which also collects information for. &lt;a href="https://content-security-policy.com/" rel="noopener noreferrer"&gt;CSP&lt;/a&gt; header generation. CSP is disabled by default, but this prepares us for it.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;doors.Import...&lt;/code&gt; handles local, embedded, and external CSS and JS assets. For JavaScript/TypeScript modules, it eenables build/bundle steps and generates an import map&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Page
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;./page.templ&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Page Path
&lt;/h4&gt;

&lt;p&gt;In &lt;em&gt;doors&lt;/em&gt;, the URI is decoded into a &lt;strong&gt;Path Model&lt;/strong&gt;. It supports path variants, parameters, and query values. &lt;/p&gt;

&lt;p&gt;Our path will have two variants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/&lt;/code&gt; location selector&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/:Id&lt;/code&gt; dashboard for selected location&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One parameter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Id of the city
And two query values: &lt;/li&gt;
&lt;li&gt;forecast days &lt;/li&gt;
&lt;li&gt;units (metric/imperial)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll omit query values for now and add them later.&lt;/p&gt;

&lt;p&gt;Our path model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4w9kf2hys4ak1bfec267.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4w9kf2hys4ak1bfec267.png" alt=" " width="800" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The framework uses &lt;code&gt;path&lt;/code&gt; tags to match the request path against the provided pattern. &lt;/li&gt;
&lt;li&gt;The matched variant’s field is set to true.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Page
&lt;/h4&gt;

&lt;p&gt;The path structure is wrapped in the state primitive (&lt;strong&gt;Beam&lt;/strong&gt;) and passed to the page render function::&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5mbur87zsdnzdikhgse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5mbur87zsdnzdikhgse.png" alt=" " width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To be compatible with the framework, the page type must implement Render(), return a component, and accept a &lt;strong&gt;Beam&lt;/strong&gt; with the path model.&lt;/p&gt;

&lt;h4&gt;
  
  
  Page Handler
&lt;/h4&gt;

&lt;p&gt;A function that runs when the path matches. It reads request data (doors.RPage) and chooses the response (doors.PageRouter).&lt;/p&gt;

&lt;p&gt;In our case, it's straightforward:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhp2omealv6nz6m3zm6pc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhp2omealv6nz6m3zm6pc.png" alt=" " width="800" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;doors.PageRouter&lt;/code&gt; also supports soft (internal) and hard (HTTP) redirects and serving static pages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Router
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;./main.go&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create a doors router, provide a page handler, and launch the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgc9ad96ipjr8u8xuvotd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgc9ad96ipjr8u8xuvotd.png" alt=" " width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how &lt;code&gt;doors.Router&lt;/code&gt; just plugs into the Go standard server! Go is awesome.&lt;/p&gt;

&lt;p&gt;We are live!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4u2ulst63r75togk3dfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4u2ulst63r75togk3dfg.png" alt=" " width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Location Selector
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;./location_selector.templ&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Search Input
&lt;/h3&gt;

&lt;p&gt;Write a search fragment and render it on the page.&lt;/p&gt;

&lt;h4&gt;
  
  
  Fragment
&lt;/h4&gt;

&lt;p&gt;A fragment is a struct with a Render() templ.Component method&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71o8wuyrlnj44ox31lbt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71o8wuyrlnj44ox31lbt.png" alt=" " width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Input Component
&lt;/h4&gt;

&lt;p&gt;Attach an event hook to the input field:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe561olmr5f38ou79hm2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe561olmr5f38ou79hm2c.png" alt=" " width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;doors.AInput&lt;/code&gt; creates a temporary, private endpoint for this element and event.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Options Component
&lt;/h4&gt;

&lt;p&gt;Queries and renders search results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1ge1q49qsd29ktalzs7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1ge1q49qsd29ktalzs7.png" alt=" " width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Each &lt;code&gt;doors.Door&lt;/code&gt; renders in parallel on a goroutine pool, so data queries during render generally don’t slow page rendering.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Put it on the page
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;./page.templ&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmwd48rx3r970fjfgnj8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmwd48rx3r970fjfgnj8.png" alt=" " width="800" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  ...and enjoy!
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwhpz21403dtumjqau0bt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwhpz21403dtumjqau0bt.gif" alt=" " width="600" height="291"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Take a second to realize. We just implemented dynamic search without custom JS or API and within a single control flow.&lt;/p&gt;




&lt;h3&gt;
  
  
  Debounce and Loader
&lt;/h3&gt;

&lt;p&gt;You don't want to stream every keystroke to the server. &lt;/p&gt;

&lt;p&gt;Add one line to the input configuration to enable debounce filtering with the &lt;strong&gt;Scopes API&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyojvw1eycnt26xxuukn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyojvw1eycnt26xxuukn.png" alt=" " width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With debounce, repeated values are more likely. Updating search results with the same data in this rare scenario makes me unhappy. Add a simple check to prevent it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm12wpf59mpdwa9dfkmn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm12wpf59mpdwa9dfkmn9.png" alt=" " width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;doors&lt;/em&gt; guarantees that the same hook’s invocations run in series, so &lt;code&gt;prevValue&lt;/code&gt; has no concurrent access issues.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In practice, responses aren’t instant, so indicate progress to the user.&lt;/p&gt;

&lt;p&gt;PicoCSS provides an attribute for this. Use the &lt;strong&gt;Indication API&lt;/strong&gt; to toggle it during pending operations.&lt;/p&gt;

&lt;p&gt;Final code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fde5fmonyon700w7qp79o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fde5fmonyon700w7qp79o.png" alt=" " width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debounce and indication together (simulated latency):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcnbzt3g66p6v531x1zr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcnbzt3g66p6v531x1zr.gif" alt=" " width="600" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The indication clears after all hook-triggered changes apply on the client.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Reactive State
&lt;/h3&gt;

&lt;p&gt;Country and city selection can use &lt;strong&gt;Door&lt;/strong&gt; only, without any reactive state.&lt;/p&gt;

&lt;p&gt;However, in multi-step forms and complex UIs, this "low-level" approach spreads logic across handlers and hurts readability and debuggability. A single source of truth in that case significantly reduces mental overhead.&lt;/p&gt;

&lt;h4&gt;
  
  
  Country Selection
&lt;/h4&gt;

&lt;p&gt;Add a Source Beam and subscribe to it in the render function:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79hsmb81q9hk6n2azoev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79hsmb81q9hk6n2azoev.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Country selector component (previously inside the main render function):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8mrrllxnnhtz5mr7lm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8mrrllxnnhtz5mr7lm7.png" alt=" " width="800" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Show the selected country and a reset button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxutapd5v5t7qmeyn18a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxutapd5v5t7qmeyn18a.png" alt=" " width="800" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Update the beam on option click:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcknkaejakbrvfz0y8dtq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcknkaejakbrvfz0y8dtq.png" alt=" " width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;BlockingScope&lt;/code&gt; cancels all new events while the previous one is being processed. It reduces unnecessary requests and clarifies intent.&lt;br&gt;
Also, note that we used the same scope set for all search options, which effectively means that events from all handlers pass through a single pipeline, allowing only one active handler.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Let's see how selection works with reactive state:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fas6mf0rqx0fuy54hvr9c.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fas6mf0rqx0fuy54hvr9c.gif" alt=" " width="600" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oops. Search results weren’t cleared. That makes sense; we didn't clear them.&lt;/p&gt;

&lt;p&gt;Fix:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6w8s1vspbth2z0fs0jbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6w8s1vspbth2z0fs0jbz.png" alt=" " width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb4n006s7p6br49p6gu0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb4n006s7p6br49p6gu0.gif" alt=" " width="600" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Location Selector
&lt;/h3&gt;

&lt;p&gt;The country selector is a prototype for an abstract place selector. Comment it out for now.&lt;/p&gt;

&lt;p&gt;Plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;strong&gt;Source Beam&lt;/strong&gt; to the location selector that holds combined country and city data. &lt;/li&gt;
&lt;li&gt;Derive separate &lt;strong&gt;Beams&lt;/strong&gt; for country and city values.&lt;/li&gt;
&lt;li&gt;Transform our previous country selector into an abstract "place" selector.&lt;/li&gt;
&lt;li&gt;Write the location selector render function with the place selectors.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's &lt;strong&gt;Go&lt;/strong&gt;!&lt;/p&gt;

&lt;h4&gt;
  
  
  Create a &lt;strong&gt;Source Beam&lt;/strong&gt; that holds country and city data
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wkppy22ct3auvehd3um.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wkppy22ct3auvehd3um.png" alt=" " width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Derive the country and city &lt;strong&gt;Beams&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36gkuxvi47jyy5tavatj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36gkuxvi47jyy5tavatj.png" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Source Beam&lt;/strong&gt; is the original value, which can be updated, mutated, and observed. &lt;strong&gt;Beam&lt;/strong&gt; can only be observed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Abstract the place selector
&lt;/h4&gt;

&lt;p&gt;Structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvbz29sf07rwg9vbcnxx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvbz29sf07rwg9vbcnxx.png" alt=" " width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Methods from our previous country selector with minimal changes (see comments):&lt;/p&gt;

&lt;p&gt;Main render:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxyubru7fbfniorrrwfe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxyubru7fbfniorrrwfe.png" alt=" " width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Show selected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4brihqah60mgbhbr9xl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4brihqah60mgbhbr9xl.png" alt=" " width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select place:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhdzygclu2i80wzeu0fu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhdzygclu2i80wzeu0fu.png" alt=" " width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Input:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvft6ir1xl4u27efgwi1t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvft6ir1xl4u27efgwi1t.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And options:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpgueuq99d0nky9tpt4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpgueuq99d0nky9tpt4d.png" alt=" " width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Use our place selectors in the location selector render
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpf9zf9r1ratl05g7ok6x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpf9zf9r1ratl05g7ok6x.png" alt=" " width="800" height="746"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic form with reactive state:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdiztje3l10ly4rutbyw2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdiztje3l10ly4rutbyw2.gif" alt=" " width="600" height="339"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Beam&lt;/strong&gt; is a communication primitive with a value stream. You can watch it directly or use &lt;code&gt;doors.Sub&lt;/code&gt;/&lt;code&gt;doors.Inject&lt;/code&gt; to render a Door that updates automatically on change.&lt;/p&gt;

&lt;p&gt;It has a few important properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Triggers subscribers only on value change.&lt;/strong&gt; By default it uses &lt;code&gt;==&lt;/code&gt;to decide if an update is needed; you can supply a custom equality function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronized with rendering.&lt;/strong&gt; During a render pass, all participating nodes observe the same value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagates changes top-to-bottom.&lt;/strong&gt; In other words, subscribers who are responsible for more significant parts of the DOM will be triggered first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale propagation is canceled&lt;/strong&gt;. Cancels stale propagation if the value changes mid-propagation (override with &lt;code&gt;NoSkip&lt;/code&gt; on &lt;strong&gt;Source Beam&lt;/strong&gt; if needed).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Derived beams update as a group.&lt;/strong&gt; Subscription handlers run in parallel on a goroutine pool.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;All these properties together just make it work as expected - you rarely need to think about it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Bonus: Improve the UX (with JavaScript)
&lt;/h3&gt;

&lt;p&gt;Missing keyboard support in a form is annoying. &lt;/p&gt;

&lt;p&gt;Add keyboard support:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Autofocus on the input.&lt;/li&gt;
&lt;li&gt;Tab and enter support on options.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Wiring up some JS
&lt;/h4&gt;

&lt;p&gt;Focus by Id via JS:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60b8kdxuertffjhbpt2l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60b8kdxuertffjhbpt2l.png" alt=" " width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Better: make it a reusable component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fneif0xhvbhv5zdvs5trh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fneif0xhvbhv5zdvs5trh.png" alt=" " width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sleek.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And render after the input:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fep05a207fbk7d8y2dqzc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fep05a207fbk7d8y2dqzc.png" alt=" " width="800" height="48"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;doors.Script&lt;/code&gt; is awesome. It converts inline script into a minified (if not configured otherwise), cacheable script with src, protects global scope, and enables &lt;code&gt;await&lt;/code&gt;. Additionally, it compiles TypeScript if you provide &lt;code&gt;type="application/typescript"&lt;/code&gt; attribute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Enabling Tab + Enter
&lt;/h4&gt;

&lt;p&gt;Attach a key event hook and add &lt;code&gt;tabindex&lt;/code&gt; to options:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xprnxyurs9bqaa6qe4q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xprnxyurs9bqaa6qe4q.png" alt=" " width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyboard control enabled:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd8ivowweqpjb0ibptpw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd8ivowweqpjb0ibptpw.gif" alt=" " width="600" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Path and Title
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Apply Selected Location
&lt;/h3&gt;

&lt;p&gt;Here’s the page code again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbi8wyes2ed30b4t51x6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbi8wyes2ed30b4t51x6.png" alt=" " width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It always displays the location selector. Remember, we have two path variants: &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;/:Id&lt;/code&gt;. The second is used when a location is selected.&lt;/p&gt;

&lt;p&gt;And now take a look at this fella:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmil0rh9da8besuv4hmhd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmil0rh9da8besuv4hmhd.png" alt=" " width="800" height="72"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can probably guess where this goes (path subscription). We’ll also have query parameters, but we don’t want the whole page to rerender on each query change, so &lt;em&gt;derive&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fni2ghh8g486jxm558k5f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fni2ghh8g486jxm558k5f.png" alt=" " width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then subscribe:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4cxk9v5jtzfzsqut8gb5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4cxk9v5jtzfzsqut8gb5.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Display the selected city or 404:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqpbkk11fk662sm94mu8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqpbkk11fk662sm94mu8.gif" alt=" " width="600" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the location selector must change the path dynamically.&lt;/p&gt;

&lt;p&gt;Add an apply dependency to the location selector:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzp4pd67bnj5a4cj6dicx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzp4pd67bnj5a4cj6dicx.png" alt=" " width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Implement submit functionality:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pczqgkjrl9w78mhhen9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pczqgkjrl9w78mhhen9.png" alt=" " width="800" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Provide the apply function to the selector:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jroljhcn6zkutq78y1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jroljhcn6zkutq78y1o.png" alt=" " width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Instead of passing a path mutation function, we could just render a link. This example shows how to navigate programmatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Location selected via path mutation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fijcty8ucbq39p0b00mgg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fijcty8ucbq39p0b00mgg.gif" alt=" " width="600" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Title
&lt;/h3&gt;

&lt;p&gt;In doors you can register a JS handler on the front end with &lt;code&gt;$d.on(...)&lt;/code&gt; and invoke it from Go with &lt;code&gt;doors.Call(...)&lt;/code&gt;. Implementing a dynamic title is straightforward. &lt;/p&gt;

&lt;p&gt;However, you can just use premade &lt;code&gt;doors.Head&lt;/code&gt; component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmqxlps7ai914llq7lpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmqxlps7ai914llq7lpf.png" alt=" " width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Besides &lt;code&gt;title&lt;/code&gt;, it also supports &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. Advanced Scope Usage
&lt;/h2&gt;

&lt;p&gt;In practice, the submit handler won’t respond instantly.  What if we interact with the UI during processing?&lt;/p&gt;

&lt;p&gt;Let's simulate conflicting actions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fehntbc7v1q1f7qr5mf9f.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fehntbc7v1q1f7qr5mf9f.gif" alt=" " width="600" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It didn't change the outcome, but caused weird UI behavior.&lt;/p&gt;

&lt;p&gt;This issue can be easily mitigated with &lt;strong&gt;Concurrent Scope&lt;/strong&gt; from the &lt;strong&gt;Scopes API&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Concurrent Scope can be “occupied” only by events with the same group id.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Add a concurrent scope to the location selector:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnn6l83zjev1ucjhikl4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnn6l83zjev1ucjhikl4m.png" alt=" " width="800" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a parent scope property to the place selector:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fko0xd29xnqc953kl34g6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fko0xd29xnqc953kl34g6.png" alt=" " width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Assign group Id 1 to both place selectors:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiupgdp4jeh1wnw6on873.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiupgdp4jeh1wnw6on873.png" alt=" " width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use it on the ‘change place’ button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vr9ef8c4kcwhugks0qx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vr9ef8c4kcwhugks0qx.png" alt=" " width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, apply it to the submit button with a different group Id:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F444jtxehp9924iv3paky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F444jtxehp9924iv3paky.png" alt=" " width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setup ensures that either the submit or the change-place event can run, not both at the same time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesr06wgviglqn6xffom0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesr06wgviglqn6xffom0.gif" alt=" " width="600" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While changes to city and country won't affect each other, since they share the same group:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vzen8wrcxgfrho0rm9t.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vzen8wrcxgfrho0rm9t.gif" alt=" " width="600" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Concurrency control is needed because of the framework’s non-blocking event model. This is a major advantage of &lt;em&gt;doors&lt;/em&gt; over Phoenix LiveView or Blazor Server, enabling highly interactive UIs without UX compromises.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  5. Menu
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Query Params
&lt;/h3&gt;

&lt;p&gt;In the weather API, besides the city, we also have two variables: units (metric/imperial) and forecast days.&lt;/p&gt;

&lt;p&gt;Add it to our path model:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./page.templ&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ph2pygzge91szzk8xet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ph2pygzge91szzk8xet.png" alt=" " width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice I used reference types. Otherwise, query parameters get a zero value and always appear in the URI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Dashboard Fragment
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;./dashboard.templ&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Separate the dashboard fragment
&lt;/h4&gt;

&lt;p&gt;To keep the page simple, let's move the dashboard to a separate fragment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxed017ua8si57uulusvh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxed017ua8si57uulusvh.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dashboard depends on the location ID (provided by the page already) and the days and units query parameters:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp1kije7nuf2oqp3lh7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvp1kije7nuf2oqp3lh7p.png" alt=" " width="800" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We derive those from the path:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtx8lnj8qpk79etlcx8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtx8lnj8qpk79etlcx8q.png" alt=" " width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Render the dashboard on the page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjj227c010nrvkuycw06p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjj227c010nrvkuycw06p.png" alt=" " width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic links
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Change City
&lt;/h4&gt;

&lt;p&gt;For the location selector to appear, we need to render a link to &lt;code&gt;/&lt;/code&gt;. It would also be nice if query parameters persisted, so we generate the link based on the settings beam:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2v9iydybz1rbvx9bkkz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2v9iydybz1rbvx9bkkz7.png" alt=" " width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;AHref&lt;/code&gt; also supports the &lt;strong&gt;Scopes&lt;/strong&gt; and &lt;strong&gt;Indication&lt;/strong&gt; APIs&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Switch to the location selector via dynamic link:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3lk0hxej1q53ans2h2qg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3lk0hxej1q53ans2h2qg.gif" alt=" " width="600" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After clicking, the query parameters appear with default values. It’s okay, but not ideal.&lt;/p&gt;

&lt;p&gt;Provide nil for the defaults so the behavior is consistent:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcntrfcfgl29o16fenie4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcntrfcfgl29o16fenie4.png" alt=" " width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I’m happy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkg15lmf5psmrwsjvqo1i.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkg15lmf5psmrwsjvqo1i.gif" alt=" " width="600" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Units
&lt;/h4&gt;

&lt;p&gt;Render a link to switch units back and forth:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2zyov4qzov6cscpvy9v8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2zyov4qzov6cscpvy9v8.png" alt=" " width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add some styles and render the units switcher:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn7yc1n6g93dkh34d39d0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn7yc1n6g93dkh34d39d0.png" alt=" " width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query param switching:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7nds3ntgtd5j3b4yiq8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7nds3ntgtd5j3b4yiq8.gif" alt=" " width="600" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Forecast Days
&lt;/h4&gt;

&lt;p&gt;The forecast-days links must preserve the units query value. To avoid unnecessary updates, derive a beam for the units:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbr594qwewiczyncbqae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbr594qwewiczyncbqae.png" alt=" " width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Subscribe the menu to it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fen3u5litvofc90dg5t1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fen3u5litvofc90dg5t1n.png" alt=" " width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And maintain the units query value:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6u86qh4e6letd5wg10ep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6u86qh4e6letd5wg10ep.png" alt=" " width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reactive menu:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi24pt88zau1h3mjvllgq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi24pt88zau1h3mjvllgq.gif" alt=" " width="600" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;doors&lt;/em&gt; doesn’t keep the whole DOM in memory. With beam derivation, you explicitly tie a specific HTML section to a specific piece of data. &lt;strong&gt;Diff data, not DOM.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Bonus: Active link highlighting
&lt;/h3&gt;

&lt;p&gt;The client can automatically apply active-link highlighting if you configure it in &lt;code&gt;doors.AHref&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9o4lyxioes1u2wcabqw3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9o4lyxioes1u2wcabqw3.png" alt=" " width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By default, it checks the whole path and all query values to apply the indication, but you can configure a narrower matching strategy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Active link is highlighted:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fommxxt8egdwxbcnrwc8a.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fommxxt8egdwxbcnrwc8a.gif" alt=" " width="600" height="145"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Path as state is powerful. It’s declarative, type-safe, and doesn’t constrain how you map paths to HTML.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Charts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Temperature
&lt;/h3&gt;

&lt;p&gt;Let’s prepare a temperature chart and see how it goes.&lt;/p&gt;

&lt;p&gt;Instead of &lt;code&gt;doors.Sub&lt;/code&gt;, I’ll use the &lt;code&gt;doors.Inject&lt;/code&gt; helper. It essentially does the same, but instead of evaluating a function, it renders children with context that contains the beam value.&lt;/p&gt;

&lt;p&gt;To serve the generated SVG, I’ll use &lt;code&gt;doors.ARawSrc&lt;/code&gt;, which creates a &lt;code&gt;src&lt;/code&gt; attribute with a custom request handler:”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tgf28qiw5ppd1ezhkyf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tgf28qiw5ppd1ezhkyf.png" alt=" " width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;doors.ARawSrc&lt;/code&gt; (and &lt;code&gt;doors.ASrc&lt;/code&gt;, &lt;code&gt;doors.AFileHref&lt;/code&gt;, &lt;code&gt;doors.ARawFileHref&lt;/code&gt;) use hook mechanics and privately serve resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Temperature line chart with dynamic SVG:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuzeo1g28297ygalr24j.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuzeo1g28297ygalr24j.gif" alt=" " width="600" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything
&lt;/h3&gt;

&lt;p&gt;Abstract the chart component so it can be reused for all charts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb592jm65rtundwg31gwk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb592jm65rtundwg31gwk.png" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All charts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvb37zeppc2alkpu3u6s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvb37zeppc2alkpu3u6s.png" alt=" " width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  UX improvements
&lt;/h3&gt;

&lt;p&gt;Image preloader + parameter-switch indication:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvfhmze3p3vx0ieqh680.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvfhmze3p3vx0ieqh680.png" alt=" " width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Include this indication on all menu links:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwo6cbfydl7aytzrzig6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwo6cbfydl7aytzrzig6.png" alt=" " width="800" height="63"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Charts with preloaders:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczbrrzagzqst4ntm6nu6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczbrrzagzqst4ntm6nu6.gif" alt=" " width="600" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Optimization
&lt;/h3&gt;

&lt;p&gt;You may have noticed that weather and humidity don’t depend on the &lt;code&gt;units&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;Apprach as always - derive the beam that does not depend on units:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3yd1bqn17sjie4mwb8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3yd1bqn17sjie4mwb8m.png" alt=" " width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, we don’t need that indication triggered, so make it more specific:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzhvbxmla6c6nw8q0qeu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzhvbxmla6c6nw8q0qeu.png" alt=" " width="800" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chart component with a &lt;code&gt;days&lt;/code&gt; variation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjaqcd2swt3fcmug3fm2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjaqcd2swt3fcmug3fm2.png" alt=" " width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final result (slow internet simulation):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8crw99mwdm245wp3z98.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8crw99mwdm245wp3z98.gif" alt=" " width="600" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Page size:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa863wm92vgjeozc7vugw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa863wm92vgjeozc7vugw.png" alt=" " width="428" height="44"&gt;&lt;/a&gt;&lt;br&gt;
where ~13 KB is PicoCSS and ~10 KB is the doors client.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Confession. I hate writing UIs, it always gives me the impression I’m doing something wrong. It's like having 10 different tools, none of which are designed for the job, so you have to combine them in an awkward way to get something done. It doesn’t feel like programming anymore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With &lt;em&gt;doors&lt;/em&gt; it feels like programming.&lt;/strong&gt; Predictable solution path, known outcome, and freedom. &lt;/p&gt;

&lt;p&gt;I enjoyed every minute of writing this small app, and I hope you will find time to experience it yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/doors-dev/doors/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;
&lt;a href="https://doors.dev" rel="noopener noreferrer"&gt;Official Website&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Special thanks to Adrian Hesketh for his brilliant work on &lt;a href="https://github.com/a-h/templ" rel="noopener noreferrer"&gt;templ&lt;/a&gt;, which made this project possible.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>go</category>
      <category>webdev</category>
      <category>backend</category>
      <category>frontend</category>
    </item>
    <item>
      <title>You don’t need NPM to ship fully-featured apps.</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Sun, 21 Sep 2025 18:52:16 +0000</pubDate>
      <link>https://dev.to/derstruct/you-dont-need-npm-to-ship-fully-featured-apps-17ak</link>
      <guid>https://dev.to/derstruct/you-dont-need-npm-to-ship-fully-featured-apps-17ak</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;In programming, a bad decision is like a node_modules directory — it never stops growing.&lt;br&gt;
Sometimes you have to step back and realize you’ve been solving problems that never needed to exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Have you ever doubted choosing React, Next.js, or another “safe” stack for your next project? Most of us have — complexity keeps growing, the developer experience keeps getting worse, but the momentum makes it hard to walk away. And then came the recent &lt;a href="https://www.trendmicro.com/en_us/research/25/i/npm-supply-chain-attack.html" rel="noopener noreferrer"&gt;NPM fiasco&lt;/a&gt;, which showed the “safe” choice might not be literally safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can probably sense that the industry is in need of a next generation of tools, just as React brought us one in the past.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hybrid approaches (HTMX-like) have gained some traction, but they can’t offer even half of what React enables. Server-driven frameworks are becoming more common, but none have pushed it far enough to overcome the compromises. &lt;/p&gt;

&lt;p&gt;So, I built a full-stack framework for dynamic web applications in the Go ecosystem. It has reactive state, components, lifecycle control, SSR by default, and an HTTP API-free architecture — while fixing the flaws that held back existing server-driven UIs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find more details about the architecture and competitors analysis in my previous articles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article provides a general overview of the framework's API and some thoughts behind its design, and at the end, I will share a link to the tutorial and GitHub repo.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fix The Web Dev: Part 4, The Framework.
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Fundamentals
&lt;/h2&gt;

&lt;p&gt;Philosophy: Deconstruct the modern front-end framework concept into orthogonal, composable primitives that developers can use independently or combine into purpose-built abstractions.&lt;/p&gt;

&lt;h3&gt;
  
  
  DOM updates: control &amp;gt; abstraction.
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Modern web UI, at its core, is dynamic HTML. How to approach it — first decision I needed to make.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reactive frameworks abstract DOM manipulation: the component's render function reruns "virtually" when the state changes, allowing the framework to calculate the difference with the previous output and apply it. &lt;/p&gt;

&lt;p&gt;Vanilla JS, jQuery, and HTMX-like solutions, on the other hand, work directly: take the element &lt;del&gt;in a hard way&lt;/del&gt; by selector and insert new content.&lt;/p&gt;

&lt;p&gt;The first one goes against my philosophy (similar behavior should be possible, not enforced). The second one is essentially just string manipulation with no semantics.&lt;/p&gt;

&lt;h4&gt;
  
  
  Meet &lt;strong&gt;Door&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A dynamic container in the DOM tree that can be updated, replaced, or removed at runtime in a type-safe way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsytxfjxswygwebcvxz9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsytxfjxswygwebcvxz9k.png" alt=" " width="800" height="690"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Door's methods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Update&lt;/code&gt; - changes its content (like innerHTML)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Replace&lt;/code&gt; - replace entirely (like outerHTML)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Clear&lt;/code&gt; - removes content&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Remove&lt;/code&gt; - removes entirely&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Reload&lt;/code&gt; - re-renders (useful if you query data during render and want new or modified data to appear)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Internally, Doors form a tree structure, where each branch has its own lifecycle. Also, Door provides local &lt;a href="https://pkg.go.dev/context" rel="noopener noreferrer"&gt;context&lt;/a&gt; that can be used as an unmount hook.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Door is a straightforward tool for DOM manipulation and a foundation for higher abstractions. However, many things can be done in a simple way just by using it directly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Reactive State: communication &amp;gt; side effects.
&lt;/h3&gt;

&lt;p&gt;Conceptually, state in modern UI is a combination of two roles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Argument to the render function, determining the resulting output.&lt;/li&gt;
&lt;li&gt;Trigger to initiate re-rendering.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But when you look at the code of a sophisticated React component, this concept breaks down. State is often used as a communication primitive or intermediary data, leading to a chain of &lt;code&gt;useEffect&lt;/code&gt; hooks that produce the actual render-driving state. &lt;/p&gt;

&lt;p&gt;I don’t think developers are following bad practices; the tool itself naturally pushes real-world code toward that abuse. &lt;strong&gt;That is the insight.&lt;/strong&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Meet &lt;strong&gt;Beam&lt;/strong&gt;
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Communication primitive first, render driver by choice.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A changing value stream that can be read, subscribed to, watched, or derived.&lt;/p&gt;

&lt;p&gt;Let's combine &lt;code&gt;Beam&lt;/code&gt; and &lt;code&gt;Door&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vpag750ehhbrmnwo2x7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vpag750ehhbrmnwo2x7.png" alt=" " width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or use the helper component (if you don't need precise control) to reduce boilerplate:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmu27slv2h5dp8g9upl62.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmu27slv2h5dp8g9upl62.png" alt=" " width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can hold any type of value&lt;/li&gt;
&lt;li&gt;Triggers subscribers upon value change (&lt;code&gt;==&lt;/code&gt; or custom distinction function)&lt;/li&gt;
&lt;li&gt;Origin (&lt;code&gt;SourceBeam&lt;/code&gt;) can be updated or mutated&lt;/li&gt;
&lt;li&gt;Not bound to a specific fragment or container&lt;/li&gt;
&lt;li&gt;Respects the dynamic container tree, guaranteeing render consistency, &lt;em&gt;beam shines through doors&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;In practice, you derive element-scoped state so each DOM node depends only on what it needs (&lt;em&gt;diff data, not DOM&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Routing: freedom &amp;gt; automation.
&lt;/h3&gt;

&lt;p&gt;In the PHP era, the path portion of a URI often was an actual filesystem path to a script, while the query parameters (the key-value map after ‘?’) functioned much like React component props. Item IDs, pagination, and filters - every page variation was expressed through query values.&lt;/p&gt;

&lt;p&gt;Today, the path itself has become an abstraction, and means whatever the developer wants… or does it? &lt;br&gt;
Front-end frameworks rely heavily on the path (less flexible than a key–value map) while enforcing some coupling between path structure and the component/file tree. &lt;strong&gt;It feels rudimentary and underpowered.&lt;/strong&gt; And it's a shame, because the ability to encode some state directly in the URI is incredibly powerful.&lt;/p&gt;

&lt;h4&gt;
  
  
  Meet &lt;strong&gt;Path Model&lt;/strong&gt;.
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Route is changing data, and changing data is state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each page route is an annotated struct used to match, decode, and encode the URI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfbr1yyy5ebkv46qg1jd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfbr1yyy5ebkv46qg1jd.png" alt=" " width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Declare multiple path variants (the matched field becomes true)&lt;/li&gt;
&lt;li&gt;Use type-safe parameter capturing &lt;/li&gt;
&lt;li&gt;Use splat parameter to capture the remaining path tail&lt;/li&gt;
&lt;li&gt;Use almost any types for query parameters (&lt;a href="https://github.com/go-playground/form" rel="noopener noreferrer"&gt;go-playground/form&lt;/a&gt; under the hood)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  How to work with it
&lt;/h4&gt;

&lt;p&gt;The path model comes through Beam into the page render function:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6c39eawe034gln4pkso.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6c39eawe034gln4pkso.png" alt=" " width="800" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Derive a specific piece from the path model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6n0n36lifyh1wv6w5qi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6n0n36lifyh1wv6w5qi.png" alt=" " width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Render dynamic content based on its value by subscribing to it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm7bwsh6kv0ayxixlvvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm7bwsh6kv0ayxixlvvg.png" alt=" " width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, you can generate links and navigate programmatically via &lt;code&gt;SourceBeam&lt;/code&gt; value mutation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This approach requires more code than usual. But its type-safe, deterministic, and declarative nature gives a solid, confident experience and unmatched freedom in route modeling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Toolkit
&lt;/h2&gt;

&lt;p&gt;Philosophy: It’s better to deal with a comprehensive system than with a set of shortcuts. Adding your own wrapper once is far easier than fighting missing features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Straightforward and type-safe event binding.
&lt;/h3&gt;

&lt;p&gt;To attach an event listener, render the "magic" attribute in front of the target element:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21k963e7i811s5mwwqzs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21k963e7i811s5mwwqzs.png" alt=" " width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This creates a protected HTTP endpoint and adds event-binding attributes to the element. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The attribute structure provides fields for event flow control, concurrency rules, pre-actions, error handling, and more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Flexible indication API.
&lt;/h3&gt;

&lt;p&gt;To communicate processing, use any combination of indicators and selectors with the &lt;strong&gt;Indication API&lt;/strong&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  Temporary indications
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Add class&lt;/li&gt;
&lt;li&gt;Remove class&lt;/li&gt;
&lt;li&gt;Set attribute&lt;/li&gt;
&lt;li&gt;Set content &lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Selectors:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Event target element &lt;/li&gt;
&lt;li&gt;Global document query selector&lt;/li&gt;
&lt;li&gt;Closest ancestor query selector&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84fce6tun8gh11723qdj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84fce6tun8gh11723qdj.png" alt=" " width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Indications are automatically cleared once all triggered state and UI changes are applied.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Nuanced concurrency management.
&lt;/h3&gt;

&lt;p&gt;Occasionally, UIs are hit with overlapping actions — rapid form resubmission, double clicks, and conflicting actions. This issue becomes especially pronounced when event processing happens remotely.&lt;/p&gt;

&lt;p&gt;The framework offers basic guarantees at a single hook level. Processing occurs sequentially, and if the hook function returns true, subsequent invocations are ignored. That’s too relaxed for some cases, so precise control can be enabled on the client side via the Scopes API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debounce: delays handling of rapid bursts of events.&lt;/li&gt;
&lt;li&gt;Blocking: cancels new events while one is processing.&lt;/li&gt;
&lt;li&gt;Serial: queues events on the client side and processes them sequentially.&lt;/li&gt;
&lt;li&gt;Priority: cancels lower-priority events when a higher-priority event occurs.&lt;/li&gt;
&lt;li&gt;Frame: creates a boundary between pending events and new ones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scope value can be shared between handlers to coordinate behavior across them. Additionally, you can combine multiple scopes to form a pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1akoowvzrn2qj7bj7pxd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1akoowvzrn2qj7bj7pxd.png" alt=" " width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Combining Scopes may feel complex, but it enables highly dynamic UI without artifacts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Straightforward JS integration.
&lt;/h3&gt;

&lt;p&gt;There is nothing wrong with using JS for small UI tasks, and in many cases, a client-side solution is more suitable (e.g. drawing).”&lt;/p&gt;

&lt;p&gt;The framework allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;declare custom hooks (request handlers) and trigger them in JS like a private API&lt;/li&gt;
&lt;li&gt;register JS handlers and call them from Go.&lt;/li&gt;
&lt;li&gt;pass Go data to JavaScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Custom hook example:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxdt25omr93k6zel6pwsu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxdt25omr93k6zel6pwsu.png" alt=" " width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Extras
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;File hosting (private and public)&lt;/li&gt;
&lt;li&gt;CSP header automation&lt;/li&gt;
&lt;li&gt;Import map generation&lt;/li&gt;
&lt;li&gt;JS/TS bundling and building&lt;/li&gt;
&lt;li&gt;Routing options&lt;/li&gt;
&lt;li&gt;Dynamic attributes&lt;/li&gt;
&lt;li&gt;Active link highlighting&lt;/li&gt;
&lt;li&gt;Session and instance lifecycle control&lt;/li&gt;
&lt;li&gt;Performance and resource management tweaks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Concerns Addressed
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Processing events on the server causes delays and hurts UX.
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Response:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With a ping of less than 100ms, the response feels almost instant.  With 200ms+, it's pronounced, but acceptable. For better UX, you need servers closer to the customer regardless of stack.&lt;/p&gt;

&lt;p&gt;Also, in a "traditional" front-end framework, if an event triggers an HTTP request anyway, there is essentially no difference.&lt;/p&gt;

&lt;h4&gt;
  
  
  Lack of animations.
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Response:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Some transitions can be achieved via the Indication API + CSS.&lt;/p&gt;

&lt;p&gt;If there really is a demand for more, let me know, and I will figure it out.&lt;/p&gt;

&lt;h4&gt;
  
  
  Some APIs are verbose.
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Response:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Intentionally. My approach is to give you more control. You can always write a small wrapper and use it everywhere — better than hacking missing functionality.&lt;/p&gt;

&lt;h4&gt;
  
  
  LSP support?
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Response:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are LSP, highlighting and IDE plugins for &lt;a href="https://templ.guide/" rel="noopener noreferrer"&gt;templ&lt;/a&gt;, and it does its job. &lt;/p&gt;

&lt;p&gt;However, not perfect. Also, it's nice to have some framework-specific support. If it succeeds, that is a first priority alongside the LLM-specific docs.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Learning curve?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Response:&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;No doubt, it's different from the tools you're familiar with.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It took me about two weeks to build my approaches and figure out the best practices. But once I got there... I didn't expect it to turn out &lt;strong&gt;so rewarding&lt;/strong&gt;. It has a &lt;strong&gt;solid&lt;/strong&gt;, almost &lt;strong&gt;mechanical&lt;/strong&gt; feel, while being &lt;strong&gt;naked&lt;/strong&gt; to your eye, because of its elementary nature. And the &lt;strong&gt;absence of client-server friction&lt;/strong&gt; makes it flow; I couldn't imagine myself using the traditional stack again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Licensing
&lt;/h2&gt;

&lt;p&gt;As mentioned in the previous article, the framework is a paid product (&lt;strong&gt;lifetime&lt;/strong&gt; license, no subscriptions). However, it's &lt;strong&gt;free for development and non-commercial production use&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Additionally, it's licensed under BUSL-1.1, so each version becomes open source after 4 years of release.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want it to be sustainable, supported, and continually improved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It's called &lt;em&gt;doors&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the two months since my last article, I’ve improved developer experience, performance, and completed documentation and tests to an appropriate level.&lt;/p&gt;

&lt;p&gt;The documentation is still not the best, the tutorial is not comprehensive, and there are probably some bugs left. But I am very satisfied with the framework itself and its internals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I need your input to push it further.&lt;/strong&gt; Please try it out, open GitHub issues with any questions, and share your feedback. &lt;/p&gt;

&lt;p&gt;Stay tuned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Link
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/doors-dev/doors" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://doors.dev" rel="noopener noreferrer"&gt;Docs and tutorial&lt;/a&gt; (works on the framework itself)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>frontend</category>
      <category>backend</category>
      <category>go</category>
      <category>webdev</category>
    </item>
    <item>
      <title>TCP does not guarantee delivery. The only thing it guarantees for the application layer is that if this byte of data is delivered, it will happen after the previous one. Everything else is the developer's responsibility.</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Fri, 25 Jul 2025 07:00:16 +0000</pubDate>
      <link>https://dev.to/derstruct/tcp-does-not-guarantee-delivery-the-only-thing-it-guarantees-for-the-application-layer-is-that-if-45m7</link>
      <guid>https://dev.to/derstruct/tcp-does-not-guarantee-delivery-the-only-thing-it-guarantees-for-the-application-layer-is-that-if-45m7</guid>
      <description></description>
      <category>network</category>
      <category>systemdesign</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>So I Quit $200k Job To Write A Framework</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Thu, 24 Jul 2025 20:07:44 +0000</pubDate>
      <link>https://dev.to/derstruct/so-i-quit-200k-job-to-write-a-framework-51p2</link>
      <guid>https://dev.to/derstruct/so-i-quit-200k-job-to-write-a-framework-51p2</guid>
      <description>&lt;p&gt;This may sound like the worst idea ever. &lt;strong&gt;But I think there is no better time than now.&lt;/strong&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  Trying To Fix The Web Dev: Part 3, The Formula.
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;p&gt;Despite new frameworks popping every week, devs' burnout keeps escalating; the ecosystem is bursting at the seams, and &lt;a href="https://x.com/vercel/status/1942575687761813910" rel="noopener noreferrer"&gt;Vercel monopoly&lt;/a&gt; gives billing nightmares. &lt;/p&gt;

&lt;p&gt;For my solution to succeed, it just needs to be:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Modern like SPA
&lt;/h4&gt;

&lt;p&gt;Composable components, shared and local state, reactivity.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Straight like MPA
&lt;/h4&gt;

&lt;p&gt;Natural SSR (not by running front-end code on the server), fast loading, functional &lt;code&gt;href&lt;/code&gt;, real &lt;code&gt;FormData&lt;/code&gt;, and execution transparency (that was lost).&lt;/p&gt;

&lt;h4&gt;
  
  
  3. API free
&lt;/h4&gt;

&lt;p&gt;Static endpoints only for serving files and pages. Everything else — wrapped and secured by framework.  &lt;/p&gt;

&lt;h4&gt;
  
  
  4. NPM free
&lt;/h4&gt;

&lt;p&gt;Not required to write/run, but not rejected if integration is needed.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Server-Driven
&lt;/h4&gt;

&lt;p&gt;Keep business logic-related stuff on the server and user-related stuff in the browser. &lt;/p&gt;

&lt;h4&gt;
  
  
  6. Straightforward to self-host
&lt;/h4&gt;

&lt;p&gt;Batteries included, a $20/month VPS + basic CI should be enough to kick-start.&lt;/p&gt;

&lt;h4&gt;
  
  
  7. And don't compromise UX along the way
&lt;/h4&gt;

&lt;p&gt;Give users a genuine feel of a lightweight SPA.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxxgez9hp4ty81htrl7ee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxxgez9hp4ty81htrl7ee.png" alt="Meme: me planning vs me implementing" width="553" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Spoiler: I made it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How??
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Architecture: Stateful Server + Extra Thin Client
&lt;/h3&gt;

&lt;p&gt;Imagine - while constructing the "front-end" form (button, whatever), simply attach a "back-end" form handler function, knowing that &lt;strong&gt;only a specific user (anonymous or logged in) on that particular form can trigger it&lt;/strong&gt; without any additional header verification.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How it works&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User loads page&lt;/li&gt;
&lt;li&gt;Server assigns session cookie (just a guid) and initializes &lt;strong&gt;page instance&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;During template rendering, the server collects handler functions (&lt;strong&gt;hooks&lt;/strong&gt;) into the &lt;strong&gt;page instance&lt;/strong&gt; and prepares event-binding attributes&lt;/li&gt;
&lt;li&gt;Browser loads the page, attaches listeners, and connects to the server for synchronization&lt;/li&gt;
&lt;li&gt;Server routes client requests via session and instance to &lt;strong&gt;hooks&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks&lt;/strong&gt; trigger state changes inside &lt;strong&gt;page instance&lt;/strong&gt; and layout updates&lt;/li&gt;
&lt;li&gt;Server-Client synchronization does its job&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fumefrezey63m4jrfris4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fumefrezey63m4jrfris4.png" alt="Schema how it works" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Approach Trade-offs&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each active page will occupy a piece of the server memory &lt;/li&gt;
&lt;li&gt;Server restart may interrupt active users&lt;/li&gt;
&lt;li&gt;Load balancing requires specific strategies &lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are curious, "back-end role" in such an approach is not gone, now it is a separate module/lib in your project or private (internal) API you call in a secure server environment. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Language. Type-safe, Relevant, and Easy to Learn.
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Typescript is JavaScript; there is no way to deny it. It's an awesome language to hack on, but it is a shaky foundation for business logic. Additionally, because of our architecture, we can't afford an interpreted language.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are three industry-proven candidates: Kotlin (as a JVM representative), C#, and Go. &lt;/p&gt;

&lt;p&gt;Golang is one of the simplest languages to learn, offering good performance and a low memory footprint, while being boring.&lt;/p&gt;

&lt;p&gt;Kotlin is a feature-rich, modern alternative to Java. It is likely the ideal choice for an enterprise. But its toolkit is too diverse - appreciated with experience, but overwhelming for most of us. &lt;/p&gt;

&lt;p&gt;I had no experience with C#, but I suspect that what I said about Kotlin applies here as well.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;boring&lt;/strong&gt; choice is obvious.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fast forward — that was a perfect choice. Its concurrency model fits right, standard lib is fantastic, and there is &lt;a href="https://templ.guide/" rel="noopener noreferrer"&gt;templ&lt;/a&gt;, which is just genius. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Concurrency Control System
&lt;/h3&gt;

&lt;p&gt;To synchronize parallel HTML updates/rendering, event processing, and state change reactions, and ensure the front-end receives only the latest state. &lt;strong&gt;No UX compromise in favor of internal simplicity&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I need my version of React Fiber. Probably deserves a separate article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Dynamic Components (and why, probably, React is broken)
&lt;/h3&gt;

&lt;p&gt;Component in the front-end framework is essentially made out of three archetypes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;State And Input&lt;/td&gt;
&lt;td&gt;Data that is used to determine rendered content, often a component is "watching" it to initiate a re-render on change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Template&lt;/td&gt;
&lt;td&gt;HTML pieces used in component logic to construct the final output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container&lt;/td&gt;
&lt;td&gt;The place in the page layout that is affected or controlled by the component&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Component Flow: state/input processing -&amp;gt; template filling &amp;amp; construction -&amp;gt; container update&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An update can affect the virtual DOM first, so only the diff will be applied in the "container"; however, the principle remains the same.&lt;/p&gt;

&lt;p&gt;Typically, this trinity is tightly coupled and represented as a &lt;strong&gt;single entity&lt;/strong&gt;. &lt;strong&gt;I think that is a bad design decision&lt;/strong&gt;, introducing a new dimension of problems on scale because of a lack of execution transparency by design.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By restricting component control to state manipulations alone, it creates indirect, unpredictable flows even for straightforward UI updates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  I kept those three as separate entities, which you can combine however you like.
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entity&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node (container)&lt;/td&gt;
&lt;td&gt;A dynamic HTML placeholder that does not affect visuals (displays its content) and can be updated, replaced, or removed. Internally stored in a &lt;strong&gt;tree&lt;/strong&gt;  structure to manage lifecycle and concurrency.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beam (state)&lt;/td&gt;
&lt;td&gt;A state and communication primitive that can be subscribed to or combined with &lt;strong&gt;Node&lt;/strong&gt;. Respects the &lt;strong&gt;tree&lt;/strong&gt; to ensure consistent render. Derivable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fragment (component + template)&lt;/td&gt;
&lt;td&gt;Entity with Render method. Composable. Can encapsulate multiple &lt;strong&gt;Node&lt;/strong&gt;s, &lt;strong&gt;Beam&lt;/strong&gt;s and have control methods and fields&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;How it works in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You create a &lt;strong&gt;Fragment&lt;/strong&gt; (component) instance, providing a &lt;strong&gt;Beam&lt;/strong&gt; (shared reactive state) as a dependency (or initialize an internal reactive state if you need one).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;Fragment&lt;/strong&gt;'s render function inserts dynamic &lt;strong&gt;Nodes&lt;/strong&gt; with initial content into the layout and establishes &lt;strong&gt;Beam&lt;/strong&gt; relationships (either by explicitly subscribing or by making parts of the layout automatically re-render on change).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On UI events, hook functions update dynamic &lt;strong&gt;Nodes&lt;/strong&gt; explicitly with new content or trigger reactivity via &lt;strong&gt;Beam&lt;/strong&gt; mutations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;Fragment&lt;/strong&gt; can expose an interface to provide abstracted control over its internal dynamic &lt;strong&gt;Nodes&lt;/strong&gt; or &lt;strong&gt;Beams&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;del&gt;If you're interested in trying it out, please submit your email address here for an early preview. I would appreciate your feedback.&lt;/del&gt; &lt;a href="https://doors.dev" rel="noopener noreferrer"&gt;Try it out&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5. Real-time Synchronization Mechanics
&lt;/h3&gt;

&lt;p&gt;That does not build on top of WS or SSE, while providing similar or better performance characteristics.&lt;/p&gt;

&lt;p&gt;Ended up with rolling handover request, simplified logic: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client sends a regular request to the server&lt;/li&gt;
&lt;li&gt;Server streams updates (HTML fragments and remote JS calls) with some metadata&lt;/li&gt;
&lt;li&gt;Client opens a new request with a report (batched results and metadata)&lt;/li&gt;
&lt;li&gt;Server switches streaming to the new request from the old one&lt;/li&gt;
&lt;li&gt;Cycle continues&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;That approach requires a more advanced synchronization error correction protocol (because it involves two independent data streams compared to WS). Probably deserves a separate article.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the client is always connected, delays are minimal, and QUIC is utilized.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Page Routing
&lt;/h3&gt;

&lt;p&gt;Declarative, reactive, and type-safe.&lt;/p&gt;

&lt;h4&gt;
  
  
  Concept
&lt;/h4&gt;

&lt;p&gt;Each page is a mini-app; if navigation occurs within the single-page routing pattern, the HTML is dynamically updated.&lt;/p&gt;

&lt;h4&gt;
  
  
  Page Route
&lt;/h4&gt;

&lt;p&gt;Page paths are deserialized into annotated structs (&lt;strong&gt;Path Models&lt;/strong&gt;), which support multiple path variants, route parameters, queries, and catch-all patterns. &lt;br&gt;
The resulting &lt;strong&gt;Path Model&lt;/strong&gt; is wrapped into &lt;strong&gt;Beam&lt;/strong&gt; for reactive updates throughout the application, enabling type-safe routing with automatic parameter parsing and propagation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Of course, &lt;strong&gt;Path Model&lt;/strong&gt; can be serialized back to path+query string, and &lt;strong&gt;Path Models Beam&lt;/strong&gt; is synced with the front-end.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Limitations
&lt;/h4&gt;

&lt;p&gt;A full reload occurs when you switch between mini-apps (and thus, their &lt;strong&gt;Path Models&lt;/strong&gt;); this is by design. You can still declare many patterns and derive smaller state pieces from the path structure to achieve a 100% SPA experience, though this approach prioritizes clean boundaries over maximum SPA fluidity.&lt;/p&gt;

&lt;p&gt;No parameter or query value validation has been implemented yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Event Handlers
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Pointer&lt;/code&gt;, &lt;code&gt;Keyboard&lt;/code&gt;, &lt;code&gt;Form&lt;/code&gt;, and &lt;code&gt;Input&lt;/code&gt; related events are captured by the client library and proxied to the back-end if a handler function (&lt;strong&gt;Hook&lt;/strong&gt;) is provided during rendering. &lt;/p&gt;

&lt;p&gt;Other events require manual (but trivial) integration and will be supported out of the box later.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. And Other Things...
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Static Serving
&lt;/h4&gt;

&lt;p&gt;Serve assets, images, etc, publicly or session-scoped.&lt;/p&gt;

&lt;h4&gt;
  
  
  JS integration tools
&lt;/h4&gt;

&lt;p&gt;To call front-end functions from the server and back-end functions from JS for advanced control and integrations&lt;/p&gt;

&lt;h4&gt;
  
  
  Resource and session/instance management mechanics
&lt;/h4&gt;

&lt;p&gt;Each active page will have representation in server memory, and UI updates will utilize server CPU, so it's a must-have.&lt;/p&gt;

&lt;h4&gt;
  
  
  JS/TS tools
&lt;/h4&gt;

&lt;p&gt;If the developer needs to enable rich on-page interactivity or embed a mini React app — &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; is embedded and integrated.&lt;/p&gt;

&lt;h4&gt;
  
  
  Front-end indications and concurrency control
&lt;/h4&gt;

&lt;p&gt;To display pending states, debounce input events, block duplicated form submissions, etc.   &lt;/p&gt;

&lt;h4&gt;
  
  
  CSP tools
&lt;/h4&gt;

&lt;p&gt;To automate &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP" rel="noopener noreferrer"&gt;CSP&lt;/a&gt; headers insertion&lt;/p&gt;

&lt;h2&gt;
  
  
  It will be a paid product, but don't worry!
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Affordable lifetime ownership license, no subscription&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Source code will be available on GitHub, like any open source&lt;/li&gt;
&lt;li&gt;Free for development, buy only to go prod&lt;/li&gt;
&lt;li&gt;No telemetry or any form of data collection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why??
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Not backed by a big company or VC funding&lt;/li&gt;
&lt;li&gt;Not relying on spare time maintenance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Here to give you the best tool, not serving other interests&lt;/strong&gt;. It's part of &lt;strong&gt;The Formula&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Explain to my wife why it should not be&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;I believe that technologies should be accessible and free. In fact, everything should be. However, that's not a world we currently live in. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Additionally, I am currently working alone, and this cannot continue forever. I need to hire dedicated professionals (it's also part of &lt;strong&gt;The Formula&lt;/strong&gt;), and the project requires top-tier talent. &lt;/p&gt;

&lt;h2&gt;
  
  
  Status
&lt;/h2&gt;

&lt;p&gt;After nearly eight months of hard work and four or five complete rewrites, I finally checked all the boxes (and I can't describe how tough that was). It's more of an application runtime than a framework. Currently, I'm working on tests (coverage is now around 70%) and documentation. And I'm honestly enjoying the experience it provides.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Server RAM usage for a simple active page is below 1MB*, and CPU utilization is low. Probably, a $16/month (8GB RAM, 4 vCPU) Hetzner VPS will be enough to host a theoretical average app for over 1000 concurrent users with balanced performance settings. However, more detailed benchmarking is needed — I plan to dedicate an article to this topic in the future.&lt;br&gt;
*UPD. Below 50KB... &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My goal is to launch a public beta in August. However, before that, input from both experienced and less experienced developers would be invaluable. &lt;del&gt;If you'd like to participate in early access or be notified when the beta is available, please sign up here&lt;/del&gt; &lt;a href="https://doors.dev" rel="noopener noreferrer"&gt;Try it out&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Production licenses will be gifted to early access participants as a token of appreciation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the following article, "The Framework", we will examine its API with real code snippets.&lt;/p&gt;

&lt;p&gt;Thank you very much for reading and your feedback. Stay tuned.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>backend</category>
      <category>frontend</category>
      <category>go</category>
    </item>
    <item>
      <title>Hypermedia or Server-Driven?</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Tue, 15 Jul 2025 15:26:39 +0000</pubDate>
      <link>https://dev.to/derstruct/hypermedia-or-server-driven-39ml</link>
      <guid>https://dev.to/derstruct/hypermedia-or-server-driven-39ml</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/derstruct" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3323219%2Ffb853a63-ac17-44bd-846b-b787e9011188.png" alt="derstruct"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/derstruct/htmx-is-worse-than-react-and-websocket-is-obsolete-c8d" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;HTMX Is Worse Than React, and WebSocket Is Obsolete?&lt;/h2&gt;
      &lt;h3&gt;Alex ・ Jul 15&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#architecture&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#frontend&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#backend&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>frontend</category>
      <category>backend</category>
    </item>
    <item>
      <title>HTMX Is Worse Than React, and WebSocket Is Obsolete?</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Tue, 15 Jul 2025 08:26:46 +0000</pubDate>
      <link>https://dev.to/derstruct/htmx-is-worse-than-react-and-websocket-is-obsolete-c8d</link>
      <guid>https://dev.to/derstruct/htmx-is-worse-than-react-and-websocket-is-obsolete-c8d</guid>
      <description>&lt;h1&gt;
  
  
  Trying To Fix The Web Dev: Part 2, The Solution?
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;If you missed an &lt;a href="https://dev.to/derstruct/trying-to-fix-the-web-dev-49fb"&gt;introduction&lt;/a&gt; to the series, please check it out first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the part where it gets serious. &lt;/p&gt;

&lt;h2&gt;
  
  
  Disqualified Candidates: New Wave Front-End Frameworks.
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Svelte, Preact, etc.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some of them try to address the complexity (making simple things more straightforward and complex things harder).&lt;/li&gt;
&lt;li&gt;Some client-side bloat&lt;/li&gt;
&lt;li&gt;Some state management burnout&lt;/li&gt;
&lt;li&gt;Some are distilled versions of others&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But none of them solves &lt;a href="https://dev.to/derstruct/trying-to-fix-the-web-dev-49fb"&gt;The Issue ™&lt;/a&gt; (even &lt;a href="https://medium.com/@resti.guay/juris-returning-to-the-source-of-universal-development-0c87b55b876c" rel="noopener noreferrer"&gt;Juris&lt;/a&gt;) because &lt;strong&gt;they do nothing with the server&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forzub6foe2zu9v3qvns3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forzub6foe2zu9v3qvns3.png" alt=" " width="515" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Candidate #1: Hypermedia-Driven
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;HTMX, Unpoly, fixi.js, etc.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"HTML enhancement" that enables fragment fetching and partial page updates in response to user actions without writing JS code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F545c8eak4qt468p5bed7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F545c8eak4qt468p5bed7.png" alt=" " width="733" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;General Flow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User triggers an event on the element (e.g., clicks a button or submits a form).&lt;/li&gt;
&lt;li&gt;The hypermedia library inspects the element's attributes to determine the request URI, then sends the request via AJAX.&lt;/li&gt;
&lt;li&gt;The server returns an HTML fragment &lt;/li&gt;
&lt;li&gt;The hypermedia library inspects attributes to determine where and how to apply the HTML patch.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;What Is Good:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Logic is on the surface (you don't have to understand component state and run JS code in your head to figure out what will happen after user action)&lt;/li&gt;
&lt;li&gt;No need for the client-side input validation logic&lt;/li&gt;
&lt;li&gt;Extremely light on the client&lt;/li&gt;
&lt;li&gt;SSR without hydration&lt;/li&gt;
&lt;li&gt;Feels like a natural evolution of HTML (there is even a standardization initiative)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Why I Will Never Use It:&lt;/strong&gt;
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Control Flow Distribution
&lt;/h3&gt;

&lt;p&gt;Imagine that every time you write a function, you do something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start as always: "function onForm(data) {"&lt;/li&gt;
&lt;li&gt;Create a separate file submit.js and write the function body there&lt;/li&gt;
&lt;li&gt;In the original file, you write a meaningless text macro to include the body at runtime and then return the result.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function onForm(data) {
  @submit.js
  return result
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Does it look like a haven? &lt;/p&gt;

&lt;p&gt;That's how I feel about HTMX:&lt;br&gt;
&lt;strong&gt;function onForm:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!--  post to "/submit", then replace the form itself with the response body --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/submit"&lt;/span&gt; &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"this"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;submit.js:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// http handler, that processes form data&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* authorization, validation */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// respond with HTML (usually using a template engine)&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;p&amp;gt;Submitted: &amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/strong&amp;gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;lt;/p&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;But isn't it the same as the "old" approach? &lt;em&gt;No, because in native HTML after submission, the new page will be loaded, so control is just passed to the back-end and never returned.&lt;/em&gt;&lt;br&gt;
Isn't calling the API a similar thing? &lt;em&gt;No, because in 90% cases, the API is just a DB wrapper; it has nothing to do with the control flow.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Shallow Simplicity
&lt;/h3&gt;

&lt;p&gt;Arguably, the hypermedia stack is the fastest way to launch. However, the limitations of the architecture complicate the implementation of an already complex UI.&lt;/p&gt;

&lt;p&gt;It's like you have only one way to build user flow - you store stateless functions that always output a single HTML fragment in a huge map, and then for each interactive element, specify the map key and arguments as constants. Does it make simple things more straightforward? Yes. Does it make tricky UIs even messier? Also, yes.&lt;/p&gt;

&lt;p&gt;What if you need a &lt;strong&gt;multi-step form or non-linear user flow&lt;/strong&gt;? Store intermediate values in HTML, cookies, or external memory. Is it more transparent than using a variable? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update several places on the page&lt;/strong&gt; in response to user action? &lt;a href="https://htmx.org/attributes/hx-swap-oob/" rel="noopener noreferrer"&gt;Make a total control flow mess&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;UI state and state management libraries are here for a reason, and &lt;strong&gt;complex web apps are a modern-day requirement&lt;/strong&gt;, keeping web relevant in the mobile apps era.  There is a particular hate towards &lt;code&gt;state&lt;/code&gt; in the community, but it's probably because it allows us to create more elaborate things.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Text-driven
&lt;/h3&gt;

&lt;p&gt;It's not wrong. However, I don't feel comfortable describing an algorithm via HTML attributes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Endpoint Security
&lt;/h3&gt;

&lt;p&gt;Although the "API issue" from &lt;a href="https://dev.to/derstruct/trying-to-fix-the-web-dev-49fb"&gt;the previous article&lt;/a&gt; is addressed - "DB wrapper" is gone (there can't be more "Backend For Frontend" than HTMX server), you still need to care a lot about proper endpoint authorization logic (with state-in-request, there are even more things to keep in mind).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Verdict:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Front-end hypermedia tools &lt;strong&gt;scale the "classic" approach, without improving it&lt;/strong&gt;, even if there are (or will be) more "sugar" containing libs that partially address my points. &lt;br&gt;
Don't get me wrong, it's great technology that has its niche. However, it's essential to consider its limitations when selecting a stack for a long-term project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;So, does it address &lt;strong&gt;The Issue&lt;/strong&gt;? Partially. Is it &lt;strong&gt;The Solution&lt;/strong&gt;? Hell no.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Candidate #2: Server-Driven
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Hotwire Turbo Streams, Phoenix Live View, Laravel Livewire, Blazor Server&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Main UI logic is written for the server and runs on the server; the client acts as a "remote" renderer and user input provider.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;General Flow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User triggers an event on the element (e.g., clicks a button or submits a form).&lt;/li&gt;
&lt;li&gt;Client lib intercepts the event and sends it to the server&lt;/li&gt;
&lt;li&gt;Server prepares HTML updates and how to apply them, wires that to the client&lt;/li&gt;
&lt;li&gt;The client follows the server's instructions&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Compared to the previous candidate, the server owns control flow completely. The browser has no idea what will happen in response to the event. You can think of it like a backwards API - the server calls the client to modify HTML on the fly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What Is Good:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Business logic has returned to the server 🔥&lt;/li&gt;
&lt;li&gt;Light on the client&lt;/li&gt;
&lt;li&gt;SSR without hydration&lt;/li&gt;
&lt;li&gt;State-full* server superpowers, like page instance &amp;amp; element scoped  endpoints/handlers (no developer-defined and exposed API at all)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;* Not all of them are stateful&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;So this is it? &lt;strong&gt;The Issue&lt;/strong&gt; is solved: extra-thin client, no API, business logic executed in a controlled and safe environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Compromises:&lt;/strong&gt;
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Blocking Event Processing
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Guilty: Phoenix Live View and Blazor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Page by design lives in a single thread or process (Phoenix). In other words, when interacting with multiple page elements, processing, database interaction, and rendering occur in series, with the next event being processed after the previous cycle is complete.&lt;/p&gt;

&lt;p&gt;That makes internal framework logic much simpler (no concurrency issues), but it looks like a blocker for mass adoption. You don't want your UI to be unresponsive or accumulate an event queue when a time-consuming task is running. You can mitigate this by leveraging background processes and asynchronous tasks; however, the complexity cost seems too high to justify the effort (task management &amp;amp; UI synchronization).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hotwire lacks concurrency control at all. Livewire, by default, blocks the component during a request on the client side, which is most of the time good enough. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  50/50 State Situation
&lt;/h3&gt;

&lt;p&gt;You can argue that modern web UI can operate without state. But it's here already, relied on everywhere, and keeps being reinvented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hotwire&lt;/strong&gt;&lt;br&gt;
No built-in state management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel Livewire&lt;/strong&gt;&lt;br&gt;
Serializes component state to hidden inputs under the hood, which seems cool. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Laravel server is stateless and does not "live" in memory. It "reacts" only to requests - restores component state from request data and session storage, then uses it to render an HTML update; However, it has built-in mechanics to trigger the update of multiple components (within one request lifecycle).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Blazor Server&lt;/strong&gt;&lt;br&gt;
Full support: component state, global state, derive functionality, reactivity. But C#, so who cares (&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phoenix Live View&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Global state&lt;/strong&gt; per LiveView (root app component) instance: propagated to components as props and triggers rendering, similar to storing state in the root component in React and passing down the tree as props.&lt;br&gt;
&lt;strong&gt;Components state&lt;/strong&gt;, which operates in a similar way to React. &lt;/p&gt;

&lt;h3&gt;
  
  
  Grandpa WebSocket
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;UI updates rely on it in: Phoenix Live View, Blazor Server, Hotwire Turbo Streams (truly server-driven part of Hotwire, because Turbo Frames are just like HTMX)&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkttw220wiv37g5z72ude.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkttw220wiv37g5z72ude.png" alt=" " width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's address the clickbait elephant in the room.&lt;/p&gt;

&lt;h4&gt;
  
  
  Not compatible with QUIC (HTTP3).
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caniuse.com/http3" rel="noopener noreferrer"&gt;95% browsers now support QUIC&lt;/a&gt;. It offers higher resistance to network conditions, lower latency, and faster connection establishment. If your app users live in a datacenter, that makes no difference. Otherwise, for a server-driven UI, lack of QUIC hurts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;WS via QUIC may be enabled in the future. However, even WS via HTTP/2 (after 10 years) adoption remains rare. Also, with wider streaming request body (hello safari) and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader" rel="noopener noreferrer"&gt;BYOB&lt;/a&gt; support or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebTransport" rel="noopener noreferrer"&gt;WebTransport&lt;/a&gt;, we won't need WS that much.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Sloppy Reconnect
&lt;/h4&gt;

&lt;p&gt;Connection loss detection is guaranteed only by pings or heartbeats. For example, 10 seconds between pings, with at least two skips required to initiate reconnect, resulting up to &lt;strong&gt;20 seconds of unusual UI behavior&lt;/strong&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Timeout-based disconnect detection is unavoidable in real-time scenarios, regardless of whether you are using WS or not. But it adds up to weaker network condition resistance of TCP compared to QUIC, longer handshake, and separate connection circuit (meaning other network app activity failing does not help WS disconnect detection) &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  App Users Notice
&lt;/h4&gt;

&lt;p&gt;I am talking from experience. WebSocket-controlled UI comes with the deal: &lt;strong&gt;there will be cases, despite the internet already being reachable, where the app is not responding for a noticeable time&lt;/strong&gt;. Maybe it will happen not so often, and perhaps it's not ruining UX, but it's a design flaw.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SSE situation is also not great. The browser is extremely sloppy at reconnecting. I have no idea why. Especially after PC wake-ups from sleep, and again, you will rely on heartbeat (and manual event source recreation).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Non-Native Forms
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Guilty: Phoenix Live View and Blazor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By default, it is serialized and passed via WebSocket. Not a deal breaker, but requires care when dealing with files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Endpoint Security
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Laravel Livewire, Hotwire&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Server is stateless and does not maintain in‑memory UI component instances with unique "hooks" between requests. Endpoints are static, so you must enforce authentication and authorization on every handler, just as you would with HTMX.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stateful Cost
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Phoenix Live View and Blazor&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load balancing is not so trivial, because a page "lives" on a specific server instance&lt;/li&gt;
&lt;li&gt;Each active page consumes memory on the server&lt;/li&gt;
&lt;li&gt;Non-cachable HTML&lt;/li&gt;
&lt;li&gt;UI will lose state after a period of inactivity (server memory is not infinite) or due to server restart&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Seems like a reasonable compromise for me. With more users, you probably can afford more computing resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Verdict:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Phoenix Live View is fascinating as a concept (app lives on the server, browser as remote actor), but quirks like  "single-threaded" blocking UI and HTTP/1.1 reliance make it hard to recommend. Not to mention that elixir is not everyone's cup of coffee.&lt;/p&gt;

&lt;p&gt;From a theoretical perspective, Laravel Livewire represents an evolution (real one this time) of the classic web — stateful Components and "live" page updates, all while the server remains stateless. However, it's not factually "live". There is no app runtime; you can't initiate UI Updates from the server because there's no reactivity, endpoints are static and require care.&lt;/p&gt;

&lt;p&gt;Blazor (the server-side variant) is conceptually similar to Phoenix LiveView, but it has a distinct Microsoft flavor. Users' reports confirm it is sluggish and has high resource consumption.&lt;/p&gt;

&lt;p&gt;Hotwire looks like it is either advanced HTMX or inferior Livewire.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;So, does it address &lt;strong&gt;The Issue&lt;/strong&gt;? Yes. Is it &lt;strong&gt;The Solution&lt;/strong&gt;? Unfortunately, no, too huge of a compromise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  There is no end to our suffering?
&lt;/h2&gt;

&lt;p&gt;I think there is, and now I know &lt;strong&gt;The Formula&lt;/strong&gt;. So, I quit my job as CTO and dived in. &lt;/p&gt;

&lt;p&gt;Subscribe for the &lt;a href="https://dev.to/derstruct/so-i-quit-200k-job-to-write-a-framework-51p2"&gt;&lt;strong&gt;Part 3: The Formula&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>frontend</category>
      <category>backend</category>
    </item>
    <item>
      <title>Future proof you career</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Tue, 08 Jul 2025 17:22:53 +0000</pubDate>
      <link>https://dev.to/derstruct/future-proof-you-career-jb2</link>
      <guid>https://dev.to/derstruct/future-proof-you-career-jb2</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/derstruct" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3323219%2Ffb853a63-ac17-44bd-846b-b787e9011188.png" alt="derstruct"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/derstruct/future-proof-your-career-in-ai-era-27da" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Young Devs: 3 Career Hacks in the Age of AI&lt;/h2&gt;
      &lt;h3&gt;Alex ・ Jul 8&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#programming&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ai&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>programming</category>
      <category>career</category>
      <category>ai</category>
    </item>
    <item>
      <title>Young Devs: 3 Career Hacks in the Age of AI</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Tue, 08 Jul 2025 11:20:59 +0000</pubDate>
      <link>https://dev.to/derstruct/future-proof-your-career-in-ai-era-27da</link>
      <guid>https://dev.to/derstruct/future-proof-your-career-in-ai-era-27da</guid>
      <description>&lt;p&gt;It's 2025; you are young, ambitious, and preparing for a career in programming.&lt;/p&gt;

&lt;p&gt;However, seems like everybody in the industry is discussing how AI will replace programmers. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maybe it's right about them, but not about you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I will not recommend or sell you an AI tool or tell you some bullshit, like "be better than others and follow your passion." &lt;br&gt;
I will share a [not easy] strategy you can follow to improve your chances, along with some practical insights - what you can start learning right now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: I was the head of the R&amp;amp;D department and interviewed over a hundred people. My area of operation was primarily related to software and firmware that run on specialized devices, such as IoT modules, cameras, industrial controllers, specialized tablets, etc. However, we also occasionally did related web development projects. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Avoid following common recommendations
&lt;/h2&gt;

&lt;p&gt;You: Where should I start?&lt;br&gt;
They: Python. JavaScript/React. Mobile apps. Testing. It's easy to learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy to learn doesn't mean &lt;strong&gt;right for you&lt;/strong&gt;.
&lt;/h3&gt;

&lt;p&gt;You are young, your brain is agile. You can handle learning more complicated things. Don't waste the opportunity to gain unique skills.  &lt;/p&gt;

&lt;h3&gt;
  
  
  LLMs are trained on common stuff
&lt;/h3&gt;

&lt;p&gt;JavaScript and Python are the default languages for LLMs due to their extensive training datasets. So React, Node.js, Django, you name it — risky choice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't get me wrong, &lt;strong&gt;LLMs can't write code well (and won't be soon), require professional guidance and supervision&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Overcrowded.
&lt;/h3&gt;

&lt;p&gt;Training courses are easy to sell. Recommended by almost everybody. &lt;a href="https://www.businessinsider.com/jobs-software-engineers-coders-bad-market-ai-2025-3" rel="noopener noreferrer"&gt;Bubble&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Look for the natural LLM barriers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Connected To the Physical World
&lt;/h3&gt;

&lt;p&gt;Mistake cost is too high (and can even be fatal); therefore, humans will continue to play a significant role in the R&amp;amp;D process for a long time. And also, you need hands.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Machinery and industrial control&lt;/li&gt;
&lt;li&gt;Medicine&lt;/li&gt;
&lt;li&gt;Automotive&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Low-Maintenance And No-Maintenance Systems
&lt;/h3&gt;

&lt;p&gt;Software or firmware upgrades in the field are often costly or impossible. No one lets the device with LLM-generated code exit the factory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Environmental data loggers &lt;/li&gt;
&lt;li&gt;Fixed-function devices (like an air-conditioner)&lt;/li&gt;
&lt;li&gt;Remote Asset Trackers&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Mission Critical Systems
&lt;/h3&gt;

&lt;p&gt;The reason is the same as in the previous points.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Telecommunication, networks&lt;/li&gt;
&lt;li&gt;Power Grid Management &lt;/li&gt;
&lt;li&gt;Transportation&lt;/li&gt;
&lt;li&gt;Financial Transaction Systems&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;But am I good enough for that?&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;You may think that no one will allow you to touch it. However, it's not true - experienced colleagues will review your code in detail, provide guidance, and education. Trials will take months if not years. And in comparison to guiding the LLM, this will pay off. You are a valuable asset. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In college, one of my fellow students worked on a nuclear power plant control sub-system. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Low-Latency and High Performance
&lt;/h3&gt;

&lt;p&gt;Requires precision, collective effort, and is highly context-dependent (an average approach will not cut it). The quality and complexity are significantly higher than the median, which renders the LLM invalid for such cases (until real revolution in approach/architecture and probably hardware).&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Data Processing Cores&lt;/li&gt;
&lt;li&gt;Financial market&lt;/li&gt;
&lt;li&gt;Transaction Systems&lt;/li&gt;
&lt;li&gt;Data compression, encoding, decoding&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Search For Niches
&lt;/h2&gt;

&lt;p&gt;Here is my top 3. I recommend starting with the first one; it's a solid foundation for the next two and a programming career in general.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microcontroller (MCU) Programming
&lt;/h3&gt;

&lt;p&gt;The average age of developers in this area is approximately &lt;em&gt;40, and there is almost no new talent&lt;/em&gt; &lt;br&gt;
Unreachable for LLMs and will always remain relevant. Nearly any electrical device more intelligent than a kettle without a colorful display is powered by an MCU.&lt;/p&gt;

&lt;p&gt;Writing low-level code to run on low-power hardware is a challenging task. However, it is also fun. Use your fantasy to select the first learning project - build a canon with ultrasonic target detection (my first project), a crawling robot, an active balancing camera mount. It's a dream.&lt;/p&gt;

&lt;p&gt;Start with a &lt;a href="https://www.st.com/en/evaluation-tools/stm32f4discovery.html" rel="noopener noreferrer"&gt;dev board&lt;/a&gt; and bare metal (HAL, learn UART, SPI, I2C), then conquer FreeRTOS and dive into electrical engineering a little—you're safe.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can try to argue that hardware will get so powerful that we can just run AI/AI-generated JavaScript everywhere. That is not true; there will always be a demand for highly energy-efficient and robust systems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Warning: &lt;strong&gt;Arduino is not recommended&lt;/strong&gt; to start learning embedded development. It's not recognized by the industry as a professional tool (for DIY, kids, it's ok). STM Discovery boards are the default choice (it's much easier to work with than you may think).&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedded Linux
&lt;/h3&gt;

&lt;p&gt;Gold specialists. I knew three who know how to modify the Linux kernel, write device drivers, and configure &amp;amp; automate the Yocto/Buildroot building process. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If a MCU is not the brain of the device and it's not 4/5G cellular enabled, it's likely running on Linux. Advanced industrial controllers, cameras, drones, smart home hubs, portable game consoles, medical equipment, and much more. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Android System (not apps)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;! Training materials are difficult to find&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even rarer specialists. I knew two, but I couldn't manage to hire a single one in-house (it was impossible to compete with x2-x3 offers from corporations).  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the company plans an Android-based product (even if it's developed and produced in China), there's no way around this competence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Build an Android from the sources. Add drivers, configure peripherals. Embed the application with system privileges, tweak power cycle and battery management, guide OTA implementation, and write platform native code (not Java/Kotlin). &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Android rocks when all-around connectivity is required, advanced media and camera capabilities, and touch-heavy UIs are needed. It's not only about smartphones. POS terminals, interactive panels, digital signage, 4G-connected cameras, vending machines, smart speakers, and various other gadgets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Conduct Your Research
&lt;/h3&gt;

&lt;p&gt;I am talking from the perspective of my industry alone. For example, low-level, high-performance server code (C/C++/Zig/Rust) - follows the same logic: LLM safe, challenging to master, and not so popular (as it could seem). Also, there is &lt;a href="https://digilent.com/blog/what-is-an-fpga/" rel="noopener noreferrer"&gt;"hardware programming" &lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: don't run for the money
&lt;/h2&gt;

&lt;p&gt;Today's trends are not tomorrow's reality. Making your choice based only on salary ranking may sound reasonable, but &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It's not your ranking, but those who came before you.&lt;/li&gt;
&lt;li&gt;You can easily end up in an overpopulated niche that was once on the hype.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;However, Go and Kotlin are &lt;strong&gt;solid&lt;/strong&gt; choices for today. Industry-proven, well-paid, job opportunities are growing, and extensive training materials are available. If you jump in right now, you probably won't miss.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cautions Of Suggested Approach
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Not So Common
&lt;/h3&gt;

&lt;p&gt;You should search first to see if there are companies that can offer such jobs within your reach.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It won't be many. Not all cities (even countries) can offer that. However, &lt;a href="https://www.linkedin.com/pulse/job-market-prospects-embedded-systems-ya7uc/" rel="noopener noreferrer"&gt;demand for specialists exceeds supply and continues to grow&lt;/a&gt;. Relocation is also common.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Fewer Remote Opportunities
&lt;/h3&gt;

&lt;p&gt;If you work with something physical, you need to visit the office often. You need help from your hardware fellas to debug. &lt;/p&gt;

&lt;h3&gt;
  
  
  Requires Education?
&lt;/h3&gt;

&lt;p&gt;It's recommended to have a related university degree. But training materials are available online (that's how I mainly learned). At least half of the people I was working with had an education in other fields (but mostly engineering).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My perspective is limited to my industry. Still, you can extrapolate suggested principles (and even find new ones following the same logic) to other areas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I can think of nothing more interesting than programming. I hope you also share my passion one day, while earning a good income as a bonus. &lt;/p&gt;

&lt;p&gt;Good Luck!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>career</category>
      <category>ai</category>
    </item>
    <item>
      <title>JavaScript Is Not The Issue</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Sun, 06 Jul 2025 08:52:46 +0000</pubDate>
      <link>https://dev.to/derstruct/trying-to-fix-the-web-dev-49fb</link>
      <guid>https://dev.to/derstruct/trying-to-fix-the-web-dev-49fb</guid>
      <description>&lt;h1&gt;
  
  
  Trying To Fix The Web Dev: Part 1, The Issue.
&lt;/h1&gt;

&lt;p&gt;Some of us remember the "old" days when web development was simple.&lt;br&gt;&lt;br&gt;
PHP, two types of forms, and some jQuery for the magic to happen (AJAX hit like using ChatGPT the first time).&lt;br&gt;&lt;br&gt;
And it was pretty reliable too, despite everybody managing servers themselves (I still do btw, using vim).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpgluhoxgke3dslwy45p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpgluhoxgke3dslwy45p.png" alt="rememberries from south park" width="320" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  React.js Happened.
&lt;/h2&gt;

&lt;p&gt;Angular happened first, but I skipped that.&lt;br&gt;&lt;br&gt;
So, React felt like a new era was coming – now you could write and embed highly interactive mini-apps.&lt;br&gt;&lt;br&gt;
Of course, that was possible before, but React enforced component architecture with props propagation and manageable component state – back then, I (20 y.o., 70% PHP monkey, 30% jQuery enjoyer) could not even think about something like this, and it gave me superpowers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;BTW, I think micro apps with little to no back-end interaction are the perfect use case for front-end frameworks. Perhaps because that was what they were actually designed for, or maybe because I don't fully understand the world.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Nowadays.
&lt;/h2&gt;

&lt;p&gt;Oh boy. I am CTO and co-owner of an international hardware development + production company (DH + EMS).&lt;br&gt;&lt;br&gt;
How I got there – different story. But what’s important: I’ve built many things with my teams.&lt;br&gt;&lt;br&gt;
From an Electron-based manufacturing control &amp;amp; automation system, to light show drone firmware with a custom UDP swarm control protocol (I wrote the core myself in Rust – I [spoiler] was a bad manager), to actual hardware.&lt;/p&gt;

&lt;p&gt;I even had my own &lt;a href="https://www.youtube.com/watch?v=QwUPs5N9I6I" rel="noopener noreferrer"&gt;JDSL&lt;/a&gt;, which powered [spoiler] server-driven production operator terminals — so you could deploy new workstations just by describing the execution process in JSON. It was so bad and somehow surprisingly awesome.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;BUT every freaking time I had to deal with the web during this career period, it caused me pain.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Issue.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5lruvxjxg80ryz2eiuj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5lruvxjxg80ryz2eiuj.jpg" alt="Who wants to be a millionaire? Meme" width="442" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't want to yell about JS and its ecosystem. Everybody’s already doing that.&lt;/p&gt;

&lt;p&gt;I think there is something much fundamental:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Business logic has migrated to the browser.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Take a look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpi4b85e3ftrgr6s58j9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpi4b85e3ftrgr6s58j9w.png" alt="meme" width="436" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, for real:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gpvhuqlsttlrsvmep0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gpvhuqlsttlrsvmep0m.png" alt=" " width="800" height="1139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It would be ignorant to claim the old approach fits the modern web. Sites built like that often feel outdated, lack features and the UX sucks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why is Business Logic In the Browser an Issue?
&lt;/h2&gt;

&lt;p&gt;You probably already felt that something was wrong. Here are my anti-highlights:&lt;/p&gt;

&lt;h4&gt;
  
  
  #1 Huge Deal: Front-end dictates API access.
&lt;/h4&gt;

&lt;p&gt;The data you need to read or write conditionally depends on business logic, and so it is reflected in the UI flow.&lt;br&gt;&lt;br&gt;
That means you have to reflect the UI flow in API access schemas, which operate in a completely different dimension.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Such a unified control schema cannot exist.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That’s arguable. And I can object to myself here, often, just role-based is good enough. Ok, role-based with some extra configurable properties. Ok, role-based with some properties and runtime conditions. Ok, with a bunch of architectural nightmare code – 100% doable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For straightforward logic applications, that’s not a big deal.&lt;br&gt;&lt;br&gt;
However, if you’re entering the B2B space with control panels, a bit of business process automation (not to mention ERP), it's going to be rough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1x3r08472xnsvdb02ak6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1x3r08472xnsvdb02ak6.gif" alt=" " width="498" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Or follow the vibe™ and don’t care. Rely on &lt;a href="https://www.reddit.com/r/funny/comments/9jxc6a/elusive_joe/" rel="noopener noreferrer"&gt;Elusive Joe&lt;/a&gt; security strategy and &lt;a href="https://graphql.org/" rel="noopener noreferrer"&gt;expose&lt;/a&gt; your whole db.&lt;/p&gt;

&lt;p&gt;Boring: &lt;a href="https://www.akamai.com/newsroom/press-release/new-study-finds-84-of-security-professionals-experienced-an-api-security-incident-in-the-past-year" rel="noopener noreferrer"&gt;84% reported experiencing at least one API‑related security incident in the past year&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  #2 Opinion: Thick Client.
&lt;/h4&gt;

&lt;p&gt;This largely depends on your industry and the complexity of your app's functionality.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Building the complex logic of your service on top of a million lines of JS dependencies to be executed in the browser doesn't feel right.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;JavaScript is the best language in the world. Or how else do you explain that something made for SCRIPTING now powers the web?&lt;/p&gt;

&lt;p&gt;But seriously. It's genius, just the application seems narrower.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I think that too much freedom in approaching deeper-than-surface-level tasks is not good (why I love Rust). &lt;a href="https://arxiv.org/abs/2203.11115" rel="noopener noreferrer"&gt;JS/TS&lt;/a&gt; provides hallucination-level abstraction tooling. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  #3 Reality: Teams synchronization and higher button cost.
&lt;/h4&gt;

&lt;p&gt;That depends on the size of your team or company. I worked in small to medium-sized companies, where adding basic functionality is usually a low-cost operation.&lt;/p&gt;

&lt;p&gt;However, I’m hearing stories from my corporate friends all the time about how their work and releases are blocked by multi-level endpoint approval processes (reason #1) and teams syncing (everybody has their priorities).&lt;/p&gt;

&lt;p&gt;Anyway, with the growth, an increase in feature cost is unavoidable.&lt;br&gt;&lt;br&gt;
But that makes the fact that you need to do the same thing in two different projections (app and API) even more frustrating.&lt;/p&gt;

&lt;h2&gt;
  
  
  It’s Just Skill Issues.
&lt;/h2&gt;

&lt;p&gt;Maybe. And I am an impostor.&lt;br&gt;
So don’t subscribe and miss &lt;a href="https://dev.to/derstruct/htmx-is-worse-than-react-and-websocket-is-obsolete-c8d"&gt;Part 2: The Solution&lt;/a&gt; (it's going to be technical)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>architecture</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Fri, 04 Jul 2025 17:00:19 +0000</pubDate>
      <link>https://dev.to/derstruct/-pac</link>
      <guid>https://dev.to/derstruct/-pac</guid>
      <description></description>
    </item>
  </channel>
</rss>
