<?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: Dennis Morello</title>
    <description>The latest articles on DEV Community by Dennis Morello (@morellodev).</description>
    <link>https://dev.to/morellodev</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F219117%2F1f9c2fb4-774c-4736-a658-17104c6ba25d.JPG</url>
      <title>DEV Community: Dennis Morello</title>
      <link>https://dev.to/morellodev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/morellodev"/>
    <language>en</language>
    <item>
      <title>The New HTTP QUERY Method</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Tue, 07 Jul 2026 18:50:16 +0000</pubDate>
      <link>https://dev.to/morellodev/the-new-http-query-method-2ek5</link>
      <guid>https://dev.to/morellodev/the-new-http-query-method-2ek5</guid>
      <description>&lt;p&gt;If you've ever built a search endpoint, you've hit this wall. Your query has filters, sort orders, a nested set of facets, maybe a geo bounding box. It doesn't fit in a URL, and cramming it into query string params is ugly and fragile. So you reach for &lt;code&gt;POST /search&lt;/code&gt;, send the whole thing as a JSON body, and quietly accept that you've just lied about what the request does. It's not creating anything. It's a read. But POST is the only tool that lets you attach a body without fighting the platform.&lt;/p&gt;

&lt;p&gt;That gap finally got filled. In June 2026 the IETF published &lt;a href="https://www.rfc-editor.org/info/rfc10008/" rel="noopener noreferrer"&gt;RFC 10008&lt;/a&gt;, which defines the HTTP QUERY method: a new verb built for exactly this case.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two bad options
&lt;/h2&gt;

&lt;p&gt;Every read that needs structured input has been stuck choosing between GET and POST, and both are wrong in their own way.&lt;/p&gt;

&lt;p&gt;GET is the semantically correct choice. It's &lt;a href="https://www.rfc-editor.org/rfc/rfc9110#name-safe-methods" rel="noopener noreferrer"&gt;safe&lt;/a&gt; (the client isn't asking to change anything), it's &lt;a href="https://www.rfc-editor.org/rfc/rfc9110#name-idempotent-methods" rel="noopener noreferrer"&gt;idempotent&lt;/a&gt; (retrying it is fine), and it's cacheable. The problem is the body. RFC 9110 is explicit that &lt;a href="https://www.rfc-editor.org/rfc/rfc9110#name-get" rel="noopener noreferrer"&gt;content in a GET request has no defined semantics&lt;/a&gt;, and sending one may cause some implementations to reject the request. So your query has to live in the URI, where you run into unknown length limits across proxies and servers, encoding overhead, and the query landing in access logs and browser history.&lt;/p&gt;

&lt;p&gt;POST solves the body problem and creates a new one. It carries any payload you want, but it's neither safe nor idempotent by definition. Intermediaries won't cache it, clients won't retry it automatically after a dropped connection, and anything inspecting traffic has to assume the request might have side effects. You get the body, you lose everything that made the request honest.&lt;/p&gt;

&lt;p&gt;QUERY is the missing third option: a method that carries a body &lt;em&gt;and&lt;/em&gt; keeps the semantics of a read.&lt;/p&gt;

&lt;h2&gt;
  
  
  What QUERY actually is
&lt;/h2&gt;

&lt;p&gt;The spec, authored by Julian Reschke, &lt;a href="https://github.com/jasnell" rel="noopener noreferrer"&gt;James Snell&lt;/a&gt;, and Mike Bishop, describes it in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A QUERY requests that the request target process the enclosed content in a safe and idempotent manner and then respond with the result of that processing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So a QUERY request looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;QUERY&lt;/span&gt; &lt;span class="nn"&gt;/products&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyboards"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"inStock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asc"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three properties that matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's safe and idempotent.&lt;/strong&gt; The client isn't requesting a state change, and the request can be retried or repeated without worrying about partial effects. This is what POST could never promise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's cacheable.&lt;/strong&gt; A cache is allowed to store the response and use it to satisfy later QUERY requests. The catch is that the cache key has to include the request content, not just the URI, since the body is what distinguishes one query from another. Two QUERYs to the same URL with different bodies are different requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The response isn't a representation of the URI.&lt;/strong&gt; Unlike GET, where the response &lt;em&gt;is&lt;/em&gt; the resource at that URL, a QUERY response is the result of running your query over some data scoped to the target. &lt;code&gt;GET /products&lt;/code&gt; returns the products resource; &lt;code&gt;QUERY /products&lt;/code&gt; returns whatever your query selected from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The details that bite
&lt;/h2&gt;

&lt;p&gt;A few rules are worth knowing before you wire this up.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Content-Type&lt;/code&gt; is mandatory. The server MUST reject the request if the &lt;code&gt;Content-Type&lt;/code&gt; field is missing or inconsistent with the body. In practice you'll see a &lt;code&gt;400&lt;/code&gt; for a missing media type, a &lt;code&gt;415 Unsupported Media Type&lt;/code&gt; for one the server doesn't handle, and a &lt;code&gt;422 Unprocessable Content&lt;/code&gt; when the body parses fine but doesn't make sense as a query.&lt;/p&gt;

&lt;p&gt;There are two response headers that trip people up because they sound alike:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Content-Location&lt;/code&gt;&lt;/strong&gt; points at a resource representing the &lt;em&gt;results&lt;/em&gt; of this query. A client can GET that URL later to fetch the same results again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Location&lt;/code&gt;&lt;/strong&gt; points at a resource representing the &lt;em&gt;query itself&lt;/em&gt;. A client can GET that URL to re-run the query without resending the body, which is handy for turning a heavy query into a shareable link.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Results points to the answer; Location points to the question.&lt;/p&gt;

