<?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: Den Odell</title>
    <description>The latest articles on DEV Community by Den Odell (@denodell).</description>
    <link>https://dev.to/denodell</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%2F1401586%2Fc52fc332-02c3-4add-8e1b-06f3dff781e8.png</url>
      <title>DEV Community: Den Odell</title>
      <link>https://dev.to/denodell</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/denodell"/>
    <language>en</language>
    <item>
      <title>You're Looking at the Wrong Pretext Demo</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Mon, 30 Mar 2026 09:29:00 +0000</pubDate>
      <link>https://dev.to/denodell/youre-looking-at-the-wrong-pretext-demo-4960</link>
      <guid>https://dev.to/denodell/youre-looking-at-the-wrong-pretext-demo-4960</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/chenglou/pretext" rel="noopener noreferrer"&gt;Pretext&lt;/a&gt;, a new JavaScript library from Cheng Lou, crossed 7,000 GitHub stars in its first three days. If you've been anywhere near the frontend engineering circles in that time, you've seen the demos: &lt;a href="https://pretext-playground.builderz.dev" rel="noopener noreferrer"&gt;a dragon that parts text like water&lt;/a&gt;, &lt;a href="https://somnai-dreams.github.io/pretext-demos/fluid-smoke.html" rel="noopener noreferrer"&gt;fluid smoke rendered as typographic ASCII&lt;/a&gt;, &lt;a href="https://somnai-dreams.github.io/pretext-demos/wireframe-torus.html" rel="noopener noreferrer"&gt;a wireframe torus drawn through a character grid&lt;/a&gt;, &lt;a href="https://somnai-dreams.github.io/pretext-demos/the-editorial-engine.html" rel="noopener noreferrer"&gt;multi-column editorial layouts with animated orbs displacing text at 60fps&lt;/a&gt;. These are visually stunning and they're why the library went viral.&lt;/p&gt;

&lt;p&gt;But they aren't the reason this library matters.&lt;/p&gt;




&lt;p&gt;The important thing Pretext does is predict the height of a block of text without ever reading from the DOM. This means you can position text nodes without triggering a single layout recalculation. The text stays in the DOM, so screen readers can read it and users can select it, copy it, and translate it. The accessibility tree remains intact, the performance gain is real, and the user experience is preserved for everyone. This is the feature that will change how production web applications handle text, and it's the feature almost nobody is demonstrating.&lt;/p&gt;

&lt;p&gt;The community has spent three days building dragons. It should be building chat interfaces. And the fact that the dragons went viral while the measurement engine went unnoticed tells us something important about how the frontend community evaluates tools: we optimize for what we can see, not for what matters most to the people using what we build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Pretext Solves
&lt;/h2&gt;

&lt;p&gt;The problem is forced layout recalculation, where the browser has to pause and re-measure the page layout before it can continue. When a UI component needs to know the height of a block of text, the standard approach is to measure it from the DOM. You call &lt;code&gt;getBoundingClientRect()&lt;/code&gt; or read &lt;code&gt;offsetHeight&lt;/code&gt;, and the browser synchronously calculates layout to give you an answer. Do this for 500 text blocks in a virtual list and you've forced 500 of these pauses. This pattern, called &lt;em&gt;layout thrashing&lt;/em&gt;, remains a leading cause of visual stuttering in complex web applications.&lt;/p&gt;

&lt;p&gt;Pretext's insight is that &lt;code&gt;canvas.measureText()&lt;/code&gt; uses the same font engine as DOM rendering but operates outside the browser's layout process entirely. Measure a word via canvas, cache the width, and from that point forward layout becomes pure arithmetic: walk cached widths, track running line width, and insert breaks when you exceed the container's maximum. No slow measurement reads, and no synchronous pauses.&lt;/p&gt;

&lt;p&gt;The architecture separates this into two phases. &lt;code&gt;prepare()&lt;/code&gt; does the expensive work once: normalize whitespace, segment the text using &lt;code&gt;Intl.Segmenter&lt;/code&gt; for locale-aware word boundaries, handle bidirectional text (such as mixing English and Arabic), measure segments with canvas, and return a reusable reference. &lt;code&gt;layout()&lt;/code&gt; is then pure calculation over cached widths, taking about 0.09ms for a 500-text batch against roughly 19ms for &lt;code&gt;prepare()&lt;/code&gt;. Cheng Lou himself calls the 500x comparison "unfair" since it excludes the one-time &lt;code&gt;prepare()&lt;/code&gt; cost, but that cost is only paid once and spread across every subsequent call. It runs once when the text appears, and every subsequent resize takes the fast path, where the performance boost is real and substantial.&lt;/p&gt;

&lt;p&gt;The core idea traces back to Sebastian Markbage's research at Meta, where Cheng Lou implemented the &lt;a href="https://github.com/chenglou/text-layout" rel="noopener noreferrer"&gt;earlier &lt;code&gt;text-layout&lt;/code&gt; prototype&lt;/a&gt; that proved canvas font metrics could substitute for DOM measurement. Pretext builds on that foundation with production-grade internationalization, bidirectional text support, and the two-phase architecture that makes the fast path so fast. Lou has a track record here: &lt;a href="https://github.com/chenglou/react-motion" rel="noopener noreferrer"&gt;react-motion&lt;/a&gt; and ReasonML both followed the same pattern of identifying a constraint everyone accepted as given and removing it with a better abstraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Measurement Breakthrough
&lt;/h2&gt;

&lt;p&gt;The first use case Pretext serves, and the one I want to make the case for, is measuring text height so you can render DOM text nodes in exactly the right position without ever asking the browser how tall they are. This isn't a compromise path, it's the most capable thing the library does.&lt;/p&gt;

&lt;p&gt;Consider a virtual scrolling list of 500 chat messages. To render only the visible ones, you need to know each message's height before it enters the viewport. The traditional approach is to insert the text into the DOM, measure it, and then position it, paying the layout cost for every message. Pretext lets you predict the height mathematically and then render the text node at the right position. The text itself still lives in the DOM, so the accessibility model, selection behavior, and find-in-page all work exactly as they would with any other text node.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in practice:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prepared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;16px Inter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&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;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prepared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;containerWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two function calls: the first measures and caches, the second predicts height through calculation. No layout cost, yet the text you render afterward is a standard DOM node with full accessibility.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chenglou.me/pretext/bubbles/" rel="noopener noreferrer"&gt;The shrinkwrap demo&lt;/a&gt; is the clearest example of why this path matters. CSS &lt;code&gt;width: fit-content&lt;/code&gt; sizes a container to the widest wrapped line, which wastes space when the last line is short. There's no CSS property that says "find the narrowest width that still wraps to exactly N lines." Pretext's &lt;code&gt;walkLineRanges()&lt;/code&gt; calculates the optimal width mathematically, and the result is a tighter chat bubble rendered as a standard DOM text node. The performance gain comes from smarter measurement, not from abandoning the DOM. Nothing about the text changes for the end user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chenglou.me/pretext/accordion/" rel="noopener noreferrer"&gt;Accordion sections&lt;/a&gt; whose heights are calculated from Pretext, and &lt;a href="https://chenglou.me/pretext/masonry/" rel="noopener noreferrer"&gt;masonry layouts&lt;/a&gt; with height prediction instead of DOM reads: these both follow the same model of fast measurement feeding into standard DOM rendering.&lt;/p&gt;

&lt;p&gt;There are edge cases worth knowing about, starting with the fact that the prediction is only as accurate as the font metrics available at measurement time, so fonts need to be loaded before &lt;code&gt;prepare()&lt;/code&gt; runs or results will drift. Ligatures (where two characters merge into one glyph, like "fi"), advanced font features, and certain &lt;a href="https://en.wikipedia.org/wiki/CJK_characters" rel="noopener noreferrer"&gt;CJK&lt;/a&gt; composition rules can introduce tiny differences between canvas measurement and DOM rendering. These are solvable problems and the library handles many of them already, but acknowledging them is part of taking the approach seriously rather than treating it as magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canvas Paints Pixels, Not Accessible Text
&lt;/h2&gt;

&lt;p&gt;Pretext also supports manual line layout for rendering to Canvas, SVG, or WebGL. These APIs give you exact line coordinates so you can paint text yourself rather than letting the DOM handle it. This is the path that went viral, and the one that dominates every community showcase.&lt;/p&gt;

&lt;p&gt;The canvas demos are impressive and they're doing things the DOM genuinely can't do at 60fps. But they're also painting pixels, and when you paint text as canvas pixels, the browser has no idea those pixels represent language. Screen readers like VoiceOver, NVDA, and JAWS derive their understanding of a page from the accessibility tree, which is itself built from the DOM, so canvas content is invisible to them. Browser find-in-page and translation tools both skip canvas pixels entirely. Native text selection is tied to DOM text nodes and canvas has no equivalent, so users can't select, copy, or navigate the content by keyboard. A &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element is also a single tab stop, meaning keyboard users can't move between individual words or paragraphs within it, even if it contains thousands of words. In short, everything that makes text behave as text rather than an image of text disappears.&lt;/p&gt;

&lt;p&gt;None of this means the canvas path is automatically wrong. There are legitimate contexts where canvas text rendering is the right choice: games, data visualizations, creative installations, and design tools that have invested years in building their own accessibility layer on top of canvas. For SVG rendering, the trade-offs are different again, since SVG text elements do participate in the accessibility tree, making it a middle ground between DOM and canvas.&lt;/p&gt;

&lt;p&gt;But the canvas path is not the breakthrough, because canvas text rendering has existed for fifteen or more years across dozens of libraries. What none of them offered was a way to predict DOM text layout without paying the layout cost. Pretext's &lt;code&gt;prepare()&lt;/code&gt; and &lt;code&gt;layout()&lt;/code&gt; do exactly that, and it's genuinely new.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wrong Demos Often Go Viral
&lt;/h2&gt;

&lt;p&gt;This pattern often repeats across the frontend ecosystem, and I understand why.&lt;/p&gt;

&lt;p&gt;A dragon parting text like water is something you can record as a GIF, post to your socials, and collect thousands of impressions. A virtual scrolling list that pre-calculates text heights looks identical to one that doesn't. The performance difference is substantial but invisible to the eye. Nobody makes a showcase called "works flawlessly with VoiceOver" or "scrolls 10,000 messages without a single forced layout" because these things look like nothing. They look like a web page working the way web pages are supposed to work.&lt;/p&gt;

&lt;p&gt;This is &lt;a href="https://en.wikipedia.org/wiki/Goodhart%27s_law" rel="noopener noreferrer"&gt;Goodhart's Law&lt;/a&gt; applied to web performance: once a metric becomes a target, it ceases to be a good measure. Frame rate and layout cost are proxies for "does this work well for users." GitHub stars are a proxy for "is this useful." When the proxy gets optimized instead, in this case by visually impressive demos that happen to use the path with the steepest accessibility trade-offs, the actual signal about what makes the library important gets lost. The library's identity gets set by its most visually impressive feature in the first 72 hours, and the framing becomes "I am drawing things" rather than "I am measuring things faster than anyone has before." Once that framing is set, it's hard to shift.&lt;/p&gt;

&lt;p&gt;The best text-editing libraries on the web, &lt;a href="https://codemirror.net" rel="noopener noreferrer"&gt;CodeMirror&lt;/a&gt;, &lt;a href="https://microsoft.github.io/monaco-editor/" rel="noopener noreferrer"&gt;Monaco&lt;/a&gt;, and &lt;a href="https://prosemirror.net" rel="noopener noreferrer"&gt;ProseMirror&lt;/a&gt;, all made the deliberate choice to stay in the DOM even when leaving it would have been faster, because the accessibility model isn't optional. Pretext's DOM measurement path belongs in that tradition but goes further: those editors still read from the DOM when they need to know how tall something is. Pretext eliminates that step entirely, predicting height through arithmetic before the node is ever rendered. It's the next logical step in the same philosophy: keep text where it belongs, but stop paying the measurement cost to do so.&lt;/p&gt;