&lt;p&gt;The spec also nudges you away from HTTP range requests for pagination. Range semantics technically apply, but most query formats already have their own paging built in (think SQL's &lt;code&gt;FETCH FIRST&lt;/code&gt;), and that's the mechanism you should reach for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you use HTTP QUERY today?
&lt;/h2&gt;

&lt;p&gt;On the client, yes, already. The Fetch API and libraries like axios accept arbitrary method strings, so nothing is stopping you from sending one right now:&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;QUERY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyboards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The friction is everywhere else in the stack. QUERY isn't a &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header" rel="noopener noreferrer"&gt;CORS-safelisted method&lt;/a&gt;, so cross-origin QUERY requests trigger a preflight &lt;code&gt;OPTIONS&lt;/code&gt;. Your server has to answer it with &lt;code&gt;Access-Control-Allow-Methods: QUERY&lt;/code&gt; or the browser blocks the real request. And anything that whitelists methods will reject QUERY until you tell it not to: reverse proxies, WAFs, API gateways, &lt;code&gt;limit_except&lt;/code&gt; blocks in nginx. The method passing through the wire is the easy part; the config that guards the wire is where you'll spend your time.&lt;/p&gt;

&lt;p&gt;Framework and server support is still landing. New HTTP methods don't come around often. The last one most people reached for was PATCH back in 2010, so the ecosystem moves slowly. Expect native routing helpers and middleware to fill in over the next couple of years rather than overnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you rush to switch?
&lt;/h2&gt;

&lt;p&gt;Probably not, and there's no need to. Your &lt;code&gt;POST /search&lt;/code&gt; endpoints work and aren't going anywhere. QUERY is the more correct tool, not an urgent migration.&lt;/p&gt;

&lt;p&gt;What it does give you is a real answer to a question we've been hacking around for years. When you're designing a new read endpoint that needs a structured body, you now have a method that says exactly what it means: this is a safe, repeatable, cacheable read, and here's the query in the body where it belongs. That's worth reaching for on the next thing you build, even if the old endpoints stay put.&lt;/p&gt;

</description>
      <category>http</category>
      <category>webdev</category>
      <category>api</category>
      <category>backend</category>
    </item>
    <item>
      <title>SolidJS 2.0: A React Developer's First Look at Signals and Async</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Wed, 01 Jul 2026 22:00:00 +0000</pubDate>
      <link>https://dev.to/morellodev/solidjs-20-a-react-developers-first-look-at-signals-and-async-51bn</link>
      <guid>https://dev.to/morellodev/solidjs-20-a-react-developers-first-look-at-signals-and-async-51bn</guid>
      <description>&lt;p&gt;I've shipped React for the better part of a decade. Hooks, Suspense, Server Components, the whole tour. So when the &lt;strong&gt;SolidJS 2.0&lt;/strong&gt; beta showed up in my feed with the tagline &lt;a href="https://github.com/solidjs/solid/releases/tag/v2.0.0-beta.0" rel="noopener noreferrer"&gt;"The &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; is Over,"&lt;/a&gt; I rolled my eyes a little and then clicked anyway.&lt;/p&gt;

&lt;p&gt;I'm glad I did. Solid has been the framework React people admire from a distance for years, and 2.0 is the release that finally made me install it and poke at the reactivity model instead of just reading about it. This post is my honest first look: what's new, what a React developer will recognize, and where Solid quietly does something React can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SolidJS 2.0?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.solidjs.com" rel="noopener noreferrer"&gt;SolidJS&lt;/a&gt; is a UI library with React-like JSX and a completely different engine underneath. Instead of re-rendering components and diffing a virtual DOM, it uses &lt;a href="https://docs.solidjs.com/concepts/intro-to-reactivity" rel="noopener noreferrer"&gt;&lt;strong&gt;fine-grained reactivity&lt;/strong&gt;&lt;/a&gt;: your component runs once, and updates flow directly to the exact DOM nodes that depend on a piece of state.&lt;/p&gt;

&lt;p&gt;The 2.0 line is currently in beta. The first public build, &lt;code&gt;v2.0.0-beta.0&lt;/code&gt;, landed on March 3, 2026, and the team skipped the alpha phase entirely because the milestones planned for it stopped feeling worth their own release. You can try it today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add solid-js@next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The headline of the whole release is async. Solid's reactive graph now understands promises natively, and a lot of the API changes fall out of that one decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine-grained reactivity, from a React brain
&lt;/h2&gt;

&lt;p&gt;Here's the mental model shift, and it's the thing to internalize before anything else makes sense.&lt;/p&gt;

&lt;p&gt;In React, state changes re-run your component function. React then builds a new virtual DOM tree and diffs it against the old one to figure out what actually changed. &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, and dependency arrays exist to keep that re-render machine from doing too much work. The &lt;a href="https://react.dev/learn/react-compiler" rel="noopener noreferrer"&gt;React Compiler&lt;/a&gt; automates a lot of that memoization now, but the underlying model is the same: the component re-runs and React diffs the result.&lt;/p&gt;

&lt;p&gt;In Solid, the component body is setup code. It runs a single time. When you read a signal in your JSX, Solid records that specific dependency and wires it straight to the DOM. Change the signal later and only that text node or attribute updates. No component re-render, and no dependency array to keep honest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Counter&lt;/span&gt;&lt;span class="p"&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;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// This whole function runs ONCE. Only the text node updates on click.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tell for a React dev: &lt;code&gt;count&lt;/code&gt; is a function, not a value. You call &lt;code&gt;count()&lt;/code&gt; to read it. That call is what subscribes the surrounding computation to changes. Once it clicks, the absence of a dependency array stops feeling like something is missing and starts feeling like a bug class that no longer exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  First-class async is the real story
&lt;/h2&gt;

&lt;p&gt;This is where 2.0 earns its version bump. In Solid 1.x, async data meant &lt;code&gt;createResource&lt;/code&gt; and wrapping things in &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;. In 2.0, a derived computation can just return a promise, and the graph handles suspending and resuming for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Solid 1.x&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;data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Solid 2.0: the promise flows through the reactive graph&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suspense gets replaced by &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;, and the semantics are the part I actually care about. &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; is scoped to &lt;em&gt;initial&lt;/em&gt; readiness. It shows a fallback while the subtree can't render anything yet, and then it stays out of your way. When &lt;code&gt;userId()&lt;/code&gt; changes and the data refetches, the UI doesn't tear itself down and flash a spinner. It holds the old content until the new content is ready.&lt;/p&gt;

&lt;p&gt;To show that a refresh is in flight, you reach for &lt;code&gt;isPending&lt;/code&gt;, which reports on pending reactive work without unmounting anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listUsers&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refreshing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;isPending&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Show&lt;/span&gt; &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;refreshing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RefreshIndicator&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Spinner&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserList&lt;/span&gt; &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React 19 gets you most of the way here: put the update in a transition with &lt;a href="https://react.dev/reference/react/useTransition" rel="noopener noreferrer"&gt;&lt;code&gt;useTransition&lt;/code&gt;&lt;/a&gt;, and Suspense holds the old content on screen instead of flashing its fallback, with &lt;code&gt;isPending&lt;/code&gt; reporting the refresh. The difference is that React makes you opt into that at each call site, while Solid bakes the distinction between "we have nothing to show yet" and "we're refreshing what's already on screen" into the primitives themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutations have a home now
&lt;/h2&gt;

&lt;p&gt;For a long time React had no blessed way to do writes, so you rolled your own or reached for a data library. React 19 changed that with &lt;a href="https://react.dev/blog/2024/12/05/react-19" rel="noopener noreferrer"&gt;Actions&lt;/a&gt; and the &lt;a href="https://react.dev/reference/react/useOptimistic" rel="noopener noreferrer"&gt;&lt;code&gt;useOptimistic&lt;/code&gt;&lt;/a&gt; hook, so this is a spot where the two frameworks have converged. Solid 2.0's version is &lt;code&gt;action&lt;/code&gt; for mutations, plus &lt;code&gt;createOptimistic&lt;/code&gt; and &lt;code&gt;createOptimisticStore&lt;/code&gt; for optimistic updates, with &lt;code&gt;refresh&lt;/code&gt; to revalidate afterward.&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createOptimisticStore&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;chatServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadMessages&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// optimistic: show it instantly&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;chatServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// await the server&lt;/span&gt;
  &lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// reconcile with the source of truth&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generator function is doing real work here. Each &lt;code&gt;yield&lt;/code&gt; is a point where the action awaits, and the reactive system tracks the optimistic state until the real value lands and reconciles. Solid has been circling this for a while: &lt;code&gt;@solidjs/router&lt;/code&gt; already shipped &lt;a href="https://docs.solidjs.com/solid-router/reference/data-apis/create-async" rel="noopener noreferrer"&gt;&lt;code&gt;createAsync&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;action&lt;/code&gt; for data loading and mutations. 2.0 takes that thinking and builds async and actions into the core reactive graph, so the patterns aren't router-specific anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deterministic batching, and a gotcha
&lt;/h2&gt;

&lt;p&gt;One behavioral change will trip you up if you're not ready for it. Writes are batched on a microtask, and reads don't reflect a write until the batch flushes.&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// still 0: the update is queued on the microtask&lt;/span&gt;
&lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// apply queued updates synchronously&lt;/span&gt;
&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// now 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;flush()&lt;/code&gt; forces the queue to apply right away when you need to read the result immediately, like focusing an input after a state change. Coming from React's synchronous batching it takes a moment to adjust, but the model is predictable, and that matters once async is woven through everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The renames a React developer should know
&lt;/h2&gt;

&lt;p&gt;Solid 2.0 is a major version, so it cleans house. Since this is beta, some of these identifiers could still shift before stable, but the direction is set, and the &lt;a href="https://github.com/solidjs/solid/blob/next/documentation/solid-2.0/MIGRATION.md" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; tracks where things stand today. The changes that matter most coming from React:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; → &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;&lt;/strong&gt;, with the initial-readiness semantics above. &lt;code&gt;&amp;lt;SuspenseList&amp;gt;&lt;/code&gt; becomes &lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;createEffect&lt;/code&gt; is split&lt;/strong&gt; into a compute phase (what to track) and an apply phase (what to run with the result). Instead of reading signals inside one callback, you pass the two separately:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Solid 1.x&lt;/span&gt;
  &lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;apiCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;signalB&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;

  &lt;span class="c1"&gt;// Solid 2.0&lt;/span&gt;
  &lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signalB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiCall&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;signalA&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;signalB&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;apiCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've ever shipped a bug because a value was in a &lt;code&gt;useEffect&lt;/code&gt; body but missing from the dependency array, separating the two is a direct answer to that. &lt;a href="https://x.com/RyanCarniato" rel="noopener noreferrer"&gt;Ryan Carniato&lt;/a&gt; has also said the idiomatic 2.0 code reaches for &lt;code&gt;createEffect&lt;/code&gt; far less than 1.x did, since async and derived state now cover cases you used to handle with an effect.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;onMount&lt;/code&gt; → &lt;code&gt;onSettled&lt;/code&gt;&lt;/strong&gt;, reflecting the async-aware lifecycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;&amp;lt;Index&amp;gt;&lt;/code&gt; → &lt;code&gt;&amp;lt;For keyed={false}&amp;gt;&lt;/code&gt;&lt;/strong&gt;; in that non-keyed mode the row is passed as an accessor, matching the old &lt;code&gt;&amp;lt;Index&amp;gt;&lt;/code&gt; stability model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store setters are draft-first by default.&lt;/strong&gt; You mutate a draft instead of threading a path. This is 1.x's &lt;code&gt;produce&lt;/code&gt; behavior promoted to the default, so you no longer wrap the callback, with a &lt;code&gt;storePath&lt;/code&gt; helper as the opt-in escape hatch for the old path style:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// 2.0 default: mutate the draft&lt;/span&gt;
  &lt;span class="nf"&gt;setStore&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// Legacy path style, opt-in&lt;/span&gt;
  &lt;span class="nf"&gt;setStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;storePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;classList&lt;/code&gt; is folded into &lt;code&gt;class&lt;/code&gt;&lt;/strong&gt;, which now takes strings, arrays, and objects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;use:&lt;/code&gt; directives are removed&lt;/strong&gt; in favor of &lt;code&gt;ref&lt;/code&gt; directive factories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context is simpler.&lt;/strong&gt; The context is directly usable as the provider, so &lt;code&gt;Context.Provider&lt;/code&gt; is gone. A context created without a default is typed as its value instead of &lt;code&gt;T | undefined&lt;/code&gt;, and &lt;code&gt;useContext&lt;/code&gt; throws &lt;code&gt;ContextNotFoundError&lt;/code&gt; when there's no provider above it; a context created with a default still returns that default:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// no default&lt;/span&gt;
  &lt;span class="c1"&gt;// 2.0: the context is its own provider&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Theme&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Signals are heading toward the standard
&lt;/h2&gt;

&lt;p&gt;The word "signals" is everywhere now, and it's worth knowing why. There's a &lt;a href="https://github.com/tc39/proposal-signals" rel="noopener noreferrer"&gt;TC39 Signals proposal&lt;/a&gt; at Stage 1, with design input from the people behind Angular, Vue, Svelte, Preact, Qwik, MobX, and Solid. Ryan Carniato, Solid's creator, has been part of that conversation for years, and his &lt;a href="https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob"&gt;writing on fine-grained reactivity&lt;/a&gt; helped push it onto the committee's radar.&lt;/p&gt;

&lt;p&gt;Worth being precise here, because it's easy to overstate: Solid 2.0 does not ship the TC39 API as its core. The proposal is synchronous, and Solid 2.0's whole thing is &lt;em&gt;async&lt;/em&gt; reactivity, which goes further than what's currently on the table. Solid influences and tracks the standard rather than implementing it. But the fact that the primitive Solid has bet on for years is now a serious standardization effort tells you the industry is drifting toward the model React chose not to adopt. Svelte 5 already rebuilt its reactivity around signals with its &lt;a href="https://svelte.dev/docs/svelte/what-are-runes" rel="noopener noreferrer"&gt;runes&lt;/a&gt; API, and it's unlikely to be the last framework to make the jump.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you drop React? No. But watch this closely.
&lt;/h2&gt;

&lt;p&gt;I'm not migrating my day job to Solid this quarter, and I don't think you should either. It's a beta, the ecosystem is smaller than React's, and API names are still moving. That's the honest read.&lt;/p&gt;

&lt;p&gt;But 2.0 is the most interesting frontend release I've looked at in a while, precisely because it isn't chasing React. It takes fine-grained reactivity seriously and follows the idea all the way through to async, and the result is a set of primitives that make the hard parts of React (stale closures, dependency arrays, Suspense re-triggering, figuring out where mutations should live) feel like problems you can just stop having.&lt;/p&gt;

&lt;p&gt;If you write React and you've never actually run Solid, this is the release to spend a weekend on. Spin up a project with &lt;code&gt;solid-js@next&lt;/code&gt;, build something small with &lt;code&gt;createSignal&lt;/code&gt; and &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;, and pay attention to how much of your usual React vigilance you can put down.&lt;/p&gt;

&lt;p&gt;The beta is live at &lt;a href="https://github.com/solidjs/solid/releases/tag/v2.0.0-beta.0" rel="noopener noreferrer"&gt;github.com/solidjs/solid&lt;/a&gt;, and the &lt;a href="https://github.com/solidjs/solid/discussions/2596" rel="noopener noreferrer"&gt;announcement discussion&lt;/a&gt; is worth reading in full if any of this piqued your interest.&lt;/p&gt;

</description>
      <category>solidjs</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Rebuilding Defrag98: Getting the Details Right</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Tue, 30 Jun 2026 17:00:00 +0000</pubDate>
      <link>https://dev.to/morellodev/rebuilding-the-windows-98-defrag-simulator-i2p</link>
      <guid>https://dev.to/morellodev/rebuilding-the-windows-98-defrag-simulator-i2p</guid>
      <description>&lt;p&gt;There's a specific kind of satisfaction that comes from watching the Windows 98 defrag screen do its thing. Clusters shuffling around. That progress bar crawling. The sense that your computer was doing something &lt;em&gt;important&lt;/em&gt;, even if you had no idea what.&lt;/p&gt;

&lt;p&gt;That's the feeling I wanted to bottle when I built &lt;a href="https://dev.to/blog/windows-98-defrag-simulator"&gt;the first version of defrag98.com&lt;/a&gt; back in 2024. The project took off beyond anything I'd expected. It got picked up by &lt;a href="https://news.ycombinator.com/item?id=40962195" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt; and &lt;a href="https://www.theverge.com/2024/7/14/24198206/take-a-moment-to-reflect" rel="noopener noreferrer"&gt;The Verge&lt;/a&gt;, and people from all over the world spent time staring at a browser tab the way their younger selves once stared at a CRT monitor. Mission accomplished.&lt;/p&gt;

&lt;p&gt;But the more I looked at it, the more I noticed the cracks.&lt;/p&gt;

&lt;h2&gt;
  
  
  It wasn't quite right
&lt;/h2&gt;

&lt;p&gt;The most significant problem was the simulation logic. The original Windows 98 defragmenter didn't march through clusters sequentially. It read blocks, wrote them, and shuffled them around in a messier, more organic process that made watching it genuinely hypnotic. My first implementation was too orderly. It &lt;em&gt;looked&lt;/em&gt; like a simulation. The real one looked like a machine thinking.&lt;/p&gt;

&lt;p&gt;The legend was missing too. The real Windows 98 defragmenter had a dedicated button that opened a dialog explaining what each block color meant: optimized, reading, writing, bad cluster. Without it, the simulation was just a pretty light show. Now it has one.&lt;/p&gt;

&lt;p&gt;On the visual side, the cluster grid also had a black background, when the real defragmenter used white. A small detail, but exactly the kind of thing that breaks the spell for anyone who actually remembers the original.&lt;/p&gt;

&lt;p&gt;Then there was the progress bar. The real utility didn't draw a smooth, continuous fill. It revealed a row of chunky segmented blocks one at a time, with a "% Complete" caption underneath. Mine was too modern, too clean. Now it fills block by block the way it should, and the window is sized to give it room to breathe.&lt;/p&gt;

&lt;p&gt;Even the controls were missing some manners. The original asked you to confirm before stopping a defrag, and it told you plainly when the job was &lt;em&gt;Paused&lt;/em&gt; or &lt;em&gt;Stopped&lt;/em&gt;. Small things, but they're the difference between a screensaver and a tool you can actually operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rebuild: Next.js to Vite, and a faithful simulation
&lt;/h2&gt;

&lt;p&gt;I moved the project from Next.js to Vite + React. This might sound like a lateral move, but it was the right call for what &lt;a href="https://defrag98.com" rel="noopener noreferrer"&gt;defrag98.com&lt;/a&gt; actually is: a single-page, entirely client-side app with no routing, no server-side data, and no need for SSR. Next.js is excellent at what it does, but fighting its SSR model to build something that runs entirely in the browser felt like wearing a suit to go swimming.&lt;/p&gt;

&lt;p&gt;Vite gave back the dev experience that SSR had been quietly taxing. Faster builds, instant HMR, and a mental model that actually matches the app. While I was in there, the rest of the stack got a refresh too: Tailwind CSS v4 through its native Vite plugin (no more PostCSS pipeline), Base UI for the accessible dialogs, selects and checkboxes, Zustand for state, TypeScript 6, and the Rust-based oxc toolchain (oxlint and oxfmt) in place of ESLint and Prettier for near-instant linting and formatting.&lt;/p&gt;

&lt;p&gt;The bigger change was moving cluster rendering to a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element. Instead of creating and updating hundreds of DOM nodes, I'm now drawing directly to a 2D canvas context. The number of clusters rendered adapts to whichever disk size you select, so a larger virtual drive produces a denser, more satisfying grid. The difference is easy to feel: the animation is smoother, the browser is happier, and it scales without complaint. I also size the canvas before the first paint, which killed the layout shift that used to jump around on mobile.&lt;/p&gt;

&lt;p&gt;On the simulation side, I rewrote the defrag algorithm to properly model read and write operations on individual clusters, with non-sequential processing that mirrors how the original utility actually behaved. If you remember the way blocks would jump around seemingly at random before gradually settling into order, that's what it does now.&lt;/p&gt;

&lt;p&gt;The sound got some attention too. The hard disk loop and the completion chime were re-encoded to a fraction of their old size (the HDD loop went from over a megabyte to around 350KB), shipped as Opus/WebM with an AAC fallback, and the Win98 fonts are preloaded now so nothing flashes on first paint. There are also toggles in Settings to mute the hard disk sound or the mouse clicks, and your choice is remembered the next time you visit.&lt;/p&gt;

&lt;p&gt;When a defrag finishes, you now get the authentic Windows 98 "Disk Defragmenter" message box telling you it's done, chime and all, instead of the run just quietly ending.&lt;/p&gt;

&lt;p&gt;A couple of things changed off-screen that I care about as much as the visible ones. I dropped Google Analytics and Tag Manager in favor of Cloudflare's cookieless Web Analytics, so there's no tracking cookie and no consent banner getting between you and the nostalgia. The site is hosted on Cloudflare Workers as static assets, and it ships an &lt;code&gt;llms.txt&lt;/code&gt; so the AI crawlers that come poking around get a clean description of what this thing is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://defrag98.com" rel="noopener noreferrer"&gt;defrag98.com&lt;/a&gt; also ships a web app manifest, so on supported browsers you can install it to your device and launch it like a native app. And it now runs fully offline: a service worker built with vite-plugin-pwa and Workbox precaches the app shell and every runtime asset, the fonts, cursors, audio, and icons, so once you've opened it once you can defragment a virtual drive on a plane with no signal. New builds activate quietly on the next launch, with no update prompt to nag you. Feels right for something built to feel like software from another era.&lt;/p&gt;

&lt;p&gt;There's also a hidden Easter egg somewhere in the app. I'll leave it at that. If you grew up on Windows 98, you'll know it when you see it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quieter ask
&lt;/h2&gt;

&lt;p&gt;The original version had a donation banner styled as an authentic Windows ME system notification: yellow background, full viewport width, sliding down from the top of the screen five seconds into your session. It was accurate to the era. It was also an interruption, landing right in the middle of the nostalgic moment it was supposed to be protecting.&lt;/p&gt;

&lt;p&gt;The data confirmed it. Despite solid traffic, very few visitors converted to donors.&lt;/p&gt;

&lt;p&gt;The new approach takes a cue from pre-release Windows software, the small line of unobtrusive text in the corner of the screen that says something like &lt;em&gt;"This is a pre-release version"&lt;/em&gt;. I added a quiet line in the bottom-right of the desktop area, linking to my &lt;a href="https://www.buymeacoffee.com/morellodev" rel="noopener noreferrer"&gt;Buy Me a Coffee page&lt;/a&gt;. It's there if you want it. It doesn't demand your attention. The only other time donating comes up is in that completion message box, once the defrag has actually finished and you've had your moment: a gentle Yes or No, never an interruption. The minimum donation has also dropped from $5 to $3, because the goal was never to extract money. It's to help cover the domain and hosting costs that keep &lt;a href="https://defrag98.com" rel="noopener noreferrer"&gt;defrag98.com&lt;/a&gt; running for anyone who gets something out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this still matters to me
&lt;/h2&gt;

&lt;p&gt;I've received $148 in total donations since launching. That's not a business. But it's proof that people feel something when they use it, enough to voluntarily send a few dollars to a stranger on the internet for a free toy.&lt;/p&gt;

&lt;p&gt;That's the part that keeps me iterating on this instead of moving on. It's not a portfolio piece. It's a tiny time machine, and apparently enough people want to visit 1998 for a few minutes that keeping the lights on feels worth caring about.&lt;/p&gt;

&lt;p&gt;If you haven't tried it yet, &lt;a href="https://defrag98.com" rel="noopener noreferrer"&gt;go defragment something&lt;/a&gt;. If you used to stare at this screen as a kid, I hope it hits the way I intended. And if you find the Easter egg, well, you'll know what to do.&lt;/p&gt;

</description>
      <category>react</category>
      <category>vite</category>
      <category>webdev</category>
      <category>retro</category>
    </item>
    <item>
      <title>Configuring My Site for AI Discoverability</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Tue, 21 Apr 2026 08:02:58 +0000</pubDate>
      <link>https://dev.to/morellodev/configuring-my-site-for-ai-discoverability-1j38</link>
      <guid>https://dev.to/morellodev/configuring-my-site-for-ai-discoverability-1j38</guid>
      <description>&lt;p&gt;A growing share of web traffic doesn't come from people anymore. It comes from models reading on their behalf. ChatGPT, Claude, Perplexity, Copilot. They fetch a handful of pages, summarize, and ship the answer back. If your site isn't readable by those agents, you don't exist to them.&lt;/p&gt;

&lt;p&gt;People are calling this &lt;a href="https://wikipedia.org/wiki/Generative_engine_optimization" rel="noopener noreferrer"&gt;GEO&lt;/a&gt;, short for Generative Engine Optimization. It overlaps with SEO but the priorities are different. Agents don't care about your layout. They care about your prose, your metadata, and how many tokens it costs them to read you.&lt;/p&gt;

&lt;p&gt;This post covers how I configured this site for GEO. The first half is framework-agnostic. The second half is specific to my setup on Cloudflare, and includes a deliberate choice that fails a popular GEO audit. I'll explain why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: general GEO techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Serve raw Markdown alongside HTML
&lt;/h3&gt;

&lt;p&gt;The single biggest GEO win is giving agents a version of each page without the navigation, styling, and scripts. HTML is designed for browsers. Markdown is designed for readers, human or otherwise. Agents spend their context window on your prose, not your DOM.&lt;/p&gt;

&lt;p&gt;Every blog post on this site has a mirror URL with a &lt;code&gt;.md&lt;/code&gt; suffix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/blog/my-post&lt;/code&gt; is the full HTML page for humans&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/blog/my-post.md&lt;/code&gt; is the raw Markdown, served as &lt;code&gt;text/markdown&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Astro, this is a two-line route at &lt;code&gt;src/pages/blog/[slug].md.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPostById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;formatPostMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/markdown; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both variants are pre-generated at build time. Same content, &lt;strong&gt;roughly half the tokens&lt;/strong&gt; for an agent to consume.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advertise the Markdown version in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Agents landing on the HTML need to know the Markdown exists. A single &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; in the head does it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/markdown"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/blog/my-post.md"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browsers ignore this tag. Agents that parse the head follow it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publish an &lt;code&gt;llms.txt&lt;/code&gt; index
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt; is a convention for a Markdown file at the root of your site listing your content with short descriptions and links. Think of it as a sitemap an LLM can actually read.&lt;/p&gt;

&lt;p&gt;I ship two variants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/llms.txt&lt;/code&gt; is the index. Title, description, one line per post with a link to its &lt;code&gt;.md&lt;/code&gt; version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/llms-full.txt&lt;/code&gt; is the full corpus. Every post body concatenated into a single response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why both? An agent researching a specific topic can fetch &lt;code&gt;llms.txt&lt;/code&gt;, pick the relevant links, and pull them. An agent doing deep research on the site as a whole fetches &lt;code&gt;llms-full.txt&lt;/code&gt; once and has everything it needs in one request. Either way there's no crawling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Declare your AI stance in &lt;code&gt;robots.txt&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;robots.txt&lt;/code&gt; now carries a &lt;code&gt;Content-Signal&lt;/code&gt; directive for AI use. Mine reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-agent: *
Content-Signal: search=yes, ai-train=no, ai-input=yes
Allow: /
Sitemap: https://morello.dev/sitemap-index.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three independent knobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;search=yes&lt;/code&gt; lets search engines index&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ai-train=no&lt;/code&gt; says my content is not for training data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ai-input=yes&lt;/code&gt; says my content &lt;em&gt;can&lt;/em&gt; be retrieved and used as input for AI answers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the stance I'm comfortable with. I want to show up when someone asks Claude about something I've written; I just don't want my posts absorbed into the next base model.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Whether any given operator actually honors this is another question. The signal's there regardless, and I'd rather be on record than silent about it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Add structured data that actually describes the content
&lt;/h3&gt;

&lt;p&gt;Most blogs ship JSON-LD schema by reflex. Few of them include the fields that help a generative engine decide whether your article is worth fetching.&lt;/p&gt;

&lt;p&gt;On each post I emit a &lt;code&gt;BlogPosting&lt;/code&gt; graph with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wordCount&lt;/code&gt; and &lt;code&gt;timeRequired&lt;/code&gt; (ISO 8601 duration), so an agent can estimate how much context it'll spend before fetching&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;articleBody&lt;/code&gt;, the full text machine-readable, with no HTML parsing required&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;author&lt;/code&gt; linked to a &lt;code&gt;Person&lt;/code&gt; node with &lt;code&gt;knowsAbout&lt;/code&gt; so the entity is grounded in real topics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BreadcrumbList&lt;/code&gt; for site hierarchy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of it goes into a single &lt;code&gt;@graph&lt;/code&gt; per page rather than scattered &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags, which makes it cheaper for an engine to walk from post to author to site without cross-referencing.&lt;/p&gt;

&lt;h3&gt;
  
  
  A sitemap that actually tracks freshness
&lt;/h3&gt;

&lt;p&gt;If you regenerate your sitemap once and never look at it again, you're wasting a signal. Every URL in mine carries a &lt;code&gt;lastmod&lt;/code&gt; timestamp pulled from the post's &lt;code&gt;updatedDate&lt;/code&gt; frontmatter, falling back to &lt;code&gt;pubDate&lt;/code&gt;. When I edit an old post, its &lt;code&gt;lastmod&lt;/code&gt; moves forward and crawlers reprioritize it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validate with real tools
&lt;/h3&gt;

&lt;p&gt;Two tools I found useful while iterating on all of the above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://isitagentready.com/" rel="noopener noreferrer"&gt;isitagentready.com&lt;/a&gt; audits across five categories: discoverability, content accessibility, bot access control, protocol discovery, and commerce. The bot access control checks (&lt;code&gt;Content-Signal&lt;/code&gt;, Web Bot Auth, AI bot rules) are the part that actually influences how agents treat your content.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://acceptmarkdown.com/" rel="noopener noreferrer"&gt;acceptmarkdown.com&lt;/a&gt; has a narrower focus. It checks whether your site responds to &lt;code&gt;Accept: text/markdown&lt;/code&gt; with a Markdown body, includes &lt;code&gt;Vary: Accept&lt;/code&gt;, returns &lt;code&gt;406&lt;/code&gt; for unsupported types, and parses q-values correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll come back to the second one at the end of the post, because my site deliberately fails it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: the Cloudflare-specific setup
&lt;/h2&gt;

&lt;p&gt;General GEO gets you most of the way there. The rest is delivery. How fast you respond, whether the edge caches correctly, and how you advertise your agent-facing resources without waiting for someone to parse your HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static assets, zero Worker invocations
&lt;/h3&gt;

&lt;p&gt;My &lt;code&gt;wrangler.jsonc&lt;/code&gt; points a &lt;code&gt;./dist&lt;/code&gt; directory at &lt;a href="https://developers.cloudflare.com/workers/static-assets/" rel="noopener noreferrer"&gt;Cloudflare's assets deployment&lt;/a&gt;, with no &lt;code&gt;main&lt;/code&gt; entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"morellodev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compatibility_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-18"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"directory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"html_handling"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"drop-trailing-slash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"not_found_handling"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"404-page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every request goes straight from the edge asset cache. HTML, Markdown, &lt;code&gt;llms.txt&lt;/code&gt;, sitemap, RSS. Same path for all of them, and no Worker ever runs. On the Workers Free tier this matters. A crawler sweep that would otherwise eat into 100k daily invocations now costs me nothing. Agents, for better or worse, don't fingerprint politely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advertise discovery endpoints in a &lt;code&gt;Link&lt;/code&gt; header
&lt;/h3&gt;

&lt;p&gt;Cloudflare's &lt;a href="https://developers.cloudflare.com/workers/static-assets/headers/" rel="noopener noreferrer"&gt;&lt;code&gt;_headers&lt;/code&gt; file&lt;/a&gt; lets you ship response headers without any server code. I use it to tell every response, not just HTML ones, where the agent-facing files live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*
  Link: &amp;lt;/sitemap-index.xml&amp;gt;; rel="sitemap",
        &amp;lt;/rss.xml&amp;gt;; rel="alternate"; type="application/rss+xml"; title="RSS",
        &amp;lt;/llms.txt&amp;gt;; rel="describedby"; type="text/plain",
        &amp;lt;/llms-full.txt&amp;gt;; rel="describedby"; type="text/plain"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A crawler doing a &lt;code&gt;HEAD&lt;/code&gt; against any URL on the site sees all four links before it parses a single byte of HTML. &lt;strong&gt;One round-trip, no body, full discovery.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Long-lived cache for hashed assets
&lt;/h3&gt;

&lt;p&gt;Astro emits fingerprinted filenames under &lt;code&gt;/_astro/&lt;/code&gt;, so those can sit in cache for a year:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/_astro/*
  Cache-Control: public, max-age=31536000, immutable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Faster first paint for humans, cheaper crawls for agents. Same lever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I skipped &lt;code&gt;Accept: text/markdown&lt;/code&gt; content negotiation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://acceptmarkdown.com/" rel="noopener noreferrer"&gt;acceptmarkdown.com&lt;/a&gt; will tell you this site doesn't do content negotiation. No &lt;code&gt;Vary: Accept&lt;/code&gt;, no &lt;code&gt;406&lt;/code&gt;, no Markdown from the canonical URL. That's not an oversight. I tried it, shipped it briefly, and rolled it back.&lt;/p&gt;

&lt;p&gt;The reason is Cloudflare's free plan. Custom cache keys are Enterprise-only, and &lt;a href="https://developers.cloudflare.com/cache/concepts/cache-control/" rel="noopener noreferrer"&gt;their docs are explicit&lt;/a&gt; that &lt;code&gt;Vary: Accept&lt;/code&gt; is ignored for caching decisions. The edge collapses every variant of &lt;code&gt;/blog/my-post&lt;/code&gt; into one cache entry, so the first requester's format &lt;strong&gt;poisons the cache for everyone else&lt;/strong&gt; until TTL expires.&lt;/p&gt;

&lt;p&gt;The workaround is a Worker that bypasses the edge cache. But now every &lt;code&gt;/blog/*&lt;/code&gt; request burns a Worker invocation, humans included, and the &lt;a href="https://developers.cloudflare.com/workers/platform/pricing/" rel="noopener noreferrer"&gt;Workers Free plan&lt;/a&gt; gives you 100k per day and 10ms of CPU each. That's a real budget to share across humans and bots, for no functional gain over a static &lt;code&gt;.md&lt;/code&gt; URL.&lt;/p&gt;

&lt;p&gt;So I deleted the Worker. The only thing I lost is &lt;code&gt;curl -H "Accept: text/markdown" …/blog/my-post&lt;/code&gt; returning Markdown. Between &lt;code&gt;llms.txt&lt;/code&gt;, &lt;code&gt;&amp;lt;link rel="alternate"&amp;gt;&lt;/code&gt;, and the &lt;code&gt;/blog/[slug].md&lt;/code&gt; convention, no mainstream agent I've seen actually needs &lt;code&gt;Accept:&lt;/code&gt; negotiation. It's the more elegant protocol; alternate URLs are the more robust one on a free-tier CDN. On a paid plan I'd probably do both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leaves things
&lt;/h2&gt;

&lt;p&gt;Every page exists in two forms, both served from the edge. Agent-facing resources are advertised in response headers on every request, before any HTML gets parsed. Structured data tells engines what the article is and how much context it takes to read. &lt;code&gt;robots.txt&lt;/code&gt; says what I'll allow and what I won't.&lt;/p&gt;

&lt;p&gt;GEO is still very new. The standards are half-drafted, the tools disagree with each other, and half the signals I described above didn't exist two years ago. I fully expect to be rewriting parts of this post within six months, probably with a different opinion about Accept-based negotiation, once I've either moved off the free plan or found a workaround that doesn't involve a Worker. But for now: serve agents a version they can cheaply consume, be explicit about what you'll allow, and accept that the defaults aren't on your side.&lt;/p&gt;

&lt;p&gt;If you're reading this via a summary from some assistant, hi. Thanks for the traffic.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>seo</category>
      <category>llm</category>
    </item>
    <item>
      <title>Git Worktrees Are Underrated</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Mon, 16 Mar 2026 22:07:48 +0000</pubDate>
      <link>https://dev.to/morellodev/git-worktrees-are-underrated-2k9</link>
      <guid>https://dev.to/morellodev/git-worktrees-are-underrated-2k9</guid>
      <description>&lt;p&gt;If you've ever stashed half-finished work to review a PR, or lost a train of thought switching branches, you should know about git worktrees. They've been in git since 2015, but most developers I talk to have never heard of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with branches
&lt;/h2&gt;

&lt;p&gt;The standard git workflow is linear. You're on a branch, you need to context-switch, so you stash your changes (or worse, make a throwaway commit), switch branches, do your thing, switch back, and pop the stash. If you're lucky, nothing conflicts.&lt;/p&gt;

&lt;p&gt;This gets worse when you're juggling multiple things at once. Reviewing a colleague's PR while your feature branch is mid-refactor. Running tests on one branch while coding on another. Hotfixing production while your main work sits frozen.&lt;/p&gt;

&lt;p&gt;A git repository has one working directory. One branch checked out at a time. Everything else has to wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worktrees
&lt;/h2&gt;

&lt;p&gt;Git worktrees let you check out multiple branches simultaneously, each in its own directory. They share the same &lt;code&gt;.git&lt;/code&gt; history and objects, so you're not cloning the entire repo, but each one has its own working directory and index.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../feat-login feat/login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now &lt;code&gt;feat/login&lt;/code&gt; is checked out in &lt;code&gt;../feat-login&lt;/code&gt;. You can open it in a separate editor window, run tests there, or just let it sit while you work on something else.&lt;/p&gt;

&lt;p&gt;When you're done:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree remove ../feat-login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Why nobody uses them
&lt;/h2&gt;

&lt;p&gt;If worktrees are so useful, why aren't they more popular? I think it comes down to two things.&lt;/p&gt;

&lt;p&gt;First, the directory problem. Every time you create a worktree, you have to pick a path. There's no convention for where they go. After a week you end up with directories scattered across your filesystem: &lt;code&gt;../feat-login&lt;/code&gt;, &lt;code&gt;~/tmp/hotfix-auth&lt;/code&gt;, &lt;code&gt;../../pr-review&lt;/code&gt;. Good luck remembering where anything is.&lt;/p&gt;

&lt;p&gt;Second, there are no ergonomics. You can't easily list your worktrees with status info, cd into one quickly, or run something like &lt;code&gt;npm install&lt;/code&gt; in the new directory after checkout. The raw git commands work, but they don't make worktrees feel like a real workflow.&lt;/p&gt;

&lt;p&gt;These aren't fundamental problems with worktrees. They're tooling gaps. That's why I built &lt;a href="https://github.com/morellodev/arbor" rel="noopener noreferrer"&gt;arbor&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  A better workflow with arbor
&lt;/h2&gt;

&lt;p&gt;Arbor is a CLI that manages git worktrees. It keeps them organized in a central directory (&lt;code&gt;~/.arbor/worktrees/&lt;/code&gt;) and handles the stuff that plain git doesn't.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone a repo (sets up a bare repo + default branch worktree)&lt;/span&gt;
arbor clone user/my-app

&lt;span class="c"&gt;# Create a worktree for a new feature&lt;/span&gt;
arbor add feat/login

&lt;span class="c"&gt;# Shell integration auto-cds you into the worktree&lt;/span&gt;
&lt;span class="c"&gt;# You're now in ~/.arbor/worktrees/my-app/feat-login&lt;/span&gt;

&lt;span class="c"&gt;# Need to review a PR? Open another worktree&lt;/span&gt;
arbor add fix/auth-bug

&lt;span class="c"&gt;# See all your worktrees with dirty/clean status&lt;/span&gt;
arbor status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every worktree for &lt;code&gt;my-app&lt;/code&gt; lives under &lt;code&gt;~/.arbor/worktrees/my-app/&lt;/code&gt;. Branch slashes become dashes in directory names, so the filesystem stays tidy. When you're done with a branch:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;arbor &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; feat/login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That removes the worktree and the local branch in one step.&lt;/p&gt;
&lt;h2&gt;
  
  
  What makes it stick
&lt;/h2&gt;

&lt;p&gt;I've been using worktrees for a while, but I kept falling off because of the friction. A few things in arbor made the difference for me.&lt;/p&gt;

&lt;p&gt;Shell integration. &lt;code&gt;arbor init&lt;/code&gt; sets up a wrapper so that &lt;code&gt;arbor add&lt;/code&gt; and &lt;code&gt;arbor switch&lt;/code&gt; automatically cd you into the worktree directory. Without it you create the worktree and then have to &lt;code&gt;cd&lt;/code&gt; into it yourself every single time, which gets old fast.&lt;/p&gt;

&lt;p&gt;Post-create hooks. You can add an &lt;code&gt;.arbor.toml&lt;/code&gt; to your repo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[hooks]&lt;/span&gt;
&lt;span class="py"&gt;post_create&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"npm install"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every new worktree gets its dependencies installed automatically. You can chain commands too:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[hooks]&lt;/span&gt;
&lt;span class="py"&gt;post_create&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"npm install"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cp .env.example .env"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And &lt;code&gt;arbor status --all&lt;/code&gt; shows the state of every worktree across all your repos, which is nice for a quick scan before you call it a day.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where I use them most
&lt;/h2&gt;

&lt;p&gt;PR reviews. &lt;code&gt;arbor add&lt;/code&gt; the branch, review it in a separate editor window, remove it when done. My own work is untouched.&lt;/p&gt;

&lt;p&gt;Long test suites. Tests running on one branch, coding on another. No waiting around.&lt;/p&gt;

&lt;p&gt;Hotfixes. &lt;code&gt;arbor add hotfix/critical&lt;/code&gt;, fix it, push it, remove it. Back to what I was doing.&lt;/p&gt;

&lt;p&gt;Comparing behavior. Two worktrees open side by side, one on &lt;code&gt;main&lt;/code&gt; and one on my feature branch. Way easier than switching back and forth.&lt;/p&gt;

&lt;p&gt;Big migrations. Upgrading a framework to a new major version in a worktree while the rest of the team keeps shipping from &lt;code&gt;main&lt;/code&gt;. No half-broken build blocking anyone.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Arbor is open source, written in Rust. Install with Homebrew:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;morellodev/tap/arbor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or grab a binary from the &lt;a href="https://github.com/morellodev/arbor/releases/latest" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;arbor init&lt;/code&gt; after installing to set up shell integration. Takes about 30 seconds.&lt;/p&gt;

&lt;p&gt;If you've never tried worktrees, give them a shot. And if you have but gave up because of the directory mess, this might be worth another look.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/morellodev" rel="noopener noreferrer"&gt;
        morellodev
      &lt;/a&gt; / &lt;a href="https://github.com/morellodev/arbor" rel="noopener noreferrer"&gt;
        arbor
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A friendly CLI for managing git worktrees
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🌳 arbor&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/morellodev/arbor/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/morellodev/arbor/actions/workflows/ci.yml/badge.svg" alt="Test &amp;amp; Lint"&gt;&lt;/a&gt;
&lt;a href="https://github.com/morellodev/arbor/releases/latest" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f3acaace43b060d086262a3945d6baf0769e50f291ecbf02c4537585761980ba/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f6d6f72656c6c6f6465762f6172626f72" alt="GitHub Release"&gt;&lt;/a&gt;
&lt;a href="https://github.com/morellodev/arbor/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667" alt="License: MIT"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A CLI for managing git worktrees. It keeps all your worktrees under &lt;code&gt;~/.arbor/worktrees&lt;/code&gt; so you can switch between branches without stashing or losing context.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/morellodev/arbor/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fmorellodev%2Farbor%2Fdemo.gif" alt="demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why arbor?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Git worktrees are great: multiple branches checked out at once, no stashing, no half-finished commits. The problem is that &lt;code&gt;git worktree add&lt;/code&gt; makes you pick a directory every time, and you end up with worktrees scattered everywhere.&lt;/p&gt;
&lt;p&gt;Arbor puts them all in one place. &lt;code&gt;arbor add feat/login&lt;/code&gt; creates the worktree, and with shell integration it &lt;code&gt;cd&lt;/code&gt;s you into it too. &lt;code&gt;arbor rm feat/login&lt;/code&gt; cleans it up.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Who is this for?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;If you review PRs while working on your own feature, juggle hotfixes alongside long-running branches, or run tests in one worktree while coding in another — arbor keeps everything organized.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Homebrew (macOS and Linux)&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;brew install morellodev/tap/arbor&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Pre-built binaries (macOS, Linux, Windows)&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Download the latest binary for your platform from…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/morellodev/arbor" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




</description>
      <category>git</category>
      <category>cli</category>
      <category>productivity</category>
      <category>rust</category>
    </item>
    <item>
      <title>Recreating History: Building a Windows 98 Disk Defrag Simulator with Modern Web Tech</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Sun, 14 Jul 2024 09:41:54 +0000</pubDate>
      <link>https://dev.to/morellodev/recreating-history-building-a-windows-98-disk-defrag-simulator-with-modern-web-tech-34bc</link>
      <guid>https://dev.to/morellodev/recreating-history-building-a-windows-98-disk-defrag-simulator-with-modern-web-tech-34bc</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%2Fysehm1enkypm8d71gnxj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysehm1enkypm8d71gnxj.gif" alt="Defragmenting a disk in Windows 98 Disk Defrag Simulator" width="559" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hey fellow devs! I'm Dennis Morello, a Senior Frontend Engineer with a passion for both cutting-edge web technologies and retro computing. I'm excited to share my latest project that combines these interests: a faithful recreation of the Windows 98 Disk Defragmenter, built entirely for the web.&lt;/p&gt;

&lt;p&gt;Check it out: &lt;a href="https://defrag98.com" rel="noopener noreferrer"&gt;defrag98.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Update 19 Jul 2024: We're on Product Hunt! 🚀
&lt;/h2&gt;

&lt;p&gt;Great news, everyone! The Windows 98 Disk Defragmenter Simulator is now &lt;a href="https://www.producthunt.com/posts/windows-98-disk-defrag-simulator" rel="noopener noreferrer"&gt;live on Product Hunt&lt;/a&gt;. This is an exciting milestone for the project, and I'm thrilled to share it with a wider audience of tech enthusiasts and nostalgia lovers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.producthunt.com/posts/windows-98-disk-defrag-simulator?embed=true&amp;amp;utm_source=badge-featured&amp;amp;utm_medium=badge&amp;amp;utm_souce=badge-windows-98-disk-defrag-simulator" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapi.producthunt.com%2Fwidgets%2Fembed-image%2Fv1%2Ffeatured.svg%3Fpost_id%3D472182%26theme%3Dlight" alt="Windows 98 Disk Defrag Simulator - Relive the 90s: Windows 98 Defrag Simulator Nostalgia | Product Hunt" width="250" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Update 14 Jul 2024: Featured on The Verge and Hacker News!
&lt;/h2&gt;

&lt;p&gt;The project has been featured on &lt;a href="https://news.ycombinator.com/item?id=40962195" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt; and &lt;a href="https://www.theverge.com/2024/7/14/24198206/take-a-moment-to-reflect" rel="noopener noreferrer"&gt;The Verge&lt;/a&gt;! Thank you to everyone who has tried it out and shared their love for this blast from the past.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;For this project, I leveraged some of the most powerful tools in modern web development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React&lt;/strong&gt;: For building the UI components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt;: To optimize performance and SEO&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zustand&lt;/strong&gt;: To manage the state of the app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TailwindCSS&lt;/strong&gt;: To style the app, along with &lt;a href="https://jdan.github.io/98.css" rel="noopener noreferrer"&gt;98.css&lt;/a&gt; for bringing in the Windows 98 aesthetic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Radix UI Primitives&lt;/strong&gt;: For accessible interactive components like sliders and modals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt;: The hosting platform for the app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges and Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Recreating the Defragmentation Algorithm
&lt;/h3&gt;

&lt;p&gt;One of the biggest challenges was implementing a defragmentation algorithm that felt authentic. I created a custom algorithm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Randomly selects clusters to process&lt;/li&gt;
&lt;li&gt;Simulates file movements across the disk&lt;/li&gt;
&lt;li&gt;Adjusts processing speed based on the selected virtual drive&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Pixel-Perfect UI Recreation
&lt;/h3&gt;

&lt;p&gt;Achieving the exact look and feel of Windows 98 required meticulous attention to detail. I used a combination of 98.css and TailwindCSS to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Match colors precisely&lt;/li&gt;
&lt;li&gt;Recreate the characteristic 'chunky' borders&lt;/li&gt;
&lt;li&gt;Implement the classic Windows 98 typography&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Simulating Hard Drive Sounds
&lt;/h3&gt;

&lt;p&gt;To add an extra layer of nostalgia, I implemented realistic hard drive sounds. This involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recording and editing authentic HDD sounds&lt;/li&gt;
&lt;li&gt;Leveraging the Web Audio API for precise playback control&lt;/li&gt;
&lt;li&gt;Synchronizing sound effects with the visual defragmentation process&lt;/li&gt;
&lt;li&gt;Adapting the HDD sounds to the chosen drive speed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;This project was a fantastic opportunity to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deep dive into the intricacies of writing a custom defrag algorithm, and find a balance between performance and simulation accuracy&lt;/li&gt;
&lt;li&gt;Explore the challenges of accurately simulating legacy software&lt;/li&gt;
&lt;li&gt;Push the boundaries of what's possible in browser-based applications&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;This is a project I started just for fun, but I'm excited to see where it goes. I'm looking forward to continuing to improve the app, and adding more features as feedbacks from users come in.&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts, suggestions, or questions about this project. Have you worked on similar retro tech simulations? What challenges did you face?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>nextjs</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>5 Things You Might Not Know About JavaScript</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Thu, 08 Jun 2023 06:50:30 +0000</pubDate>
      <link>https://dev.to/morellodev/5-things-you-might-not-know-about-javascript-5dck</link>
      <guid>https://dev.to/morellodev/5-things-you-might-not-know-about-javascript-5dck</guid>
      <description>&lt;p&gt;JavaScript is a programming language that has been around for over 20 years. It's used in many different ways, but what are some things you might not know about it? In this blog post, we'll explore 5 things you might not know about JavaScript.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Original post at &lt;a href="https://morello.dev/articles/five-things-you-might-not-know-about-javascript" rel="noopener noreferrer"&gt;https://morello.dev/articles/five-things-you-might-not-know-about-javascript&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;JavaScript has undoubtedly become one of the most essential programming languages in the world of web development. As the backbone of interactive web pages, it enables developers to create dynamic and engaging user experiences. You might already be familiar with the basics of JavaScript, but there are numerous lesser-known aspects and hidden gems within the language that can enhance your coding skills and expand your understanding.&lt;/p&gt;

&lt;p&gt;In this blog post, we'll delve into 5 intriguing things about JavaScript that you might not be aware of. Whether you're ready to impress your colleagues with newfound JavaScript knowledge or simply eager to enhance your own programming prowess, this blog post is for you. Let's dive in and uncover the hidden gems that await us in the vast world of JavaScript!&lt;/p&gt;

&lt;h2&gt;
  
  
  1 - "JavaScript" is trademarked by Oracle
&lt;/h2&gt;

&lt;p&gt;The name "JavaScript" is a &lt;a href="https://tsdr.uspto.gov/#caseNumber=75026640&amp;amp;caseType=SERIAL_NO&amp;amp;searchType=statusSearch" rel="noopener noreferrer"&gt;trademark of Oracle Corporation&lt;/a&gt; in the United States. The trademark was originally owned by Netscape Communications Corporation, which created the language in 1995. In 1996, Netscape transferred the trademark to Sun Microsystems, which was acquired by Oracle in 2010.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A trademark is a legal protection that grants exclusive rights to use a particular word, phrase, symbol, or design to identify and distinguish a product or service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a trademark holder, Oracle has the authority to control the usage of the term "JavaScript" within the United States. This means that other entities must seek permission from Oracle or adhere to specific guidelines if they wish to use the term in a way that may be covered by the trademark. Unauthorized use of a trademarked term can lead to legal repercussions.&lt;/p&gt;

&lt;p&gt;It is important to note that while the term "JavaScript" is trademarked by Oracle in the United States, the programming language itself is an open standard governed by the ECMAScript specification. This means that the language can still be used and implemented by developers and organizations globally, regardless of the trademark ownership.&lt;/p&gt;

&lt;p&gt;In 2022, Node.js and Deno creator &lt;a href="https://tinyclouds.org" rel="noopener noreferrer"&gt;Ryan Dahl&lt;/a&gt; wrote an open letter titled &lt;a href="https://tinyclouds.org/trademark" rel="noopener noreferrer"&gt;Dear Oracle, Please Release the JavaScript Trademark&lt;/a&gt;, where he urges Oracle to release JavaScript trademark.&lt;/p&gt;

&lt;h2&gt;
  
  
  2 - JavaScript and ECMAScript are not the same thing
&lt;/h2&gt;

&lt;p&gt;JavaScript is often referred to as &lt;em&gt;ECMAScript&lt;/em&gt;, but the two terms are not interchangeable. JavaScript is a programming language, while ECMAScript is a standardized specification that defines the syntax, semantics, and features of the JavaScript language. It provides a standardized framework for implementing JavaScript across different platforms and browsers.&lt;/p&gt;

&lt;p&gt;ECMAScript is maintained by &lt;a href="https://www.ecma-international.org" rel="noopener noreferrer"&gt;Ecma International&lt;/a&gt;, an industry association, and undergoes regular updates to introduce new features and enhancements.&lt;/p&gt;

&lt;p&gt;It is important to note that different versions of JavaScript correspond to different editions of the ECMAScript specification. For example, ECMAScript 5 (ES5) introduced significant improvements and features to the JavaScript language, while ECMAScript 6 (ES6) brought about even more significant changes, such as arrow functions, classes, and modules.&lt;/p&gt;

&lt;p&gt;In summary, JavaScript is the widely used programming language that follows the ECMAScript specification. ECMAScript provides the standardization and guidelines for implementing JavaScript across platforms, ensuring compatibility and allowing developers to write code that can be executed consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  3 - JavaScript is a dynamically typed language
&lt;/h2&gt;

&lt;p&gt;As in every programming language, in JavaScript variables and values have types associated with them. Although JavaScript is often referred to as a &lt;em&gt;dynamically typed&lt;/em&gt; language, meaning that variables can hold values of any type, it does have a type system that determines how values can be used and manipulated.&lt;/p&gt;

&lt;p&gt;JavaScript employs a loose type system, where type coercion can occur implicitly, allowing for flexibility in operations between different types. For example, the &lt;code&gt;+&lt;/code&gt; operator can be used to add numbers or concatenate strings, but also to cast a value to a number.&lt;/p&gt;

&lt;p&gt;There is also an operator called &lt;code&gt;typeof&lt;/code&gt; that can be used to determine the type of a value at a given point in time. It returns a string representing the type of the operand.&lt;/p&gt;

&lt;h2&gt;
  
  
  4 - JavaScript has seven primitive types
&lt;/h2&gt;

&lt;p&gt;As of &lt;a href="https://262.ecma-international.org/11.0" rel="noopener noreferrer"&gt;ECMAScript 2020&lt;/a&gt; release, JavaScript has seven primitive types, which are the building blocks of all JavaScript values:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Boolean&lt;/strong&gt;: Represents a logical value of either &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Null&lt;/strong&gt;: Represents the intentional absence of any object value. It is a primitive type, but its sole value is &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Undefined&lt;/strong&gt;: Represents a variable that has been declared but has not been assigned a value. If a variable is declared but not assigned a value, it is automatically assigned the value of &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Number&lt;/strong&gt;: Represents numeric values, including integers and floating-point numbers. JavaScript uses the double-precision 64-bit binary format (&lt;a href="https://en.wikipedia.org/wiki/IEEE_754" rel="noopener noreferrer"&gt;IEEE 754&lt;/a&gt;) to represent numbers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;BigInt&lt;/strong&gt;: Introduced in ECMAScript 2020, BigInt is a primitive type that provides a way to represent arbitrarily large integers. It allows developers to perform operations on numbers beyond the limits of the Number type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;String&lt;/strong&gt;: Represents a sequence of characters enclosed in single quotes (''), double quotes ("") or backticks (``). Strings are used to store and manipulate textual data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Symbol&lt;/strong&gt;: Introduced in &lt;a href="https://262.ecma-international.org/6.0" rel="noopener noreferrer"&gt;ECMAScript 2015&lt;/a&gt;, &lt;code&gt;Symbol&lt;/code&gt; is a primitive type that represents a unique identifier. Symbols are often used as keys in objects to avoid name collisions.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These primitive types in JavaScript are &lt;em&gt;immutable&lt;/em&gt;, meaning their values cannot be changed once created. They are distinct from objects, which are composite data types that can store multiple values and have methods and properties associated with them.&lt;/p&gt;

&lt;p&gt;Another property of primitive types in JavaScript is that they are passed &lt;em&gt;by value&lt;/em&gt;. This means that when a primitive value is assigned to a variable or passed as an argument to a function, a copy of the value is created and stored in the variable or parameter, and any changes made to the variable or parameter will not affect the original value.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 - JavaScript is a "compiled" language
&lt;/h2&gt;

&lt;p&gt;In the context of JavaScript, the term &lt;em&gt;compiled&lt;/em&gt; can be misleading. Unlike languages such as C++ or Rust, where the source code is directly transformed into machine code before execution, JavaScript goes through a different process. When a JavaScript program is executed, it is first passed through a &lt;a href="https://en.wikipedia.org/wiki/Just-in-time_compilation" rel="noopener noreferrer"&gt;just-in-time (JIT) compiler&lt;/a&gt; built into the JavaScript engine.&lt;/p&gt;

&lt;p&gt;During the execution, the &lt;a href="https://en.wikipedia.org/wiki/JavaScript_engine" rel="noopener noreferrer"&gt;JavaScript engine&lt;/a&gt; performs several optimizations, including parsing the source code, optimizing the code based on various heuristics, and generating machine code or bytecode for efficient execution. This dynamic compilation process, performed by the JavaScript engine at runtime, helps improve the performance of JavaScript programs.&lt;/p&gt;

&lt;p&gt;It is worth noting that the compilation process in JavaScript is different from the traditional pre-compilation step found in languages like C or C++. In JavaScript, there is no separate compilation step that generates an executable file beforehand. Instead, the compilation happens as part of the runtime execution.&lt;/p&gt;

&lt;p&gt;Overall, while JavaScript can be considered a compiled language in the sense that it undergoes compilation during runtime, it is more accurate to describe it as an interpreted language with a just-in-time compilation process.&lt;/p&gt;

&lt;p&gt;If you want to go deeper, &lt;a href="https://me.getify.com" rel="noopener noreferrer"&gt;Kyle Simpson&lt;/a&gt; explores this topic in detail in his book &lt;a href="https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/get-started/ch1.md#whats-in-an-interpretation" rel="noopener noreferrer"&gt;You Don't Know JS Yet&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;JavaScript never ceases to surprise and fascinate developers with its hidden depths and lesser-known features. In this blog post, we explored five intriguing aspects of JavaScript that you might not have been aware of, expanding your understanding of this versatile language.&lt;/p&gt;

&lt;p&gt;As you continue your journey with JavaScript, remember that there is always more to explore and learn. Embrace the ever-evolving nature of this dynamic language, and continue to discover new facets that will enhance your development skills.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>React Awesome Reveal v3 Beta</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Sun, 02 Aug 2020 21:43:21 +0000</pubDate>
      <link>https://dev.to/morellodev/react-awesome-reveal-v3-beta-459m</link>
      <guid>https://dev.to/morellodev/react-awesome-reveal-v3-beta-459m</guid>
      <description>&lt;p&gt;React Awesome Reveal v3 using Emotion to provide animations is now on beta 🎉 Check it out by adding react-awesome-reveal@beta to your React project.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>react-awesome-reveal v2.4.0 released!</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Sat, 18 Jan 2020 11:12:32 +0000</pubDate>
      <link>https://dev.to/morellodev/react-awesome-reveal-v2-4-0-released-4ghj</link>
      <guid>https://dev.to/morellodev/react-awesome-reveal-v2-4-0-released-4ghj</guid>
      <description>&lt;p&gt;A new version of &lt;a href="https://www.npmjs.com/package/react-awesome-reveal/v/2.4.0" rel="noopener noreferrer"&gt;react-awesome-reveal&lt;/a&gt; has just been released, featuring a &lt;code&gt;reverse&lt;/code&gt; option that... reverses the animation direction 👻 It is useful for making elements visible and invisible in a controlled way (form error indicators, badges, notifications and so on)!&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Add data-test-id attributes to React apps</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Fri, 03 Jan 2020 22:10:11 +0000</pubDate>
      <link>https://dev.to/morellodev/add-data-test-id-attributes-to-react-apps-1hfj</link>
      <guid>https://dev.to/morellodev/add-data-test-id-attributes-to-react-apps-1hfj</guid>
      <description>&lt;p&gt;Meet &lt;strong&gt;react-test-attributes&lt;/strong&gt;, my new library for React apps to add custom &lt;code&gt;data-*&lt;/code&gt; attributes to DOM elements. Use it for your E2E tests!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/react-test-attributes" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/react-test-attributes&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>testing</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>React Awesome Reveal v2 Released</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Sat, 30 Nov 2019 21:57:18 +0000</pubDate>
      <link>https://dev.to/morellodev/react-awesome-reveal-v2-released-2m80</link>
      <guid>https://dev.to/morellodev/react-awesome-reveal-v2-released-2m80</guid>
      <description>&lt;p&gt;React Awesome Reveal has just received a major update featuring zero external dependencies and a cleaner API 🎉 Check it out at &lt;a href="https://github.com/dennismorello/react-awesome-reveal" rel="noopener noreferrer"&gt;github.com/dennismorello/react-awesome-reveal&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://react-awesome-reveal.morello.dev" rel="noopener noreferrer"&gt;Demo site&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>React Awesome Reveal</title>
      <dc:creator>Dennis Morello</dc:creator>
      <pubDate>Tue, 26 Nov 2019 18:26:32 +0000</pubDate>
      <link>https://dev.to/morellodev/react-awesome-reveal-1khh</link>
      <guid>https://dev.to/morellodev/react-awesome-reveal-1khh</guid>
      <description>&lt;p&gt;Hi all, I am writing &lt;a href="https://github.com/dennismorello/react-awesome-reveal" rel="noopener noreferrer"&gt;react-awesome-reveal&lt;/a&gt;, a React library to animate elements when they appear in the browser viewport. It uses the Intersection Observer API and CSS Animations to be buttery smooth 🚀&lt;/p&gt;

&lt;p&gt;Let me know what you think!&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>css</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