&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;I've been thinking about performance engineering as a discipline for most of my career, and what strikes me about Pretext is that the real innovation is the one that is hardest to see. Predicting how text will lay out before it reaches the page, while keeping the text in the DOM and preserving everything that makes it accessible, is a genuinely new capability on the web platform. It's the kind of foundational improvement that every complex text-heavy application can adopt immediately.&lt;/p&gt;

&lt;p&gt;If you're reaching for Pretext this week, reach for &lt;code&gt;prepare()&lt;/code&gt; and &lt;code&gt;layout()&lt;/code&gt; first. Build something that keeps text in the DOM and predicts its height without asking the browser. Ship an interface that every user can read, select, search, and navigate. Nobody else has done this yet, and it deserves building.&lt;/p&gt;

&lt;p&gt;Performance engineering is at its best when it serves everyone without asking anyone to give something up. Faster frame rates that don't make someone nauseous. Fewer layout pauses that mean a page responds when someone with motor difficulties needs it to. Text that is fast and readable and selectable and translatable and navigable by keyboard and comprehensible to a screen reader.&lt;/p&gt;

&lt;p&gt;The dragons are fun. The measurement engine is important. Let's try not to confuse the two.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Constraints and the Lost Art of Optimization</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Mon, 23 Feb 2026 01:00:00 +0000</pubDate>
      <link>https://dev.to/denodell/constraints-and-the-lost-art-of-optimization-4f6m</link>
      <guid>https://dev.to/denodell/constraints-and-the-lost-art-of-optimization-4f6m</guid>
      <description>&lt;p&gt;In 1984, Steve Jobs walked over to a bag standing on stage and &lt;a href="https://youtu.be/1tQ5XwvjPmA?si=Hahif7zg8E5QSRrX&amp;amp;t=487" rel="noopener noreferrer"&gt;pulled out a computer&lt;/a&gt; that would change the world. The Macintosh had an operating system, a graphical user interface, window manager, font renderer, and a complete graphics engine called QuickDraw, one of the most elegant pieces of software ever written.&lt;/p&gt;

&lt;p&gt;The whole thing fit inside the machine’s 64KB ROM.&lt;br&gt;
&lt;em&gt;Sixty&lt;/em&gt;. &lt;em&gt;Four&lt;/em&gt;. &lt;em&gt;Kilobytes&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A typical webpage hero image is larger. A "Hello World" React application is larger. The entire intellectual and creative output of a team that reinvented personal computing fits in a space that, today, we wouldn’t think twice about wasting on a single font file.&lt;/p&gt;

&lt;p&gt;They did it because there just wasn’t any other choice. A larger ROM would have been too expensive for a mass-market consumer device, so efficiency and hyper-optimization was the only route.&lt;/p&gt;

&lt;p&gt;Somewhere in the years that followed we’ve lost the creative solutions, the art of optimization, that being constrained in that way produces.&lt;/p&gt;




&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Atari_2600" rel="noopener noreferrer"&gt;Atari 2600 game console&lt;/a&gt; had 4KB of ROM and 128 bytes of RAM. Not kilobytes… &lt;em&gt;bytes&lt;/em&gt;. And because there was no display buffer, programmers had to manually synchronize their code with the connected TV’s electron beam as it swept across the screen, line by line, sixty times a second. If your code ran too slow, the image tore. If it ran too fast, you’d corrupt the next line.&lt;/p&gt;

&lt;p&gt;They called it "&lt;a href="https://en.wikipedia.org/wiki/Racing_the_Beam" rel="noopener noreferrer"&gt;racing the beam&lt;/a&gt;." It was brutal, unforgiving, and it forced some of the most inventive programming in computing history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mario.nintendo.com/history/" rel="noopener noreferrer"&gt;Super Mario Bros&lt;/a&gt; shipped on a 40KB NES cartridge. Tetris for the Nintendo Game Boy, the most successful handheld game ever made at the time, &lt;a href="https://github.com/kaspermeerts/tetris" rel="noopener noreferrer"&gt;was 32KB&lt;/a&gt;. These weren’t compromised experiences, they were masterpieces. The constraints didn’t diminish the work, they actually defined it.&lt;/p&gt;

&lt;p&gt;The programmers who built these things weren’t just efficient, they thought differently. They knew their medium the way a sculptor knows stone. They understood every byte, every clock cycle, and every trade-off. The machine had no secrets from them because it couldn’t afford to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Abundant, Cheap, Fast, and Powerful
&lt;/h2&gt;

&lt;p&gt;Modern development is defined by abundance. Cheap storage, fast networks, and powerful hardware. The practical consequences of inefficiency have largely disappeared. A few extra megabytes, a few wasted milliseconds, an unnecessary UI re-render. For the most part, nobody notices and nothing breaks.&lt;/p&gt;

&lt;p&gt;And so we’ve stopped noticing ourselves.&lt;/p&gt;

&lt;p&gt;This isn’t out of laziness, it’s just how rational people work. When the hard limits disappear, the thinking they demanded tends to disappear with them. There’s no forcing function, no electron beam to race, no 128 bytes standing between your idea and disaster. We can afford &lt;em&gt;not&lt;/em&gt; to understand, and so increasingly, we simply don’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Can We Learn From The Past?
&lt;/h2&gt;

&lt;p&gt;Here’s what I think is worth recovering: not the constraints themselves, but the &lt;em&gt;relationship&lt;/em&gt; with the medium that having those constraints produced.&lt;/p&gt;

&lt;p&gt;The engineers who wrote the Mac ROM didn’t just know how to be efficient, they understood their problem at a level that made elegance possible. &lt;a href="https://computerhistory.org/blog/macpaint-and-quickdraw-source-code/" rel="noopener noreferrer"&gt;Bill Atkinson’s QuickDraw&lt;/a&gt; wasn’t just small, it was a beautiful piece of code. The 64KB forced him to find the &lt;em&gt;right&lt;/em&gt; solution, not just a working one.&lt;/p&gt;

&lt;p&gt;That instinct, to understand deeply before you build, to ask whether this is the right structure and not just a functional one, to treat your medium as something to be understood rather than merely used, that’s the transferable thing. Not the bit-twiddling, the &lt;em&gt;thinking&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The best engineers I’ve worked with carry this instinct even when others might think it crazy. They impose their own constraints. They ask what this would look like if it had to be half the size, or run twice as fast, or use a tenth of the memory. Not because anyone demanded it, but because just by thinking there could be a better, more efficient solution, one often emerges.&lt;/p&gt;

&lt;p&gt;If you want to start developing this instinct today, the most valuable thing you can do is learn how your runtime actually works. Not at the API level, but internally. How your platform parses, allocates, renders, and executes. For web developers that means &lt;a href="https://howbrowserswork.com/" rel="noopener noreferrer"&gt;understanding the browser pipeline&lt;/a&gt;: parsing, style resolution, layout, paint, and compositing. For mobile developers it means understanding how iOS or Android manages memory, handles drawing, and schedules work. Understanding your platform changes what you notice, what makes you wince, and what you reach for instinctively. The engineers who built the Mac knew their domain completely, and you can know yours too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fast by Default
&lt;/h2&gt;

&lt;p&gt;There’s a principle I keep coming back to in engineering apps for performance: &lt;a href="https://fastbydefault.com/" rel="noopener noreferrer"&gt;&lt;em&gt;fast by default&lt;/em&gt;&lt;/a&gt;. Not fast because you optimized after the fact, but fast because the thinking that produces fast software is simply &lt;em&gt;better thinking&lt;/em&gt;. It catches unnecessary complexity early, and it produces systems that are easier to understand, easier to change, and easier to reason about under pressure.&lt;/p&gt;

&lt;p&gt;The Atari programmers were fast by default; they had no choice. But the discipline they practiced, that intimate, demanding relationship with their constraints, that’s a choice we can still make.&lt;/p&gt;

&lt;p&gt;The 64KB Mac ROM isn’t just a remarkable footnote, it’s a provocation. It asks: if they could do &lt;em&gt;that&lt;/em&gt; with &lt;em&gt;that&lt;/em&gt;, then what’s our excuse?&lt;/p&gt;

&lt;p&gt;Not to shame us, but to remind us that constraints aren’t the enemy of great work.&lt;br&gt;
&lt;em&gt;They’re often the source of it&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>optimization</category>
      <category>history</category>
    </item>
    <item>
      <title>The Main Thread Is Not Yours</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Thu, 08 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/denodell/the-main-thread-is-not-yours-khl</link>
      <guid>https://dev.to/denodell/the-main-thread-is-not-yours-khl</guid>
      <description>&lt;p&gt;When a user visits your site or app, their browser dedicates a single thread to running your JavaScript, handling their interactions, and painting what they see on the screen. This is the &lt;em&gt;main thread&lt;/em&gt;, and it’s the direct link between your code and the person using it.&lt;/p&gt;

&lt;p&gt;As developers, we often use it without considering the end user and their device, which could be anything from a mid-range phone to a high-end gaming rig. We don’t think about the fact that the main thread doesn’t belong to us; it belongs to them.&lt;/p&gt;

&lt;p&gt;I’ve watched this mistake get repeated for years: we burn through the user’s main thread budget as if it were free, and then act surprised when the interface feels broken.&lt;/p&gt;




&lt;p&gt;Every millisecond you spend executing JavaScript is a millisecond the browser can’t spend responding to a click, updating a scroll position, or acknowledging that the user did just try to type something. When your code runs long, you’re not causing "jank" in some abstract technical sense; you’re ignoring someone who’s trying to talk to you.&lt;/p&gt;

&lt;p&gt;Because the main thread can only do one thing at a time, everything else waits while your JavaScript executes: clicks queue up, scrolls freeze, and keystrokes pile up hoping you’ll finish soon. If your code takes 50ms to respond nobody notices, but at 500ms the interface starts feeling sluggish, and after several seconds the browser may offer to kill your page entirely.&lt;/p&gt;

&lt;p&gt;Users don’t know why the interface isn’t responding. They don’t see your code executing; they just see a broken experience and blame themselves, then the browser, then you, in that order.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 200ms You Don’t Own
&lt;/h2&gt;

&lt;p&gt;Browser vendors have spent years studying how humans perceive responsiveness, and the research converged on a threshold: respond to user input within 100ms and the interaction feels instant, push past 200ms and users notice the delay. The industry formalized this as the &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Interaction_to_next_paint" rel="noopener noreferrer"&gt;Interaction to Next Paint (INP) metric&lt;/a&gt;, where anything over 200ms is considered poor and now affects your search rankings.&lt;/p&gt;

&lt;p&gt;But that 200ms budget isn’t just for your JavaScript. The browser needs time for style calculations, layout, and painting, so your code gets what’s left: maybe 50ms per interaction before things start feeling wrong. That’s your allocation from a resource you don’t own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Platform Has Your Back
&lt;/h2&gt;

&lt;p&gt;The web platform has evolved specifically to help you be a better guest on the main thread, and many of these APIs exist because browser engineers got tired of watching developers block the thread unnecessarily.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;Web Workers&lt;/a&gt; let you run JavaScript in a completely separate thread. Heavy computation, whether parsing large datasets, image processing, or complex calculations, can happen in a Worker without blocking the main thread at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Main thread: delegate work and stay responsive
const worker = new Worker('heavy-lifting.js');

// Send a large dataset from the main thread to the worker
// The worker then processes it in its own thread
worker.postMessage(largeDataset);

// Receive results back and update the UI
worker.onmessage = (e) =&amp;gt; updateUI(e.data);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Workers can’t touch the DOM, but that constraint is deliberate since it forces a clean separation between "work" and "interaction."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback" rel="noopener noreferrer"&gt;requestIdleCallback&lt;/a&gt; lets you run code only when the browser has nothing better to do. (Due to a &lt;a href="https://bugs.webkit.org/show_bug.cgi?id=285049" rel="noopener noreferrer"&gt;WebKit bug&lt;/a&gt;, Safari support is still pending at time of writing.) When the user is actively interacting, your callback waits; when things are quiet, your code gets a turn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requestIdleCallback((deadline) =&amp;gt; {

  // Process your tasks from a queue you created earlier
  // deadline.timeRemaining() tells you how much time you have left
  while (tasks.length &amp;amp;&amp;amp; deadline.timeRemaining() &amp;gt; 0) {
    processTask(tasks.shift());
  }

  // If there are tasks left, schedule another idle callback to complete later
  if (tasks.length) requestIdleCallback(processRemainingTasks);
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is ideal for non-urgent work like analytics, pre-fetching, or background updates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Scheduling/isInputPending" rel="noopener noreferrer"&gt;isInputPending&lt;/a&gt; (Chromium-only for now) is perhaps the most user-respecting API of the lot, because it lets you check mid-task whether someone is waiting for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function processChunk(items) {

  // Process items from a queue one at a time
  while (items.length) {
    processItem(items.shift());

    // Check if there’s pending input from the user
    if (navigator.scheduling?.isInputPending()) {

      // Yield to the main thread to handle user input,
      // then resume processing after
      setTimeout(() =&amp;gt; processChunk(items), 0);

      // Stop processing for now
      return;
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’re explicitly asking "is someone trying to get my attention?" and if the answer is yes, you stop and let them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Subtle Offenders
&lt;/h2&gt;

&lt;p&gt;The obvious main thread crimes like infinite loops or rendering 100,000 table rows are easy to spot, but the subtle ones look harmless.&lt;/p&gt;

&lt;p&gt;Calling &lt;code&gt;JSON.parse()&lt;/code&gt;, for example, on a large API response blocks the main thread until parsing completes, and while this feels instant on a developer’s machine, a mid-range phone with a throttled CPU and competing browser tabs might take 300ms to finish the same operation, ignoring the user’s interactions the whole time.&lt;/p&gt;

&lt;p&gt;The main thread doesn’t degrade gracefully; it’s either responsive or it isn’t, and your users are running your code in conditions you’ve probably never tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure What You Spend
&lt;/h2&gt;

&lt;p&gt;You can’t manage what you can’t measure, and Chrome DevTools’ Performance panel shows exactly where your main thread time goes if you know where to look. Find the "Main" track and watch for long yellow blocks of JavaScript execution. Tasks exceeding 50ms get flagged with red shading to mark the overtime portion. Use the Insights pane to surface these automatically if you prefer a guided approach. For more precise instrumentation, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure" rel="noopener noreferrer"&gt;&lt;code&gt;performance.measure()&lt;/code&gt;&lt;/a&gt; API lets you time specific operations in your own code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Mark the start of a heavy operation
performance.mark('parse-start');

// The operation you want to measure
const data = JSON.parse(hugePayload);

// Mark the end of the operation
performance.mark('parse-end');

// Name the measurement for later analysis
performance.measure('json-parse', 'parse-start', 'parse-end');

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/GoogleChrome/web-vitals" rel="noopener noreferrer"&gt;Web Vitals library&lt;/a&gt; can capture INP scores from real users across all major browsers in production, and when you see spikes you’ll know where to start investigating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Framework Tax
&lt;/h2&gt;

&lt;p&gt;Before your application code runs a single line, your framework has already spent some of the user’s main thread budget on initialization, hydration, and virtual DOM reconciliation.&lt;/p&gt;

&lt;p&gt;This isn’t an argument against frameworks so much as an argument for understanding what you’re spending. A framework that costs 200ms to hydrate has consumed four times your per-interaction budget before you’ve done anything, and that needs to be a conscious choice you’re making, rather than an accident.&lt;/p&gt;

&lt;p&gt;Some frameworks have started taking this seriously: &lt;a href="https://qwik.dev/" rel="noopener noreferrer"&gt;Qwik’s&lt;/a&gt; "resumability" avoids hydration entirely, while &lt;a href="https://react.dev/blog/2022/03/29/react-v18" rel="noopener noreferrer"&gt;React’s concurrent features&lt;/a&gt; let rendering yield to user input. These are all responses to the same fundamental constraint, which is that the main thread is finite and we’ve been spending it carelessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Borrow, Don’t Take
&lt;/h2&gt;

&lt;p&gt;The technical solutions matter, but they follow from a shift in perspective, and when I finally internalized that the main thread belongs to the user, not to me, my own decisions started to change.&lt;/p&gt;

&lt;p&gt;Performance stops being about how fast your code executes and starts being about how responsive the interface stays while your code executes. Blocking the main thread stops being an implementation detail and starts feeling like taking something that isn’t yours.&lt;/p&gt;

&lt;p&gt;The browser gave us a single thread of execution, and it gave our users that same thread for interacting with what we built. The least we can do is share it fairly.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Escape Velocity: Break Free from Framework Gravity</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Wed, 05 Nov 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/denodell/escape-velocity-break-free-from-framework-gravity-3e3o</link>
      <guid>https://dev.to/denodell/escape-velocity-break-free-from-framework-gravity-3e3o</guid>
      <description>&lt;p&gt;Frameworks were supposed to free us from the messy parts of the web. For a while they did, until their gravity started drawing everything else into orbit.&lt;/p&gt;

&lt;p&gt;Every framework brought with it real progress. React, Vue, Angular, Svelte, and others all gave structure, composability, and predictability to frontend work. But now, after a decade of React dominance, something else has happened. We haven’t just built apps with React, we’ve built an entire ecosystem around it—hiring pipelines, design systems, even companies—all bound to its way of thinking.&lt;/p&gt;

&lt;p&gt;The problem isn’t React itself, nor any other framework for that matter. The problem is the inertia that sets in once any framework becomes infrastructure. By that point, it’s “too important to fail,” and everything nearby turns out to be just fragile enough to prove it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Gravity of Success
&lt;/h2&gt;

&lt;p&gt;React is no longer just a library. It’s a full ecosystem that defines how frontend developers are allowed to think.&lt;/p&gt;

&lt;p&gt;Its success has created its own kind of gravity, and the more we’ve built within it, the harder it’s become to break free.&lt;/p&gt;

&lt;p&gt;Teams standardize on it because it’s safe: it’s been proven to work at massive scale, the talent pool is large, and the tooling is mature. That’s a rational choice, but it also means React exerts institutional gravity. Moving off it stops being an engineering decision and becomes an organizational risk instead. Solutions to problems tend to be found within its orbit, because stepping outside it feels like drifting into deep space.&lt;/p&gt;

&lt;p&gt;We saw this cycle with jQuery in the past, and we’re seeing it again now with React. We’ll see it with whatever comes next. Success breeds standardization, standardization breeds inertia, and inertia convinces us that progress can wait. It’s the pattern itself that’s the problem, not any single framework.&lt;/p&gt;

&lt;p&gt;But right now, React sits at the center of this dynamic, and the stakes are far higher than they ever were with jQuery. Entire product lines, architectural decisions, and career paths now depend on React-shaped assumptions. We’ve even started defining developers by their framework: many job listings ask for “React developers” instead of frontend engineers. Even AI coding agents default to React when asked to start a new frontend project, unless deliberately steered elsewhere.&lt;/p&gt;

&lt;p&gt;Perhaps the only thing harder than building on a framework is admitting you might need to build without one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evolution and Innovation
&lt;/h2&gt;

&lt;p&gt;React’s evolution captures this tension perfectly. Recent milestones include the &lt;a href="https://react.dev/blog/2025/10/07/introducing-the-react-foundation" rel="noopener noreferrer"&gt;creation of the React Foundation&lt;/a&gt;, the &lt;a href="https://react.dev/learn/react-compiler" rel="noopener noreferrer"&gt;React Compiler reaching v1.0&lt;/a&gt;, and &lt;a href="https://react.dev/blog/2025/10/01/react-19-2" rel="noopener noreferrer"&gt;new additions in React 19.2&lt;/a&gt; such as the &lt;code&gt;&amp;lt;Activity /&amp;gt;&lt;/code&gt; component, &lt;code&gt;useEffectEvent&lt;/code&gt;, Performance Tracks in DevTools, and experimental &lt;code&gt;&amp;lt;ViewTransition /&amp;gt;&lt;/code&gt; and Fragment Refs.&lt;/p&gt;

&lt;p&gt;These updates represent tangible improvements. Especially the compiler, which brings automatic memoization at build time, eliminating the need for manual &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt; optimization. Production deployments show real performance wins using it: apps in the Meta Quest Store saw up to 2.5x faster interactions as a direct result. This kind of automatic optimization is genuinely valuable work that pushes the entire ecosystem forward.&lt;/p&gt;

&lt;p&gt;But here’s the thing: the web platform has been quietly heading in the same direction for years, building many of the same capabilities frameworks have been racing to add.&lt;/p&gt;

&lt;p&gt;Browsers now ship View Transitions, Container Queries, and smarter scheduling primitives. The platform keeps evolving at a fair pace, but most teams won’t touch these capabilities until React officially wraps them in a hook or they show up in Next.js docs.&lt;/p&gt;

&lt;p&gt;Innovation keeps happening right across the ecosystem, but for many it only becomes “real” once React validates the approach. Which is fine, assuming you enjoy waiting for permission to use the platform you’re already building on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The React Foundation and the Question of Priorities
&lt;/h2&gt;

&lt;p&gt;The React Foundation represents an important milestone for governance and sustainability. This new foundation is a part of the Linux Foundation, and founding members include Meta, Vercel, Microsoft, Amazon, Expo, Callstack, and Software Mansion.&lt;/p&gt;

&lt;p&gt;This is genuinely good for React’s long-term health, providing better governance and removing the risk of being owned by a single company. It ensures React can outlive any one organization’s priorities.&lt;/p&gt;

&lt;p&gt;But it doesn’t fundamentally change the development dynamic of the framework. Yet.&lt;/p&gt;

&lt;p&gt;The engineers who actually build React still work at companies like Meta and Vercel. The research still happens at that scale, driven by those performance needs. The roadmap still reflects the priorities of the companies that fund full-time development.&lt;/p&gt;

&lt;p&gt;And to be fair, React operates at a scale most frameworks will never encounter. Meta serves billions of users through frontends that run on constrained mobile devices around the world, so it needs performance at a level that justifies dedicated research teams. The innovations they produce, including compiler-driven optimization, concurrent rendering, and increasingly fine-grained performance tooling, solve real problems that exist only at that kind of massive scale.&lt;/p&gt;

&lt;p&gt;But those priorities aren’t necessarily &lt;em&gt;your&lt;/em&gt; priorities, and that’s the tension. React’s innovations are shaped by the problems faced by companies running apps at billions-of-users scale, not necessarily the problems faced by teams building for thousands or millions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signals in the Lab: Research at Enterprise Scale
&lt;/h2&gt;

&lt;p&gt;React’s internal research reveals the team’s awareness of current architectural limitations. Experimental projects like &lt;strong&gt;Forest&lt;/strong&gt; explore signal-like lazy computation graphs; essentially fine-grained reactivity instead of React’s coarse re-render model. Another project, &lt;strong&gt;Fir&lt;/strong&gt; , investigates incremental rendering techniques.&lt;/p&gt;

&lt;p&gt;These aren’t roadmap items; they’re just research prototypes happening inside Meta. They may never ship publicly. But they do reveal something important: React’s team knows the virtual DOM model has performance ceilings and they’re actively exploring what comes after it.&lt;/p&gt;

&lt;p&gt;This is good research, but it also illustrates the same dynamic at play again: that these explorations happen behind the walls of Big Tech, on timelines set by corporate priorities and resource availability. Meanwhile, frameworks like Solid and Qwik have been shipping production-ready fine-grained reactivity for years. Svelte 5 shipped runes in 2024, bringing signals to mainstream adoption.&lt;/p&gt;

&lt;p&gt;The gap isn’t technical capability, but rather when the industry feels permission to adopt it. For many teams, that permission only comes once React validates the approach. This is true regardless of who governs the project or what else exists in the ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What React Got Right
&lt;/h2&gt;

&lt;p&gt;I don’t want this critique to take away from what React has achieved over the past twelve years.&lt;/p&gt;

&lt;p&gt;React popularized declarative UIs and made component-based architecture mainstream, which was a huge deal in itself. It proved that developer experience matters as much as runtime performance and introduced the idea that UI could be a pure function of input props and state. That shift made complex interfaces far easier to reason about. Later additions like hooks solved the earlier class component mess elegantly, and concurrent rendering through &lt;code&gt;&amp;lt;Suspense /&amp;gt;&lt;/code&gt; opened new possibilities for truly responsive UIs.&lt;/p&gt;

&lt;p&gt;The React team’s research into compiler optimization, server components, and fine-grained rendering pushes the entire ecosystem forward. This is true even when other frameworks ship similar ideas first. There’s value in seeing how these patterns work at Meta’s scale.&lt;/p&gt;

&lt;p&gt;The critique isn’t that React is bad, but that treating any single framework as infrastructure creates blind spots in how we think and build. When React becomes the lens through which we see the web, we stop noticing what the platform itself can already do, and we stop reaching for native solutions because we’re waiting for the framework-approved version to show up first.&lt;/p&gt;

&lt;p&gt;And crucially, switching to Solid, Svelte, or Vue wouldn’t eliminate this dynamic; it would only shift its center of gravity. Every framework creates its own orbit of tools, patterns, and dependencies. The goal isn’t to find the “right” framework, but to build applications resilient enough to survive migration to any framework, including those that haven’t been invented yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inertia and Cultural Limitations
&lt;/h2&gt;

&lt;p&gt;This inertia isn’t about laziness; it’s about logistics. Switching stacks is expensive and disruptive. Retraining developers, rebuilding component libraries, and retooling CI pipelines all take time and money, and the payoff is rarely immediate. It’s high risk, high cost, and hard to justify, so most companies stay put, and honestly, who can blame them?&lt;/p&gt;

&lt;p&gt;But while we stay put, the platform keeps moving. The browser can stream and hydrate progressively, animate transitions natively, and coordinate rendering work without a framework. Yet most development teams won’t touch those capabilities until they’re built in or officially blessed by the ecosystem.&lt;/p&gt;

&lt;p&gt;That isn’t an engineering limitation; it’s a cultural one. We’ve somehow made “works in all browsers” feel riskier than “works in our framework.”&lt;/p&gt;

&lt;p&gt;Better governance doesn’t solve this. The problem isn’t React’s organizational structure; it’s our relationship to it. Too many teams wait for React to package and approve platform capabilities before adopting them, even when those same features already exist in browsers today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;&amp;lt;Activity /&amp;gt;&lt;/code&gt; Component: Formalization vs. Innovation
&lt;/h2&gt;

&lt;p&gt;React 19.2’s &lt;code&gt;&amp;lt;Activity /&amp;gt;&lt;/code&gt; component captures this pattern perfectly. It serves as a boundary that hides UI while preserving component state and unmounting effects. When set to &lt;code&gt;mode="hidden"&lt;/code&gt;, it pauses subscriptions, timers, and network requests while keeping form inputs and scroll positions intact. When revealed again by setting &lt;code&gt;mode="visible"&lt;/code&gt;, those effects remount cleanly.&lt;/p&gt;

&lt;p&gt;It’s a genuinely useful feature. Tabbed interfaces, modals, and progressive rendering all benefit from it, and the same idea extends to cases where you want to pre-render content in the background or preserve state as users navigate between views. It integrates smoothly with React’s lifecycle and &lt;code&gt;&amp;lt;Suspense /&amp;gt;&lt;/code&gt; boundaries, enabling selective hydration and smarter rendering strategies.&lt;/p&gt;

&lt;p&gt;But it also draws an important line between &lt;em&gt;formalization&lt;/em&gt; and &lt;em&gt;innovation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The core concept isn’t new; it’s simply about pausing side effects while maintaining state. Similar behavior can already be built with visibility observers, effect cleanup, and careful state management patterns. The web platform even provides the primitives for it through tools like &lt;code&gt;IntersectionObserver&lt;/code&gt;, DOM state preservation, and manual effect control.&lt;/p&gt;

&lt;p&gt;What &lt;code&gt;&amp;lt;Activity /&amp;gt;&lt;/code&gt; adds is formalization and coordination with React’s internals. It’s easier to use, harder to misuse, and integrates cleanly with features like &lt;code&gt;&amp;lt;Suspense /&amp;gt;&lt;/code&gt;. Yet it also exposes how dependent our thinking has become on frameworks. We wait for React to formalize platform behaviors instead of reaching for them directly.&lt;/p&gt;

&lt;p&gt;This isn’t a criticism of &lt;code&gt;&amp;lt;Activity /&amp;gt;&lt;/code&gt; itself; it’s a well-designed API that solves a real problem. But it serves as a reminder that we’ve grown comfortable waiting for framework solutions to problems the platform already lets us solve. After orbiting React for so long, we’ve forgotten what it feels like to build without its pull.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform First Thinking
&lt;/h2&gt;

&lt;p&gt;The answer isn’t necessarily to abandon your framework, but to remember that it runs inside the web, not the other way around. I’ve written before about &lt;a href="https://denodell.com/blog/building-the-web-in-islands?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=escape_velocity_post" rel="noopener noreferrer"&gt;building the web in islands&lt;/a&gt; as one way to rediscover platform capabilities we already have.&lt;/p&gt;

&lt;p&gt;Even within React’s constraints, you can still think platform first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use native forms and form submissions to a server, then enhance with client-side logic&lt;/li&gt;
&lt;li&gt;Prefer semantic HTML and ARIA before reaching for component libraries&lt;/li&gt;
&lt;li&gt;Try &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API" rel="noopener noreferrer"&gt;View Transitions&lt;/a&gt; directly with minimal React wrappers instead of waiting for an official API&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components" rel="noopener noreferrer"&gt;Web Components&lt;/a&gt; for self-contained widgets that could survive a framework migration&lt;/li&gt;
&lt;li&gt;Keep business logic framework-agnostic, plain TypeScript modules rather than hooks, and aim to keep your hooks short by pulling logic from outside React&lt;/li&gt;
&lt;li&gt;Profile performance using browser DevTools first and React DevTools second&lt;/li&gt;
&lt;li&gt;Try native CSS features like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has" rel="noopener noreferrer"&gt;&lt;code&gt;:has()&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@layer" rel="noopener noreferrer"&gt;&lt;code&gt;@layer&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_scroll_snap" rel="noopener noreferrer"&gt;scroll snap&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries" rel="noopener noreferrer"&gt;&lt;code&gt;@container&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion" rel="noopener noreferrer"&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/a&gt; before adding JavaScript solutions&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noopener noreferrer"&gt;&lt;code&gt;fetch&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;&lt;code&gt;FormData&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams" rel="noopener noreferrer"&gt;&lt;code&gt;URLSearchParams&lt;/code&gt;&lt;/a&gt; instead of framework-specific alternatives wherever possible&lt;/li&gt;
&lt;li&gt;Experiment with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API" rel="noopener noreferrer"&gt;History API&lt;/a&gt; (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/History/pushState" rel="noopener noreferrer"&gt;&lt;code&gt;pushState&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event" rel="noopener noreferrer"&gt;&lt;code&gt;popstate&lt;/code&gt;&lt;/a&gt;) directly before reaching for React Router&lt;/li&gt;
&lt;li&gt;Structure code so routing, data fetching, and state management can be swapped out independently of React&lt;/li&gt;
&lt;li&gt;Test against real browser APIs and behaviors, not just framework abstractions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren’t anti-React practices, they’re portable practices that make your web app more resilient. They let you adopt new browser capabilities as soon as they ship, not months later when they’re wrapped in a hook. They make framework migration feasible rather than catastrophic.&lt;/p&gt;

&lt;p&gt;When you build this way, React becomes a rendering library that happens to be excellent at its job, not the foundation everything else has to depend on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freedom Beyond Frameworks
&lt;/h2&gt;

&lt;p&gt;A React app that respects the platform can outlast React itself.&lt;/p&gt;

&lt;p&gt;When you treat React as an implementation detail instead of an identity, your architecture becomes portable. When you embrace progressive enhancement and web semantics, your ideas survive the next framework wave.&lt;/p&gt;

&lt;p&gt;The recent wave of changes, including the React Foundation, React Compiler v1.0, the &lt;code&gt;&amp;lt;Activity /&amp;gt;&lt;/code&gt; component, and internal research into alternative architectures, all represent genuine progress. The React team is doing thoughtful work, but these updates also serve as reminders of how tightly the industry has become coupled to a single ecosystem’s timeline. That timeline is still dictated by the engineering priorities of large corporations, and that remains true regardless of who governs the project.&lt;/p&gt;

&lt;p&gt;If your team’s evolution depends on a single framework’s roadmap, you are not steering your product; you are waiting for permission to move.&lt;/p&gt;

&lt;p&gt;That is true whether you are using React, Vue, Angular, or Svelte. The framework does not matter; the dependency does. It is ironic that we spent years escaping jQuery’s gravity, only to end up caught in another orbit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reclaim the Platform
&lt;/h2&gt;

&lt;p&gt;React was once the radical idea that changed how we build for the web. Every successful framework reaches this point eventually, when it shifts from innovation to institution, from tool to assumption. jQuery did it, React did it, and something else will do it next.&lt;/p&gt;

&lt;p&gt;The React Foundation is a positive step for the project’s long-term sustainability, but the next real leap forward will not come from better governance. It will not come from React finally adopting signals either, and it will not come from any single framework “getting it right.”&lt;/p&gt;

&lt;p&gt;Progress will come from developers who remember that frameworks are implementation details, not identities.&lt;/p&gt;

&lt;p&gt;Build for the platform first.&lt;br&gt;
Choose frameworks second.&lt;/p&gt;

&lt;p&gt;The web isn’t React’s, it isn’t Vue’s, and it isn’t Svelte’s. It belongs to no one. If we remember that, it will stay free to evolve at its own pace, drawing the best ideas from everywhere rather than from whichever framework happens to hold the cultural high ground.&lt;/p&gt;

&lt;p&gt;Frameworks are scaffolding, not the building. Escaping their gravity does not mean abandoning progress; it means finding enough momentum to keep moving.&lt;/p&gt;

&lt;p&gt;Reaching escape velocity, one project at a time.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>frameworks</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>HTML’s Best Kept Secret: The &lt;output&gt; Tag</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Wed, 01 Oct 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/denodell/htmls-best-kept-secret-the-tag-1f</link>
      <guid>https://dev.to/denodell/htmls-best-kept-secret-the-tag-1f</guid>
      <description>&lt;p&gt;Every developer knows &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;. It’s the workhorse of the web.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt;? Most have never touched it. Some don’t even know it exists.&lt;/p&gt;

&lt;p&gt;That’s a shame, because it solves something we’ve been cobbling together with &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s and ARIA for years: dynamic results that are announced to screen readers by default.&lt;/p&gt;

&lt;p&gt;It’s been in the spec for years. &lt;em&gt;Yet it’s hiding in plain sight&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;Here’s what the HTML5 spec says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; element represents the result of a calculation performed by the application, or the result of a user action.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s mapped to &lt;code&gt;role="status"&lt;/code&gt; in the accessibility tree. In plain terms, it announces its value when it changes, as if it already had &lt;code&gt;aria-live="polite" aria-atomic="true"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In practice, that means updates do not interrupt the user. They are read shortly after, and the &lt;em&gt;entire&lt;/em&gt; content is spoken rather than just the part that changed. You can override this behavior by setting your own ARIA properties if needed.&lt;/p&gt;

&lt;p&gt;Usage is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;output&amp;gt;Your dynamic value goes here&amp;lt;/output&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. Built-in assistive technology support. No attributes to memorize. Just HTML doing what it was always meant to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  My moment of discovery
&lt;/h2&gt;

&lt;p&gt;I discovered &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; on an accessibility project with a multi-step form. The form updated a risk score as fields changed. It looked perfect in the browser, but screen reader users had no idea the score was updating.&lt;/p&gt;

&lt;p&gt;Adding an ARIA live region fixed it. But I’ve always believed in reaching for semantic HTML first, and live regions often feel like a patch.&lt;/p&gt;

&lt;p&gt;That’s when I scoured the spec and &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; jumped out. It understands forms without requiring one, and it announces its changes natively. Turns out the simplest fix had been in the spec all along.&lt;/p&gt;

&lt;h2&gt;
  
  
  So why don’t we use it?
&lt;/h2&gt;

&lt;p&gt;Because we forgot. It’s not covered in most tutorials. It doesn’t look flashy. When I searched GitHub public repos, it barely showed up at all.&lt;/p&gt;

&lt;p&gt;It gets overlooked in patterns and component libraries too. That absence creates a feedback loop: if no one teaches it, no one uses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few things to know
&lt;/h2&gt;

&lt;p&gt;Like &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; has a &lt;code&gt;for=""&lt;/code&gt; attribute. Here you list the &lt;code&gt;id&lt;/code&gt;s of any &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; elements the result depends on, separated by spaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;input id="a" type="number"&amp;gt; +
&amp;lt;input id="b" type="number"&amp;gt; =
&amp;lt;output for="a b"&amp;gt;&amp;lt;/output&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For most users, nothing changes visually. But in the accessibility tree it creates a semantic link, letting assistive technology users connect the inputs with their calculated result.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update 7 Oct 2025&lt;/em&gt;: Some screen readers have been found not to announce updates to the tag, so explicitly emphasising the &lt;code&gt;role&lt;/code&gt; attribute might be worthwhile for now until support improves: &lt;code&gt;&amp;lt;output role="status"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It doesn’t require a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; either. You can use it anywhere you are updating dynamic text on the page based on the user’s input.&lt;/p&gt;

&lt;p&gt;By default &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; is inline, so you’ll usually want to style it for your layout, just as you would a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And because it has been part of the spec since 2008, support is excellent across browsers and screen readers. It also plays nicely with any JavaScript framework you might be using, like React or Vue.&lt;/p&gt;

&lt;p&gt;One thing to note: &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; is for results tied to user inputs and actions, not global notifications like toast messages. Those are better handled with &lt;code&gt;role="status"&lt;/code&gt; or &lt;code&gt;role="alert"&lt;/code&gt; on a generic element, since they represent system feedback rather than calculated output.&lt;/p&gt;

&lt;p&gt;So what does this look like in practice?&lt;/p&gt;

&lt;h2&gt;
  
  
  Real world examples
&lt;/h2&gt;

&lt;p&gt;I’ve personally reached for &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; in multiple real-world projects since discovering it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple calculator app
&lt;/h3&gt;

&lt;p&gt;During a recent 20-minute coding challenge, I used &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; to display calculation results. Without adding a single ARIA role, the screen reader announced each result as it updated. No hacks required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Range slider formatting
&lt;/h3&gt;

&lt;p&gt;At Volvo Cars, we displayed user-friendly versions of slider values. Internally the slider might hold &lt;code&gt;10000&lt;/code&gt;, but the output showed &lt;code&gt;10,000 miles/year&lt;/code&gt;. We wrapped the slider and &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; in a container with &lt;code&gt;role="group"&lt;/code&gt; and a shared label, creating a cohesive React component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div role="group" aria-labelledby="mileage-label"&amp;gt;
  &amp;lt;label id="mileage-label" htmlFor="mileage"&amp;gt;
    Annual mileage
  &amp;lt;/label&amp;gt;
  &amp;lt;input
    id="mileage"
    name="mileage"
    type="range"
    value={mileage}
    onChange={(e) =&amp;gt; setMileage(Number(e.target.value))}
  /&amp;gt;
  &amp;lt;output name="formattedMileage" htmlFor="mileage"&amp;gt;
    {mileage.toLocaleString()} miles/year
  &amp;lt;/output&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Form validation feedback
&lt;/h3&gt;

&lt;p&gt;I found that password strength indicators and real-time validation messages work beautifully with &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;label for="password"&amp;gt;Password&amp;lt;/label&amp;gt;
&amp;lt;input type="password" id="password" name="password"&amp;gt;
&amp;lt;output for="password"&amp;gt;
  Password strength: Strong
&amp;lt;/output&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Server-calculated output? No problem.
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; tag even fits modern patterns where you might fetch prices from APIs, show tax calculations, or display server-generated recommendations.&lt;/p&gt;

&lt;p&gt;Here, a shipping cost calculator updates an &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; tag, informing users once the cost has been calculated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function ShippingCalculator() {
  const [weight, setWeight] = useState("");
  const [price, setPrice] = useState("");

  useEffect(() =&amp;gt; {
    if (weight) {
      // Fetch shipping price from server based on package weight
      fetch(`/api/shipping?weight=${weight}`)
        .then((res) =&amp;gt; res.json())
        .then((data) =&amp;gt; setPrice(data.price));
    }
  }, [weight]);

  return (
    &amp;lt;form&amp;gt;
      &amp;lt;label&amp;gt;
        Package weight (kg):
        &amp;lt;input
          type="number"
          name="weight"
          value={weight}
          onChange={(e) =&amp;gt; setWeight(e.target.value)}
        /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;output name="price" htmlFor="weight"&amp;gt;
        {price ? `Estimated shipping: $${price}` : "Calculating..."}
      &amp;lt;/output&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Satisfaction guaranteed
&lt;/h2&gt;

&lt;p&gt;There’s something satisfying about using a native HTML element for what it was designed for, especially when it makes your UI more accessible with less code.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; might be HTML’s best kept secret, and discovering gems like this shows how much value is still hiding in the spec.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sometimes the best tool for the job is the one you didn’t even know you had.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>html</category>
      <category>a11y</category>
      <category>forms</category>
      <category>semantics</category>
    </item>
    <item>
      <title>Code Reviews That Actually Improve Frontend Quality</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Wed, 20 Aug 2025 08:00:00 +0000</pubDate>
      <link>https://dev.to/denodell/code-reviews-that-actually-improve-frontend-quality-e3c</link>
      <guid>https://dev.to/denodell/code-reviews-that-actually-improve-frontend-quality-e3c</guid>
      <description>&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%2F6suhmsd9tezwl7uylc8z.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%2F6suhmsd9tezwl7uylc8z.png" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most frontend reviews pass quickly. Linting's clean, TypeScript's happy, nothing looks broken. And yet: a modal won't close, a button's unreachable, an API call fails silently.&lt;/p&gt;

&lt;p&gt;The code was fine. &lt;em&gt;The product wasn't&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;We say we care about frontend quality. But most reviews never look at the thing users actually touch.&lt;/p&gt;

&lt;p&gt;A good frontend review isn't about nitpicking syntax or spotting clever abstractions. It's about seeing what this code becomes in production. How it behaves. What it breaks. What it forgets.&lt;/p&gt;

&lt;p&gt;If you want to catch those bugs, you need to look beyond the diff. Here's what matters most, and how to catch these issues before they ship:&lt;/p&gt;

&lt;h2&gt;
  
  
  Catch System Failures
&lt;/h2&gt;

&lt;p&gt;When reviewing, start with the obvious question: &lt;em&gt;what happens if something goes wrong?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the API fails, the user is offline, or a third-party script hangs, if the response is empty, slow, or malformed, will the UI recover? Will the user even know?&lt;/p&gt;

&lt;p&gt;If there's no loading state, no error fallback, no retry logic, the answer is probably &lt;em&gt;no&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
And by the time it shows up in a bug report, the damage is already done.&lt;/p&gt;
&lt;h2&gt;
  
  
  Check Usability
&lt;/h2&gt;

&lt;p&gt;Once you've handled system failures, think about how real people interact with this code.&lt;/p&gt;

&lt;p&gt;Does &lt;code&gt;Tab&lt;/code&gt; reach every element it should?&lt;br&gt;&lt;br&gt;
Does &lt;code&gt;Escape&lt;/code&gt; close the modal?&lt;br&gt;&lt;br&gt;
Does keyboard focus land somewhere useful after a dialog opens?&lt;/p&gt;

&lt;p&gt;A lot of code passes review because it works for the developer who wrote it. The real test is what happens on someone else's device, with someone else's habits, expectations, and constraints.&lt;/p&gt;
&lt;h2&gt;
  
  
  Look for Subtle Performance Traps
&lt;/h2&gt;

&lt;p&gt;Performance bugs hide in plain sight.&lt;/p&gt;

&lt;p&gt;Watch out for nested loops that create quadratic time complexity: fine on 10 items, disastrous on 10,000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// O(n²). Fine for small test data, brutal at scale
items.map(item =&amp;gt; 
  categories.find(cat =&amp;gt; cat.id === item.categoryId)
)

// Fix: Pre-index categories with a Map() for O(1) lookups
const categoryMap = new Map(categories.map(cat =&amp;gt; [cat.id, cat]));
items.map(item =&amp;gt; categoryMap.get(item.categoryId));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recalculating values on every render is also a performance hit waiting to happen. And a one-line import that drags in 100KB of unused helpers? If you miss it now, Lighthouse will flag it later.&lt;/p&gt;

&lt;p&gt;The worst performance bugs rarely look ugly. They just feel slow.&lt;br&gt;&lt;br&gt;
And by then, they've shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trace State and Side Effects
&lt;/h2&gt;

&lt;p&gt;State problems don't always raise alarms. But when side effects run more than they should, when event listeners stick around too long, when flags toggle in the wrong order, things go wrong. Quietly. Indirectly. Sometimes only after the next deploy.&lt;/p&gt;

&lt;p&gt;If you don't trace through what actually happens when the component (or view) initializes, updates, or gets torn down, you won't catch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catch Accessibility Gaps Early
&lt;/h2&gt;

&lt;p&gt;Same goes for accessibility.&lt;/p&gt;

&lt;p&gt;Watch out for missing labels, skipped headings, broken focus traps, and no live announcements when something changes, like a toast message appearing without a screen reader ever announcing it.&lt;/p&gt;

&lt;p&gt;No one's writing &lt;code&gt;&amp;lt;div role="button"&amp;gt;&lt;/code&gt; maliciously; they're just not thinking about how it works without a pointer.&lt;/p&gt;

&lt;p&gt;You don't need to be an accessibility expert to catch these basics. The fixes aren't hard. The hard part is noticing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spot What's Missing
&lt;/h2&gt;

&lt;p&gt;And sometimes, the problem isn't what's broken. It's what's missing.&lt;/p&gt;

&lt;p&gt;Watch out for missing empty states, no message when a list is still loading, and no indication that an action succeeded or failed.&lt;/p&gt;

&lt;p&gt;The developer knows what's going on.&lt;br&gt;&lt;br&gt;
The user just sees a blank screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Call Out Brittle Complexity
&lt;/h2&gt;

&lt;p&gt;Other times, the issue is complexity.&lt;/p&gt;

&lt;p&gt;The component fetches data, transforms it, renders markup, triggers side effects, handles errors, and logs analytics, all in one file.&lt;br&gt;&lt;br&gt;
It's not technically wrong. But it's brittle.&lt;br&gt;&lt;br&gt;
And no one will refactor it once it's merged.&lt;/p&gt;

&lt;p&gt;Call it out before it calcifies.&lt;/p&gt;

&lt;p&gt;Same with naming.&lt;/p&gt;

&lt;p&gt;A function called &lt;code&gt;handleClick&lt;/code&gt; might sound harmless, until you realize it toggles login state, starts a network request, and navigates the user to a new route.&lt;br&gt;&lt;br&gt;
That's not a click handler. It's a full user flow in disguise.&lt;/p&gt;

&lt;p&gt;Reviews are the last chance to notice that sort of thing before it disappears behind good formatting and familiar patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Deliver These Reviews
&lt;/h2&gt;

&lt;p&gt;A good review finds problems. A great review gets them fixed without putting anyone on the defensive.&lt;/p&gt;

&lt;p&gt;Keep the focus on the code, not the coder.&lt;br&gt;&lt;br&gt;
"This component re-renders on every keystroke" lands better than "You didn't memoize this."&lt;/p&gt;

&lt;p&gt;Explain why it matters.&lt;br&gt;&lt;br&gt;
"This will slow down typing in large forms" is clearer than "This is inefficient."&lt;/p&gt;

&lt;p&gt;And when you point something out, give the next step.&lt;br&gt;&lt;br&gt;
"Consider using &lt;code&gt;useMemo()&lt;/code&gt; here" is a path forward. "This is wrong" is a dead end.&lt;/p&gt;

&lt;p&gt;Call out what's done well. A quick "Nice job handling the loading state" makes the rest easier to hear.&lt;/p&gt;

&lt;p&gt;If the author feels attacked, they'll tune out. And the bug will still be there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the Experience, Not Just the Diff
&lt;/h2&gt;

&lt;p&gt;What journey is this code part of?&lt;br&gt;&lt;br&gt;
What's the user trying to do here?&lt;br&gt;&lt;br&gt;
Does this change make that experience faster, clearer, or more resilient?&lt;/p&gt;

&lt;p&gt;If you can't answer that, open the app. Click through it. Break it. Slow it down.&lt;/p&gt;

&lt;p&gt;Better yet, make it effortless.&lt;br&gt;&lt;br&gt;
Spin up a temporary, production-like copy of the app for every pull request.&lt;br&gt;&lt;br&gt;
Now anyone, not just the reviewer, can click around, break things, and see the change in context before it merges.&lt;/p&gt;

&lt;p&gt;Tools like Vercel Preview Deployments, Netlify Deploy Previews, GitHub Codespaces, or Heroku Review Apps make this almost effortless.&lt;/p&gt;

&lt;p&gt;Catch them here, and they never make it to production.&lt;br&gt;&lt;br&gt;
Miss them, and your users will find them for you.&lt;/p&gt;

&lt;p&gt;The real bugs aren't in the code; they're in the product, waiting in your next pull request.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>codereviews</category>
      <category>a11y</category>
      <category>performance</category>
    </item>
    <item>
      <title>We Keep Reinventing CSS, but Styling Was Never the Problem</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Wed, 06 Aug 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/denodell/we-keep-reinventing-css-but-styling-was-never-the-problem-2e7p</link>
      <guid>https://dev.to/denodell/we-keep-reinventing-css-but-styling-was-never-the-problem-2e7p</guid>
      <description>&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%2Ffk2w3sel8p6u2vsipkhs.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%2Ffk2w3sel8p6u2vsipkhs.png" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ve been building for the web for decades. CSS has had time to grow up, and in many ways, it has. We’ve got scoped styles, design tokens, cascade layers, even utility-first frameworks that promise to eliminate bikeshedding entirely.&lt;/p&gt;

&lt;p&gt;And yet, somehow, every new project still begins with a shrug and the same old question:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;“So… how are we styling things this time?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s not that we lack options. It’s that every option comes with trade-offs. None of them quite fit.&lt;br&gt;&lt;br&gt;
We keep reinventing CSS as if it’s the root cause.&lt;br&gt;&lt;br&gt;
&lt;em&gt;It isn’t.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  CSS Wasn’t Made for This
&lt;/h3&gt;

&lt;p&gt;It’s easy to forget what CSS was originally designed for: documents. You’d write some HTML, style a few headings and paragraphs, maybe float an image to the left, and call it a day. In that world, global styles made sense. The cascade was helpful. Inheritance was elegant.&lt;/p&gt;

&lt;p&gt;Fast-forward a couple of decades and we’re building highly interactive, component-based, state-driven, design-system-heavy applications, still with a language meant to style a résumé in the early 2000s.&lt;/p&gt;

&lt;p&gt;CSS wasn’t built for encapsulated components. It wasn’t built for dynamic theming or runtime configuration or hydration mismatches. So we’ve spent years bolting on strategies to make it work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every Option Solves One Problem. None Solve All of Them.
&lt;/h3&gt;

&lt;p&gt;What we have now is a landscape of trade-offs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BEM&lt;/strong&gt; gives you naming predictability, and very verbose selectors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS Modules&lt;/strong&gt; give you scoping, unless you need runtime theming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Utility-first CSS&lt;/strong&gt; (like Tailwind) enables fast iteration, but clutters your markup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS-in-JS&lt;/strong&gt; offers colocation and flexibility, at the cost of runtime performance and complexity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cascade Layers and &lt;code&gt;:where()&lt;/code&gt;&lt;/strong&gt; give you more control, if your team is ready to learn them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each approach solves something. None solve everything. Yet we keep framing them as silver bullets, not as trade-off tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maybe It’s Not CSS That’s the Problem
&lt;/h3&gt;

&lt;p&gt;Here’s the uncomfortable truth: &lt;em&gt;most of our styling pain doesn’t come from CSS itself&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
It comes from trying to shoehorn CSS into frontend architectures that weren’t designed to support it.&lt;/p&gt;

&lt;p&gt;React, Vue, Svelte. They all put components at the core. Scoped logic. Scoped templates. Scoped state. Then we hand them a stylesheet that’s global, cascading, and inherited by default.&lt;/p&gt;

&lt;p&gt;We’ve spent the last decade asking CSS to behave like a module system. It isn’t one.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Real Question: Which Pain Are You Willing to Accept?
&lt;/h3&gt;

&lt;p&gt;This isn’t just a tooling choice.&lt;br&gt;&lt;br&gt;
It’s a question of what trade-offs you’re prepared to live with.&lt;/p&gt;

&lt;p&gt;Do you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scoped styles with minimal tooling? Use CSS Modules and accept limited runtime flexibility.&lt;/li&gt;
&lt;li&gt;Predictability and no cascade? Use utility-first CSS and brace for cluttered markup.&lt;/li&gt;
&lt;li&gt;Dynamic styles colocated with logic? Use CSS-in-JS and monitor your bundle size closely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s no single solution. Just strategies. Just context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accept the Mess. Choose With Your Eyes Open.
&lt;/h3&gt;

&lt;p&gt;Styling the web isn’t solved. It may never be. But it gets easier when we stop pretending there’s a perfect answer just one abstraction away.&lt;/p&gt;

&lt;p&gt;Be clear about what matters, and deliberate about what you’re willing to trade.&lt;/p&gt;

&lt;p&gt;Because at the end of the day, no one writes perfect CSS.&lt;br&gt;&lt;br&gt;
&lt;em&gt;Just CSS that’s good enough to ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>frontend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The Web Is About to Get Better for Everyone, Everywhere</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Wed, 23 Jul 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/denodell/the-web-is-about-to-get-better-for-everyone-everywhere-5300</link>
      <guid>https://dev.to/denodell/the-web-is-about-to-get-better-for-everyone-everywhere-5300</guid>
      <description>&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%2Fqa384fs8401vd0tmilw8.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%2Fqa384fs8401vd0tmilw8.png" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next big thing in web development isn’t a framework. It’s a piece of legislation.&lt;/p&gt;

&lt;p&gt;Starting summer 2025, the European Accessibility Act will require digital products provided in the EU to meet actual, enforceable accessibility standards. Not "we added some alt text" accessibility. Real accessibility. The kind you can be sued over.&lt;/p&gt;

&lt;p&gt;And that means the internet is about to get a lot more usable.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Turns out “do the right thing” works better when it’s legally enforced.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Accidental Global Upgrade
&lt;/h2&gt;

&lt;p&gt;On paper, the EAA is about protecting the rights of disabled users in the EU. In practice, it will quietly upgrade the user experience for everyone, everywhere.&lt;/p&gt;

&lt;p&gt;Because global companies don’t build fifty different versions of their site. They build one. And when that one needs to pass strict accessibility checks in Europe, it becomes the new default for everyone. Not because they’ve had a sudden ethical awakening, but because it’s cheaper than maintaining a fork.&lt;/p&gt;

&lt;p&gt;I’ve seen this happen firsthand. At Volvo Cars, I helped lead accessibility work on a complex finance interface used across dozens of countries. Some markets had legal requirements. Others didn’t. We didn’t build two versions. We built one good one. That’s what’s about to happen at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Isn’t GDPR. It’s Deeper.
&lt;/h2&gt;

&lt;p&gt;We’ve been here before. The GDPR forced companies to rethink privacy. Cookie banners popped up everywhere, and data practices changed, even in countries that had no such law.&lt;/p&gt;

&lt;p&gt;The EAA will have the same ripple effect. But where GDPR mostly lives in popups and policy pages, accessibility lives in your codebase. This time, it’s going to affect how teams design, build, and ship products.&lt;/p&gt;

&lt;p&gt;Developers will need to stop misusing &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s and start writing semantic markup. They’ll have to test keyboard navigation and make sure screen readers can do more than guess. Designers will need to think about contrast, focus indicators, and interaction patterns that work for more than just perfect eyesight and fine motor control. Product managers will need to bring accessibility forward in the roadmap, instead of leaving it to sprint eleven.&lt;/p&gt;

&lt;p&gt;For teams that never took it seriously, this will feel like a lot. Because it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everyone Wins, Eventually
&lt;/h2&gt;

&lt;p&gt;The good news is, once companies start updating their design systems and components to comply, those updates don’t stay locked inside. They spread.&lt;/p&gt;

&lt;p&gt;When Figma kits, React libraries, or UI frameworks adjust for accessibility, thousands of developers benefit, even if they’ve never heard of the EAA. Small teams and solo devs inherit improvements without lifting a finger. Your bootstrapped SaaS gets better because someone at a bank had to follow the law.&lt;/p&gt;

&lt;p&gt;And just like GDPR influenced privacy legislation far beyond Europe, the EAA could nudge other governments into action. The US has had the ADA for decades, but it has never applied consistently to the web. That may change once global companies start setting a new baseline.&lt;/p&gt;

&lt;p&gt;Even if no new laws appear, the precedent is clear. Accessibility is not a bonus. It is a requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  There’s Still Work to Do
&lt;/h2&gt;

&lt;p&gt;Laws help, but they don’t write your code. Teams will need support. Tooling will need to catch up. Companies will need to invest in audits, training, and inclusive hiring.&lt;/p&gt;

&lt;p&gt;But the momentum is there. And for once, it isn’t being driven by blog posts or industry talks. It’s being driven by legislation with actual teeth.&lt;/p&gt;

&lt;p&gt;For years, accessibility experts have asked companies to take this seriously. Now they have to.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Turns out the web gets fixed quicker when ignoring it brings in the lawyers.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>frontend</category>
      <category>policy</category>
    </item>
    <item>
      <title>AI Is Just The Latest Frontend Killer. Don't Panic.</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Wed, 09 Jul 2025 10:08:08 +0000</pubDate>
      <link>https://dev.to/denodell/ai-is-just-the-latest-frontend-killer-dont-panic-ke0</link>
      <guid>https://dev.to/denodell/ai-is-just-the-latest-frontend-killer-dont-panic-ke0</guid>
      <description>&lt;p&gt;Apparently, frontend developers are about to be made obsolete.&lt;br&gt;&lt;br&gt;
AI can scaffold a site from a prompt, restyle it mid-conversation, and write tests it will never have to maintain.&lt;br&gt;&lt;br&gt;
It’s fast. It’s confident. And according to some, it means we’re done here.  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;This all sounds very familiar.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The End Of The Frontend Profession?
&lt;/h2&gt;

&lt;p&gt;The first time I saw an AI coding assistant write a React component, I paused.&lt;/p&gt;

&lt;p&gt;It used the right hook. Destructured props neatly. Even remembered the &lt;code&gt;aria-label&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Well,&lt;/em&gt; I thought, &lt;em&gt;there goes the frontend job market.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But the more I used it, the more I tried to hand it real work, the more it missed the mark. It was fast, yes. But shallow. Confident, but clueless. It could write code, but not the right code. Not in the way I needed it.&lt;/p&gt;

&lt;p&gt;And that’s when it clicked: the risk isn’t that AI will replace frontend jobs. It’s that it will replace the ones that were &lt;em&gt;already replaceable&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI Still Doesn’t Get
&lt;/h2&gt;

&lt;p&gt;AI coding assistants are great at scaffolding UIs, rewriting code, even generating decent boilerplate for common components. But they don’t know why one pattern is better than another. They don’t care about &lt;a href="https://web.dev/articles/cls" rel="noopener noreferrer"&gt;CLS&lt;/a&gt; or &lt;a href="https://web.dev/articles/inp" rel="noopener noreferrer"&gt;INP&lt;/a&gt; or how something feels when a user taps it on a laggy phone. They don’t push back when &lt;a href="https://denodell.com/blog/building-the-web-in-islands" rel="noopener noreferrer"&gt;300kB of JavaScript bloats the homepage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Frontend engineering isn’t just building. It’s choosing. Tuning. Diagnosing. Holding the line when product wants “just a quick popup” and you know it’ll tank performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Demos Look Clean. The Reality Isn’t.
&lt;/h2&gt;

&lt;p&gt;I’ve seen the posts.&lt;/p&gt;

&lt;p&gt;AI tools generating complete sites in a single session. Pixel-perfect designs from a few prompts. “Production-ready” apps without a line of hand-written code.&lt;/p&gt;

&lt;p&gt;And yes, it’s impressive. The scaffolding is faster than ever. The demos look clean.&lt;/p&gt;

&lt;p&gt;But when you try to scale them, test them, or make them fast, accessible, and reliable across real devices and real users?&lt;/p&gt;

&lt;p&gt;That’s where the job actually begins.&lt;br&gt;&lt;br&gt;
And that’s not something I trust a code generator to own.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Than Just a Coder
&lt;/h2&gt;

&lt;p&gt;I still write code, of course. But I also…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trace where the bloat comes from
&lt;/li&gt;
&lt;li&gt;Fix interactivity delays no audit tool flags
&lt;/li&gt;
&lt;li&gt;Catch the missing &lt;code&gt;aria-describedby&lt;/code&gt; before it hits QA
&lt;/li&gt;
&lt;li&gt;Explain to designers why we can't animate everything, everywhere, all at once
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren’t things I want to outsource. They’re the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI is a Tool, Not a Threat
&lt;/h2&gt;

&lt;p&gt;I do use AI. It helps me prototype faster, summarize unfamiliar APIs, and draft tests I’d rather not write by hand. Sometimes it even surprises me with a pattern I hadn’t considered.&lt;/p&gt;

&lt;p&gt;But I never copy anything I don’t understand. I don’t ship what I wouldn’t explain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://denodell.com/blog/try-a-week-on-call" rel="noopener noreferrer"&gt;I’ve done on-call&lt;/a&gt;. I’ve supported real users at real scale. And I can tell you, I wouldn’t want to be the one debugging production issues in code I didn’t fully understand, especially not at 2am. If AI wrote it, I need to own it before I ship it.&lt;/p&gt;

&lt;p&gt;The responsibility, the judgment, is still mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  We’ve Been Here Before
&lt;/h2&gt;

&lt;p&gt;I’ve been building for the web professionally for 25 years. That’s long enough to have watched entire tech stacks rise and fall. From &lt;a href="https://denodell.com/blog/hacking-layout-before-css-existed" rel="noopener noreferrer"&gt;table layouts&lt;/a&gt; to Flash to jQuery to SPAs to server-first rendering and back again.&lt;/p&gt;

&lt;p&gt;And I’ve seen the “death of frontend” declared more than once.&lt;/p&gt;

&lt;p&gt;When &lt;a href="https://en.wikipedia.org/wiki/Adobe_Dreamweaver" rel="noopener noreferrer"&gt;Dreamweaver&lt;/a&gt; promised you’d never need to write HTML again.&lt;br&gt;&lt;br&gt;
When &lt;a href="https://en.wikipedia.org/wiki/Adobe_Flash" rel="noopener noreferrer"&gt;Flash&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Microsoft_Silverlight" rel="noopener noreferrer"&gt;Silverlight&lt;/a&gt; tried to replace the browser.&lt;br&gt;&lt;br&gt;
When JavaScript frameworks told us the DOM was obsolete.&lt;br&gt;&lt;br&gt;
When no-code tools promised designers wouldn’t need developers.&lt;br&gt;&lt;br&gt;
Now it’s AI’s turn.&lt;/p&gt;

&lt;p&gt;Each time, the pitch was the same: this new thing writes the code for you.&lt;br&gt;&lt;br&gt;
But the complexity didn’t go away. It just shifted.&lt;br&gt;&lt;br&gt;
And the need for people who understand users, performance, and systems never disappeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Hasn’t Changed
&lt;/h2&gt;

&lt;p&gt;So what’s different this time? Not much. Just the speed, and the hype.&lt;/p&gt;

&lt;p&gt;AI is better than the tools that came before it. No question.&lt;br&gt;&lt;br&gt;
But the fundamentals of great frontend work haven’t changed: fast interactions, inclusive experiences, clear architecture, systems thinking.&lt;/p&gt;

&lt;p&gt;We debug the weird bugs. We balance performance with design.&lt;br&gt;&lt;br&gt;
We build for everyone, not just the fast, the lucky, or the local.&lt;/p&gt;

&lt;p&gt;That’s what real frontend engineers do.&lt;br&gt;&lt;br&gt;
That’s what I do.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And I’m not planning to become obsolete anytime soon&lt;/em&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building the Web in Islands, Not Mountains</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Wed, 25 Jun 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/denodell/building-the-web-in-islands-not-mountains-5cb2</link>
      <guid>https://dev.to/denodell/building-the-web-in-islands-not-mountains-5cb2</guid>
      <description>&lt;p&gt;Not every site needs to be a full-blown app. In fact, most don’t. What they need is to be fast, focused, and respectful of the user’s device.&lt;/p&gt;

&lt;p&gt;Put another way: if your ‘About Us’ page needs 300kB of JavaScript to render a sentence about your mission, &lt;em&gt;it may be time to reconsider&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;We’ve leaned heavily into building everything as if it were an application: routing, state, hydration, and all. That approach works brilliantly for complex, interactive experiences. But for many sites, it leads to overengineered pages, unnecessary JavaScript, and slower interactions.&lt;/p&gt;

&lt;p&gt;There’s a better pattern, and it’s gaining momentum again. It’s called &lt;strong&gt;islands architecture&lt;/strong&gt; , and it offers a way to ship less, do more, and give users speed where it counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With “One Big App”
&lt;/h2&gt;

&lt;p&gt;Single-page apps (SPAs) brought a wave of innovation to the frontend. They gave us smooth routing, shared state, and dynamic interactivity. For many use cases, they remain the right choice.&lt;/p&gt;

&lt;p&gt;But when every page becomes part of one large application, even simple, mostly static ones, we start to pay a price. That price often includes larger bundles, delayed interactivity, and more work for the browser than is truly needed.&lt;/p&gt;

&lt;p&gt;If your marketing page or blog post loads the same hydration logic and route configuration as your account dashboard, something is off. It creates work that doesn’t need to happen, especially on mobile.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Islands Architecture?
&lt;/h2&gt;

&lt;p&gt;Islands architecture takes a more selective approach. It renders the majority of the page server-side, using static HTML whenever possible, and hydrates only the components that need to be interactive.&lt;/p&gt;

&lt;p&gt;Picture a page as a landmass. Most of it — layout, content, structure — is pre-rendered and delivered instantly. Then come the &lt;strong&gt;islands&lt;/strong&gt; : self-contained interactive parts like a search box, a shopping cart, or a comment form. These hydrate independently and only when needed.&lt;/p&gt;

&lt;p&gt;Instead of treating every page like a fully hydrated application, you focus interactivity where it adds the most value.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Isn’t a New Idea
&lt;/h2&gt;

&lt;p&gt;Before SPAs and JavaScript frameworks, developers often rendered pages server-side and used scripts sparingly to enhance behavior. You’d write HTML, sprinkle in jQuery plugins, and call it a day.&lt;/p&gt;

&lt;p&gt;In hindsight, this was a kind of early islands model. The challenge was maintainability. Everything felt stitched together.&lt;/p&gt;

&lt;p&gt;Today’s tools bring back the same mindset, but with component-based discipline and modern developer ergonomics.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Islands Work in Practice
&lt;/h2&gt;

&lt;p&gt;Frameworks like Astro are built with islands in mind. You can specify how and when a component should hydrate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;InteractiveRating client:idle /&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Astro to render the component on the server and hydrate it only once the page is idle. Other directives include &lt;code&gt;client:load&lt;/code&gt;, &lt;code&gt;client:visible&lt;/code&gt;, and &lt;code&gt;client:media&lt;/code&gt;, letting you match hydration to user context.&lt;/p&gt;

&lt;p&gt;Other frameworks take similar or adjacent approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Qwik&lt;/strong&gt; uses &lt;strong&gt;resumability&lt;/strong&gt; , which avoids hydration entirely by serializing state for instant interactivity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhance.dev&lt;/strong&gt; leans into web standards and progressive enhancement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fresh&lt;/strong&gt; (Deno) supports islands as a core rendering model.&lt;/li&gt;
&lt;li&gt;Even &lt;strong&gt;Next.js&lt;/strong&gt; , with React Server Components and the &lt;code&gt;app/&lt;/code&gt; directory, is giving developers more fine-grained control over what runs where.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not opposing models. They are part of a broader shift toward delivering more HTML and less JavaScript when appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It’s Fast
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Less JavaScript up front means quicker interactivity.&lt;/li&gt;
&lt;li&gt;Fewer layout shifts make the experience more stable.&lt;/li&gt;
&lt;li&gt;Lower memory and CPU usage helps users on mid-range or older devices.&lt;/li&gt;
&lt;li&gt;Core Web Vitals like LCP, INP, and CLS often improve naturally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And most importantly, it feels better. The page is usable immediately. You don’t wait for hydration to finish or for a loading spinner to disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use It
&lt;/h2&gt;

&lt;p&gt;Islands architecture shines when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your content is mostly static, with pockets of interactivity.&lt;/li&gt;
&lt;li&gt;You care about first load performance.&lt;/li&gt;
&lt;li&gt;Your audience includes mobile users or bandwidth-constrained regions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a great fit for blogs, marketing pages, documentation sites, and e-commerce product pages. Anywhere that’s mostly static but benefits from pockets of interactivity.&lt;/p&gt;

&lt;p&gt;For complex apps with deep state and interactive workflows, a hybrid model may make more sense.&lt;/p&gt;

&lt;p&gt;But even there, treating your UI as a set of islands can still lead to smarter hydration, clearer boundaries, and better performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  It’s Future-Ready Too
&lt;/h2&gt;

&lt;p&gt;The trend is clear. Edge rendering, streaming, and component-level monitoring are all rising. Islands work well in this environment.&lt;/p&gt;

&lt;p&gt;They support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML streaming for faster time to first byte.&lt;/li&gt;
&lt;li&gt;Incremental rendering and delivery.&lt;/li&gt;
&lt;li&gt;Real user monitoring at the component level.&lt;/li&gt;
&lt;li&gt;Smarter delivery based on user context such as device, viewport, and connection speed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your stack supports server rendering and hydration control, you are already most of the way there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Islands, Not Mountains
&lt;/h2&gt;

&lt;p&gt;Modern frameworks brought us power, flexibility, and developer joy. But they also nudged us toward treating every site like a full application.&lt;/p&gt;

&lt;p&gt;Islands architecture brings balance. It keeps the parts of modern development that work, such as components, server-side rendering, and interactivity, and refocuses them around speed, simplicity, and user needs.&lt;/p&gt;

&lt;p&gt;You don’t need to build a mountain for every page. &lt;br&gt;
Islands get users where they need to go. &lt;br&gt;
&lt;em&gt;Without the altitude sickness&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>architecture</category>
      <category>frameworks</category>
    </item>
    <item>
      <title>Hacking Layout Before CSS Even Existed</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Wed, 11 Jun 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/denodell/hacking-layout-before-css-even-existed-1n9c</link>
      <guid>https://dev.to/denodell/hacking-layout-before-css-even-existed-1n9c</guid>
      <description>&lt;p&gt;&lt;strong&gt;Before &lt;code&gt;flex&lt;/code&gt;, before &lt;code&gt;grid&lt;/code&gt;, even before &lt;code&gt;float&lt;/code&gt;, we still had to lay out web pages.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not just basic scaffolding, full designs. Carefully crafted interfaces with precise alignment, overlapping layers, and brand-driven visuals. But in the early days of the web, HTML wasn’t built for layout. CSS was either brand-new or barely supported. Positioning was unreliable. Browser behavior was inconsistent.&lt;/p&gt;

&lt;p&gt;And yet, somehow, we made it work.&lt;/p&gt;




&lt;p&gt;So how &lt;em&gt;did&lt;/em&gt; we lay out the web?&lt;/p&gt;

&lt;p&gt;With tables. &lt;br&gt;
Yup. &lt;em&gt;Tables.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Not the kind used for tabular data. These were layout tables, deeply nested, stretched and tweaked, often stuffed with invisible spacer GIFs to push elements into place. Text, links, and buttons were dropped into cells and floated among a scaffolding of invisible structure.&lt;/p&gt;

&lt;p&gt;If you were building websites in the late ’90s or early 2000s, this will sound familiar. If not, consider this a quick trip back to one of the more creatively chaotic eras of frontend development, and what it can still teach us today.&lt;/p&gt;
&lt;h2&gt;
  
  
  HTML wasn’t made for layout
&lt;/h2&gt;

&lt;p&gt;HTML began as a way to mark up academic documents. Headings, paragraphs, links, lists, that was about it. There was no real concept of “layout” in the design sense. Early browsers had little support for positioning or styling beyond font tweaks and basic alignment.&lt;/p&gt;

&lt;p&gt;But developers still wanted structure. And clients, especially as the web grew more mainstream, wanted their brands to show up consistently. They expected full visual treatments: custom type, precise alignment, multi-column layouts, and logos in exactly the right place. Designs weren’t just documents, they were meant to &lt;em&gt;look&lt;/em&gt; like something. Often like print. Often pixel-perfect.&lt;/p&gt;

&lt;p&gt;So we did what developers always do: we got creative.&lt;/p&gt;
&lt;h2&gt;
  
  
  Enter the layout table
&lt;/h2&gt;

&lt;p&gt;HTML tables gave us something no other element did at the time: control. You could create rows and columns. You could define cell widths and heights. You could nest tables inside tables to carve up the page into zones. That control was intoxicating.&lt;/p&gt;

&lt;p&gt;It wasn’t elegant. It definitely wasn’t semantic. But it worked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;table width="100%" cellpadding="0" cellspacing="0"&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;td width="200"&amp;gt;
      &amp;lt;img src="spacer.gif" width="200" height="1" alt=""&amp;gt;
    &amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
      Main content goes here
    &amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spacer GIFs like the one above were a standard trick. You’d create a 1×1 pixel transparent image, then stretch it using width and height attributes to force the browser to reserve space. There were entire toolkits built to generate spacer-driven layouts automatically.&lt;/p&gt;

&lt;p&gt;If you wanted padding, you’d nest another table. For alignment, you’d add empty cells or tweak the &lt;code&gt;align&lt;/code&gt; attribute. And when that wasn’t enough, you’d resort to comment-tag hacks or browser-specific rendering quirks just to make things behave.&lt;/p&gt;

&lt;h2&gt;
  
  
  Slicing designs into the web
&lt;/h2&gt;

&lt;p&gt;At agencies like AKQA, where I worked at the time, the designs weren’t simple page frames. They were fully realized compositions, often with custom art direction, background textures, and layered effects. We’d receive static visuals, usually Photoshop files, and break them apart manually into dozens of individual image slices.&lt;/p&gt;

&lt;p&gt;Some slices were background textures. Some were visual foreground elements: shadows, corners, borders, custom typography before &lt;code&gt;@font-face&lt;/code&gt; existed. Then we’d reassemble everything with HTML tables, mixing sliced images with live HTML, real text, buttons, form inputs, to recreate the original design as closely as browsers would allow.&lt;/p&gt;

&lt;p&gt;It was part engineering, part pixel-pushing, part dark art.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we did it anyway
&lt;/h2&gt;

&lt;p&gt;It’s easy to laugh now, but back then layout tables gave us something CSS didn’t: &lt;strong&gt;predictability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;CSS support was spotty. Browsers implemented it inconsistently. You could spend hours tweaking styles, only to have them break in IE5.5. Tables weren’t perfect, but they rendered the same almost everywhere.&lt;/p&gt;

&lt;p&gt;WYSIWYG tools like Dreamweaver leaned hard into the table model. You’d drag content into cells and it would spit out layers of nested HTML you weren’t really meant to touch.&lt;/p&gt;

&lt;p&gt;Was it bloated? Yes. Fragile? Absolutely. But it shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  The long road to modern CSS
&lt;/h2&gt;

&lt;p&gt;CSS1 arrived in 1996. CSS2 in 1998 brought &lt;code&gt;position: absolute&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, and &lt;code&gt;z-index&lt;/code&gt;. But it took years for browsers to catch up, and even longer for developers to trust it.&lt;/p&gt;

&lt;p&gt;The table era didn’t really end until the mid-2000s, when modern browsers matured and CSS layout finally became viable. Even then, it took time for the idea of &lt;strong&gt;separation of concerns&lt;/strong&gt; to take hold: structure in HTML, style in CSS, behavior in JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Today: grid systems, not grid hacks
&lt;/h2&gt;

&lt;p&gt;Now we have &lt;code&gt;display: grid&lt;/code&gt; and &lt;code&gt;display: flex&lt;/code&gt;. We can align elements without nesting. We can reorder content for accessibility. We can build responsive layouts without a single spacer GIF in sight.&lt;/p&gt;

&lt;p&gt;What used to take 100 lines of messy table markup now takes 10 lines of clean, declarative CSS. It’s better for developers, and for users, especially those using assistive tech that struggled to parse table-based scaffolding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the table era still teaches us
&lt;/h2&gt;

&lt;p&gt;A few lessons from the layout table era still hold true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross-browser consistency matters.&lt;/strong&gt; Even now, not everything renders the same. Test broadly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You’ll always work with constraints.&lt;/strong&gt; Back then it was no CSS. Today it might be legacy code, team skills, or framework limitations. Creativity under constraint is part of the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand the tools you’re misusing.&lt;/strong&gt; Tables weren’t designed for layout, but we understood them deeply. That same mindset helps today when bending modern tools to fit the real world.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  In closing
&lt;/h2&gt;

&lt;p&gt;Table-based layouts were a workaround. But they also reflect something constant about web development: we’re always adapting. Always hacking. Always building better experiences with the tools we have, until the next set of tools comes along.&lt;/p&gt;

&lt;p&gt;So next time you float a div or write a neat little grid template, give a small nod to the table layouts that walked so Flexbox could run.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>css</category>
      <category>html</category>
      <category>history</category>
    </item>
    <item>
      <title>Want to Be a Better Frontend Engineer? Try a Week On-Call</title>
      <dc:creator>Den Odell</dc:creator>
      <pubDate>Wed, 04 Jun 2025 12:29:01 +0000</pubDate>
      <link>https://dev.to/denodell/want-to-be-a-better-frontend-engineer-try-a-week-on-call-eka</link>
      <guid>https://dev.to/denodell/want-to-be-a-better-frontend-engineer-try-a-week-on-call-eka</guid>
      <description>&lt;p&gt;&lt;strong&gt;You’re going to hate me for saying this, but I actually like being on-call.&lt;/strong&gt; &lt;br&gt;
I know. &lt;em&gt;I know.&lt;/em&gt; But hear me out. &lt;br&gt;
Obviously not the part where PagerDuty yanks you out of a dream with your heart pounding. &lt;br&gt;
But on-call taught me more about frontend quality than any bug tracker ever did.&lt;/p&gt;




&lt;p&gt;It was 2:43 AM. &lt;br&gt;
A customer couldn’t click through to checkout. &lt;br&gt;
Revenue was on the line. &lt;br&gt;
And the alert came to me.&lt;/p&gt;

&lt;p&gt;The root cause wasn’t in the frontend. A backend job had failed quietly, returning malformed data. But to the user, the result was simple: the button didn’t work. And when you’re on-call for the frontend, you’re the one who has to respond, no matter where the problem starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  On-Call Sharpens What You Already Care About
&lt;/h2&gt;

&lt;p&gt;I’ve always cared about quality. &lt;br&gt;
I’ve written tests, chased down edge cases, and treated polish as part of the craft. &lt;br&gt;
But on-call changes how you think about all of it.&lt;/p&gt;

&lt;p&gt;It’s not just about whether your code works. &lt;br&gt;
It’s about how it fails. &lt;br&gt;
It’s about how quickly it can be understood under pressure. &lt;br&gt;
It’s about whether your interface can recover from issues far outside your control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Frontend Catches Everything
&lt;/h2&gt;

&lt;p&gt;When something fails in the system, the user usually sees it in the frontend. &lt;br&gt;
If a service goes down, your component gets no data. &lt;br&gt;
If a token expires, the user gets stuck. &lt;br&gt;
If a third-party script blocks rendering, your buttons stop working. &lt;br&gt;
If checkout breaks, your app takes the blame.&lt;/p&gt;

&lt;p&gt;You may not control the systems upstream, but on-call teaches you that you still own the experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  You Start Building for Real-World Failure
&lt;/h2&gt;

&lt;p&gt;On-call shifts your habits.&lt;/p&gt;

&lt;p&gt;You write clearer loading states. &lt;br&gt;
You handle empty, broken, or missing data. &lt;br&gt;
You stop assuming things will behave. &lt;br&gt;
You add useful logs. A &lt;em&gt;lot&lt;/em&gt; of them. &lt;br&gt;
You recover from failure instead of hiding from it.&lt;/p&gt;

&lt;p&gt;You stop writing code that works in theory. &lt;br&gt;
You start writing code that holds up at 2 AM.&lt;/p&gt;

&lt;p&gt;You begin asking better questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens if this API returns nothing?&lt;/li&gt;
&lt;li&gt;What if the feature flag system is down?&lt;/li&gt;
&lt;li&gt;Will this UI leave the user stranded if it doesn’t render properly?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  You Build Like You’re the One Who’ll Be Paged
&lt;/h2&gt;

&lt;p&gt;Because you might be.&lt;/p&gt;

&lt;p&gt;On-call brings accountability. &lt;br&gt;
You stop cutting corners you know might break later. &lt;br&gt;
You stop leaving vague TODOs for someone else. &lt;br&gt;
You stop letting “it works on my machine” be the final word.&lt;/p&gt;

&lt;p&gt;A single week of on-call teaches you what months of bug triage can’t.&lt;br&gt;&lt;br&gt;
It shows you what real-world pressure feels like, and how it exposes every weak spot in your stack.&lt;/p&gt;

&lt;p&gt;And once you’ve lived through that, you start building differently. &lt;br&gt;
&lt;em&gt;You start building better.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>oncall</category>
      <category>codequality</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
