<?xml version="1.0" encoding="UTF-8"?><rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Smashing Magazine Articles</title><link>https://www.smashingmagazine.com/category/react</link><atom:link href="http://rss.144-124-237-35.sslip.io/smashingmagazine/react" rel="self" type="application/rss+xml"></atom:link><description>Latest Articles on Smashingmagazine.com - Powered by AtomRSS</description><generator>AtomRSS</generator><webMaster>contact@atomgroup.dev (AtomRSS)</webMaster><language>en-us</language><lastBuildDate>Sat, 08 Aug 2026 14:44:26 GMT</lastBuildDate><ttl>5</ttl><item><title>Weaponizing And Defending The React Flight Protocol: Deserialization Sinks In RSCs</title><description>&lt;h1 id=&quot;main-heading&quot; class=&quot;article-header--title&quot;&gt;Weaponizing And Defending The React Flight Protocol: Deserialization Sinks In RSCs&lt;/h1&gt;&lt;span id=&quot;article__start&quot; class=&quot;summary__heading&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;While React Server Components rely on the custom Flight protocol to stream interactive UIs, this same mechanism introduces powerful deserialization sinks that attackers can exploit. Durgesh Pawar breaks down the mechanics behind the CVSS 10.0 “React2Shell” vulnerability to show how protocol manipulation can lead to remote code execution. It also covers a practical, ranked set of defenses, from strict schema validation to CSRF hardening, for securing React applications against these structural risks.&lt;ul class=&quot;meta-box meta-box--article&quot;&gt;&lt;li class=&quot;meta-box--item meta-box--published&quot;&gt;27 min read&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--tags&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/category/react&quot;&gt;React&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/security&quot;&gt;Security&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/coding&quot;&gt;Coding&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--share&quot;&gt;Share on &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/intent/tweet?text=Weaponizing%20And%20Defending%20The%20React%20Flight%20Protocol%3a%20Deserialization%20Sinks%20In%20RSCs&amp;amp;url=https%3A%2F%2Fwww.smashingmagazine.com%2f2026%2f07%2fweaponizing-defending-react-flight-protocol%2f&amp;amp;via=smashingmag&quot;&gt;Twitter&lt;/a&gt;,&amp;nbsp;&lt;a rel=&quot;nofollow&quot; href=&quot;https://data.smashing.services/ball?uri=//www.linkedin.com/shareArticle?url=https://www.smashingmagazine.com%2f2026%2f07%2fweaponizing-defending-react-flight-protocol%2f&amp;amp;title=Weaponizing%20And%20Defending%20The%20React%20Flight%20Protocol%3a%20Deserialization%20Sinks%20In%20RSCs&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;React Server Components don’t send HTML to your browser. They don’t send JSON either. When a server component renders, what actually travels over the wire is a custom streaming protocol called &lt;strong&gt;Flight&lt;/strong&gt;. It’s a line-delimited format with its own type system, its own reference resolution, and its own rules for reconstructing executable behavior on the client.&lt;/p&gt;&lt;p&gt;Most React developers have never opened the Network tab and actually looked at a Flight payload. It looks like a mix of JSON fragments, dollar-sign-prefixed references, and module pointers that the React runtime silently reassembles into a live component tree. The framework handles it, so nobody questions it.&lt;/p&gt;&lt;p&gt;I’m not sure most teams have thought carefully about what that trust actually implies.&lt;/p&gt;&lt;p&gt;I started pulling apart the Flight protocol after &lt;a href=&quot;https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components&quot;&gt;CVE-2025-55182&lt;/a&gt; dropped in December 2025. The security community called it &lt;strong&gt;React2Shell&lt;/strong&gt;, and for good reason. It was a CVSS 10.0, unauthenticated remote code execution vulnerability sitting in the Flight deserialization layer. One crafted HTTP request to a Server Function endpoint, and an attacker had shell access. No credentials needed.&lt;/p&gt;&lt;p&gt;The federal Cybersecurity &amp;amp; Infrastructure Agency (CISA) added it to the &lt;a href=&quot;https://www.cisa.gov/known-exploited-vulnerabilities-catalog&quot;&gt;Known Exploited Vulnerabilities catalog&lt;/a&gt;. &lt;a href=&quot;https://sysdig.com/blog/etherrat-dprk-uses-novel-ethereum-implant-in-react2shell-attacks/&quot;&gt;Sysdig&lt;/a&gt; tied in-the-wild exploitation to North Korean state-sponsored actors deploying file-less implants through the Ethereum blockchain. That’s the kind of CVE that gets your attention.&lt;/p&gt;&lt;p&gt;After spending time in the source (mostly &lt;code&gt;getOutlinedModel&lt;/code&gt; and &lt;code&gt;getChunk&lt;/code&gt;, which is where the resolution logic that matters actually lives), I realized React2Shell wasn’t a one-off parsing bug. It was a symptom. Flight reconstructs executable references, lazy-loaded components, server RPC endpoints, and async state from a stream of text. That’s a &lt;strong&gt;deserialization system&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;The attack surface extends well beyond a single missing &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty&quot;&gt;&lt;code&gt;hasOwnProperty&lt;/code&gt;&lt;/a&gt; check. This article covers how Flight works on the wire, where the deserialization sinks are, what attackers have already weaponized, and what’s still exposed.&lt;/p&gt;&lt;p&gt;This leads to a &lt;strong&gt;ranked, practical set of defenses&lt;/strong&gt; for your own Server Components: schema validation on every Server Action, the &lt;a href=&quot;https://react.dev/reference/react/experimental_taintUniqueValue#using-server-only-and-taintuniquevalue-to-prevent-leaking-secrets&quot;&gt;&lt;code&gt;server-only&lt;/code&gt; package&lt;/a&gt;, cross-site request forgery (CSRF) hardening beyond framework defaults, and an assessment of what the Taint API and Web Application Firewalls (WAFs) provide.&lt;/p&gt;&lt;h3 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#flight-on-the-wire&quot;&gt;Flight On The Wire&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#why-flight-is-a-deserialization-sink&quot;&gt;Why Flight Is A Deserialization Sink&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#the-mechanics-of-react2shell&quot;&gt;The Mechanics Of React2Shell&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#the-fix&quot;&gt;The Fix&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#defenses-ranked-by-impact&quot;&gt;Defenses, Ranked By Impact&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#what-came-after-react2shell&quot;&gt;What Came After React2Shell&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#whats-still-exposed&quot;&gt;What’s Still Exposed&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#this-has-happened-before&quot;&gt;This Has Happened Before&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#where-this-goes-next&quot;&gt;Where This Goes Next&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;flight-on-the-wire&quot;&gt;Flight On The Wire&lt;/h2&gt;&lt;p&gt;Open your browser’s Network tab on any Next.js App Router page and look for requests returning &lt;code&gt;Content-Type: text/x-component&lt;/code&gt;. That’s Flight. It’s not a single JSON blob. It’s a streaming, line-delimited format where each line is a self-contained “row” that the client-side React runtime processes as it arrives over the connection.&lt;/p&gt;&lt;p&gt;Here’s what a simple Flight payload looks like in practice:&lt;/p&gt;&lt;p&gt;Row 1 is an import directive. It tells the client to load &lt;code&gt;ClientComponent.js&lt;/code&gt; from the bundler’s chunk map. Row 2 is a JSON tree that constructs an &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; HTML element, and the &lt;code&gt;&quot;$1&quot;&lt;/code&gt; inside &lt;code&gt;children&lt;/code&gt; is a reference back to chunk 1 (the imported component). Row 0 defines the server execution context, marking this as a &lt;code&gt;RootLayout&lt;/code&gt; running in the &lt;code&gt;Server&lt;/code&gt; environment. Even in this tiny example, you can see the mix of structural data, module references, and cross-chunk pointers that makes Flight different from plain JSON.&lt;/p&gt;&lt;h3 id=&quot;the-row-format&quot;&gt;The Row Format&lt;/h3&gt;&lt;p&gt;Every row follows the same syntax: &lt;code&gt;&amp;lt;ROW_ID&amp;gt;:&amp;lt;ROW_TAG&amp;gt;&amp;lt;PAYLOAD&amp;gt;\n&lt;/code&gt;. The row ID is a numeric identifier that other rows can reference. The tag is a single character (or short string) that tells the parser what kind of data follows. The payload is the actual content.&lt;/p&gt;&lt;p&gt;Here are the row tags I found while reading through the source:&lt;/p&gt;&lt;table class=&quot;tablesaw break-out&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Tag&lt;/th&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;What it does&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;J&lt;/td&gt;&lt;td&gt;JSON Tree&lt;/td&gt;&lt;td&gt;Serialized virtual DOM nodes, component props, and HTML elements.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;M&lt;/td&gt;&lt;td&gt;Module&lt;/td&gt;&lt;td&gt;Metadata for a specific Client Component module or chunk.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;I&lt;/td&gt;&lt;td&gt;Import&lt;/td&gt;&lt;td&gt;Tells the client to load a module from the bundler’s chunk map.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;HL&lt;/td&gt;&lt;td&gt;Hint/Preload&lt;/td&gt;&lt;td&gt;Instructs the browser to preload resources such as stylesheets or fonts.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;D&lt;/td&gt;&lt;td&gt;Data&lt;/td&gt;&lt;td&gt;Server-rendered element context and environment info.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;E&lt;/td&gt;&lt;td&gt;Error&lt;/td&gt;&lt;td&gt;Serialized server-side exceptions and error boundaries.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;So far, this might look like a benign structured data format with some custom tags, but the real complexity and attack surface live in the &lt;strong&gt;prefix system&lt;/strong&gt;.&lt;/p&gt;&lt;h3 id=&quot;the-prefix-system&quot;&gt;The &lt;code&gt;$&lt;/code&gt; Prefix System&lt;/h3&gt;&lt;p&gt;This is where I started paying closer attention.&lt;/p&gt;&lt;p&gt;When the client-side parser encounters a string value starting with &lt;code&gt;$&lt;/code&gt;, it doesn’t treat it as literal text. It intercepts the string, checks the prefix, and routes it through a type-specific resolution path. The &lt;code&gt;parseModelString&lt;/code&gt; function in &lt;code&gt;ReactFlightClient.js&lt;/code&gt; is where this happens. It’s essentially a big switch statement on the character after &lt;code&gt;$&lt;/code&gt;.&lt;/p&gt;&lt;table class=&quot;tablesaw break-out&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Prefix&lt;/th&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;What the parser does with it&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Model Reference&lt;/td&gt;&lt;td&gt;Resolves to another chunk in the stream (e.g., &lt;code&gt;$2&lt;/code&gt; points to row 2).&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$:&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Property Access&lt;/td&gt;&lt;td&gt;Traverses into a resolved chunk’s properties (e.g., &lt;code&gt;$1:user:name&lt;/code&gt;).&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$S&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Symbol&lt;/td&gt;&lt;td&gt;Creates a native JavaScript &lt;code&gt;Symbol&lt;/code&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$F&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Server Reference&lt;/td&gt;&lt;td&gt;Represents a callable Server Action (an RPC endpoint on the server).&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$L&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Lazy Component&lt;/td&gt;&lt;td&gt;Defers component loading until it’s needed in the render tree&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$@&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Promise/Raw Chunk&lt;/td&gt;&lt;td&gt;Returns the internal Chunk wrapper object itself (often acting as a Thenable/Promise), not its resolved value.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$B&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Blob/Binary&lt;/td&gt;&lt;td&gt;Triggers the blob deserialization handler for binary data.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Every other prefix resolves a chunk and gives you the parsed result. &lt;code&gt;$@&lt;/code&gt; hands you the raw internal &lt;code&gt;Chunk&lt;/code&gt; object instead, the wrapper React uses to track resolution state, pending callbacks, and internal metadata (which is why it’s used for Promises and why exploits use it to get a mutable handle). Exposing framework plumbing through the protocol looks like a design mistake to me, though I’d be interested to hear the rationale if there is one.&lt;/p&gt;&lt;p&gt;And &lt;code&gt;$:&lt;/code&gt; (property access) is the other critical prefix. It lets the protocol specify a path like &lt;code&gt;$1:user:name&lt;/code&gt;, which tells the parser to resolve chunk 1, then access &lt;code&gt;.user&lt;/code&gt;, then access &lt;code&gt;.name&lt;/code&gt; on the result. That’s arbitrary property traversal driven by data in the stream. If you’ve spent any time auditing JavaScript for prototype pollution, that pattern should feel familiar.&lt;/p&gt;&lt;h3 id=&quot;this-is-not-just-a-data-format&quot;&gt;This Is Not Just A Data Format&lt;/h3&gt;&lt;p&gt;Flight is not JSON with extra steps. JSON gives you data. Flight gives you &lt;strong&gt;behavior&lt;/strong&gt;. It reconstructs module references that trigger client-side code loading, creates server action endpoints the client can invoke as RPC calls, sets up Promise chains that the React runtime will &lt;code&gt;await&lt;/code&gt;, and builds lazy-loaded component boundaries that execute on demand.&lt;/p&gt;&lt;p&gt;Whether React developers think of it that way or not, the mechanics look very similar to deserialization systems that have historically caused problems. The stream doesn’t just describe what the UI looks like. It instructs the client runtime on what code to load, what functions to call, and what to trust.&lt;/p&gt;&lt;p&gt;If you want to read the implementation yourself, fair warning: the chunk resolution path is miserable to follow. State transitions bounce between helper functions, and the naming obscures what the code is actually doing. I gave up on static reading and just set breakpoints. The key files are &lt;a href=&quot;https://github.com/react/react/blob/main/packages/react-client/src/ReactFlightClient.js&quot;&gt;&lt;code&gt;react-client/src/ReactFlightClient.js&lt;/code&gt;&lt;/a&gt; for the client-side parser (look for &lt;code&gt;parseModelString&lt;/code&gt;, &lt;code&gt;getChunk&lt;/code&gt;, &lt;code&gt;reviveModel&lt;/code&gt;, and &lt;code&gt;getOutlinedModel&lt;/code&gt;) and &lt;a href=&quot;https://github.com/react/react/blob/main/packages/react-server/src/ReactFlightServer.js&quot;&gt;&lt;code&gt;react-server/src/ReactFlightServer.js&lt;/code&gt;&lt;/a&gt; for the serialization side. The reply handler for Server Actions lives in &lt;a href=&quot;https://github.com/react/react/blob/main/packages/react-server/src/ReactFlightReplyServer.js&quot;&gt;&lt;code&gt;react-server/src/ReactFlightReplyServer.js&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;why-flight-is-a-deserialization-sink&quot;&gt;Why Flight Is A Deserialization Sink&lt;/h2&gt;&lt;p&gt;The deserialization pattern is familiar: Java’s &lt;code&gt;ObjectInputStream&lt;/code&gt; gave us ysoserial, Python’s &lt;code&gt;pickle&lt;/code&gt; executes code on &lt;code&gt;load()&lt;/code&gt;, PHP’s &lt;code&gt;unserialize&lt;/code&gt; chains &lt;code&gt;__wakeup&lt;/code&gt; and &lt;code&gt;__destruct&lt;/code&gt; methods, and .NET’s &lt;code&gt;BinaryFormatter&lt;/code&gt; was deprecated entirely.&lt;/p&gt;&lt;blockquote&gt;The pattern: deserialize attacker-controlled input → invoke behavior during reconstruction → lose control of execution.&lt;/blockquote&gt;&lt;p&gt;So JavaScript should be immune to this, right? &lt;code&gt;JSON.parse()&lt;/code&gt; only produces plain data objects. No constructors fire. No magic methods run. You get back exactly what the JSON string describes, nothing more.&lt;/p&gt;&lt;p&gt;That’s true for raw &lt;code&gt;JSON.parse()&lt;/code&gt;. But it stops being true the moment a framework wraps custom deserialization logic around it. And that’s exactly what Flight does.&lt;/p&gt;&lt;h3 id=&quot;prototype-pollution&quot;&gt;Prototype Pollution&lt;/h3&gt;&lt;p&gt;JavaScript uses prototype-based inheritance. Every object has a &lt;code&gt;__proto__&lt;/code&gt; link to its prototype, and property lookups walk up this chain. If an attacker injects &lt;code&gt;__proto__&lt;/code&gt; or &lt;code&gt;constructor.prototype&lt;/code&gt; as a key during reconstruction, they modify the shared base prototypes that all objects inherit from. Downstream code reads attacker-controlled values without knowing.&lt;/p&gt;&lt;p&gt;Flight’s &lt;code&gt;$:&lt;/code&gt; prefix performs property traversal on deserialized objects. The &lt;code&gt;getOutlinedModel&lt;/code&gt; function walks colon-separated paths like &lt;code&gt;$1:user:name&lt;/code&gt; by iterating through each segment and accessing it on the parent object. If those path segments include &lt;code&gt;__proto__&lt;/code&gt; or &lt;code&gt;constructor&lt;/code&gt;, the traversal walks straight up the prototype chain. That’s not a theoretical risk. It’s exactly how React2Shell worked.&lt;/p&gt;&lt;h3 id=&quot;duck-typing-and-thenables&quot;&gt;Duck Typing and Thenables&lt;/h3&gt;&lt;p&gt;The V8 engine (and the JavaScript spec) treats any object with a &lt;code&gt;.then&lt;/code&gt; property as a &lt;strong&gt;Thenable&lt;/strong&gt;. When you &lt;code&gt;await&lt;/code&gt; something, the runtime checks for &lt;code&gt;.then&lt;/code&gt; and calls it if it exists. No class check. No internal slot verification. If &lt;code&gt;.then&lt;/code&gt; is callable, it gets invoked.&lt;/p&gt;&lt;p&gt;Flight resolves chunks asynchronously. If an attacker constructs an object with a manipulated &lt;code&gt;.then&lt;/code&gt; property and gets it into the chunk resolution pipeline, the runtime calls the attacker’s function during normal await behavior. The language semantics do the work.&lt;/p&gt;&lt;p&gt;I initially focused on &lt;code&gt;$F&lt;/code&gt; because forging Server Action references seemed like the obvious attack surface. After tracing the resolution path, &lt;code&gt;$:&lt;/code&gt; property traversal looked much more interesting. I also spent a few hours examining chunk status transitions (pending, blocked, resolved, errored) to see if you could force a chunk into an unexpected state, though that approach didn’t yield any results.&lt;/p&gt;&lt;h3 id=&quot;the-core-problem&quot;&gt;The Core Problem&lt;/h3&gt;&lt;p&gt;These two risks converge in Flight because the protocol doesn’t just deserialize data. It deserializes &lt;em&gt;behavior&lt;/em&gt;. The &lt;code&gt;$&lt;/code&gt; prefix system dictates which execution path the parser takes: &lt;code&gt;$F&lt;/code&gt; creates a callable server endpoint, &lt;code&gt;$L&lt;/code&gt; sets up lazy code loading, &lt;code&gt;$B&lt;/code&gt; triggers a blob handler, &lt;code&gt;$@&lt;/code&gt; exposes internal framework state. The parser’s control flow is driven entirely by what’s in the stream.&lt;/p&gt;&lt;p&gt;If an attacker can influence the stream’s content, they control which functions the parser calls, which objects it constructs, and which internal state it exposes.&lt;/p&gt;&lt;h2 id=&quot;the-mechanics-of-react2shell&quot;&gt;The Mechanics Of React2Shell&lt;/h2&gt;&lt;p&gt;This is the CVE that proved the theory. &lt;a href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2025-55182&quot;&gt;CVE-2025-55182&lt;/a&gt;, nicknamed React2Shell, is a CVSS 10.0 unauthenticated remote code execution vulnerability in the Flight deserialization layer. One HTTP request, no login required, full shell access.&lt;/p&gt;&lt;p&gt;I want to walk through the entire gadget chain because understanding it reveals how much power the Flight protocol hands to an attacker who can control the stream.&lt;/p&gt;&lt;h3 id=&quot;the-root-cause&quot;&gt;The Root Cause&lt;/h3&gt;&lt;p&gt;The vulnerability sits in &lt;code&gt;getOutlinedModel&lt;/code&gt;, a function responsible for resolving deep property paths from the &lt;code&gt;$:&lt;/code&gt; reference system. The instance used in the exploit chain lives in the server-side reply handling code (&lt;code&gt;ReactFlightReplyServer.js&lt;/code&gt;). When the parser encounters a reference like &lt;code&gt;$1:user:name&lt;/code&gt;, it splits on the colons and walks the path segment by segment. Here’s the vulnerable loop:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;for (key = 1; key &amp;lt; reference.length; key++)
    parentObject = parentObject[reference[key]];
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Two lines. No &lt;code&gt;hasOwnProperty&lt;/code&gt; check. No validation that the property exists on the object itself rather than somewhere up the prototype chain. Just &lt;code&gt;parentObject[reference[key]]&lt;/code&gt; and move on.&lt;/p&gt;&lt;p&gt;So an attacker supplies &lt;code&gt;$1:__proto__:constructor:constructor&lt;/code&gt;, and the loop traverses from a plain JSON object up through &lt;code&gt;Object.prototype&lt;/code&gt; to the &lt;code&gt;Object&lt;/code&gt; constructor to the &lt;code&gt;Function&lt;/code&gt; constructor. &lt;code&gt;Function&lt;/code&gt; in JavaScript behaves like &lt;code&gt;eval()&lt;/code&gt;. &lt;code&gt;Function(&quot;arbitrary code&quot;)()&lt;/code&gt; executes.&lt;/p&gt;&lt;p&gt;No allowlist on property names. No check for &lt;code&gt;__proto__&lt;/code&gt;. I searched &lt;code&gt;reviveModel&lt;/code&gt; and the chunk initialization path for any filtering. Nothing.&lt;/p&gt;&lt;h3 id=&quot;the-gadget-chain&quot;&gt;The Gadget Chain&lt;/h3&gt;&lt;p&gt;Getting from &lt;em&gt;“I can reach the Function constructor”&lt;/em&gt; to &lt;em&gt;“I have RCE”&lt;/em&gt; requires chaining several Flight protocol features together. The &lt;a href=&quot;https://resecurity.com/blog/article/react2shell-explained-cve-2025-55182-from-vulnerability-discovery-to-exploitation&quot;&gt;Resecurity write-up&lt;/a&gt; covers the full chain in detail; here’s the high-level sequence:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Step 1: Prototype walk to Function.&lt;/strong&gt;&lt;br&gt;The &lt;code&gt;$:&lt;/code&gt; path &lt;code&gt;__proto__:constructor:constructor&lt;/code&gt; walks from any plain object to &lt;code&gt;Object.prototype&lt;/code&gt;, then to the &lt;code&gt;Object&lt;/code&gt; constructor, then to &lt;code&gt;Function&lt;/code&gt; — JavaScript’s built-in &lt;code&gt;eval()&lt;/code&gt; equivalent.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Step 2: Raw chunk self-reference.&lt;/strong&gt;&lt;br&gt;&lt;code&gt;$@0&lt;/code&gt; returns the raw internal &lt;code&gt;Chunk&lt;/code&gt; wrapper instead of its resolved value, giving the attacker a mutable handle on React’s internal state machine.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Step 3: Thenable hijack.&lt;/strong&gt;&lt;br&gt;The attacker sets the chunk’s &lt;code&gt;.then&lt;/code&gt; to &lt;code&gt;Chunk.prototype.then&lt;/code&gt;, so React’s resolution pipeline treats the manipulated chunk as a legitimate Promise-like object and awaits it.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Step 4: Context confusion.&lt;/strong&gt;&lt;br&gt;During the second deserialization pass, the payload overwrites &lt;code&gt;_response._formData.get&lt;/code&gt; to point to the hijacked &lt;code&gt;Function&lt;/code&gt; constructor and places the attacker’s shell command into &lt;code&gt;_response._prefix&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Step 5: Trigger via blob handler.&lt;/strong&gt;&lt;br&gt;&lt;code&gt;$B0&lt;/code&gt; invokes the blob handler, which internally calls &lt;code&gt;response._formData.get(response._prefix + blobId)&lt;/code&gt; — now equivalent to &lt;code&gt;Function(&quot;attacker_shell_command&quot;)()&lt;/code&gt;. That’s arbitrary code execution with whatever privileges the Node.js process has.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Each step uses a legitimate Flight protocol feature in a way the designers didn’t anticipate. There’s no single “broken” feature. The vulnerability emerges from how these features compose when an attacker controls the input.&lt;/p&gt;&lt;h3 id=&quot;impact&quot;&gt;Impact&lt;/h3&gt;&lt;p&gt;The numbers on this one are stark:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;CVSS 10.0. The maximum possible score.&lt;/li&gt;&lt;li&gt;Unauthenticated and pre-auth. No credentials needed — and the deserialization happens before any application-level auth checks run, so even endpoints behind login walls are exposed.&lt;/li&gt;&lt;li&gt;Single HTTP request. One POST to a Server Function endpoint.&lt;/li&gt;&lt;li&gt;Affected React 19.0.0, 19.1.0, 19.1.1, and 19.2.0, across &lt;code&gt;react-server-dom-webpack&lt;/code&gt;, &lt;code&gt;react-server-dom-parcel&lt;/code&gt;, and &lt;code&gt;react-server-dom-turbopack&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;CISA added it to the Known Exploited Vulnerabilities catalog within days.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;what-happened-in-the-wild&quot;&gt;What Happened In The Wild&lt;/h3&gt;&lt;p&gt;Exploitation was immediate. &lt;a href=&quot;https://sysdig.com/blog/etherrat-dprk-uses-novel-ethereum-implant-in-react2shell-attacks/&quot;&gt;Sysdig&lt;/a&gt; published research linking &lt;strong&gt;EtherRAT&lt;/strong&gt; deployments to North Korean state-sponsored actors who weaponized the vulnerability within hours of disclosure. &lt;a href=&quot;https://www.sysdig.com/blog/etherrat-dprk-uses-novel-ethereum-implant-in-react2shell-attacks&quot;&gt;EtherRAT&lt;/a&gt; is a file-less implant that uses the Ethereum blockchain for command-and-control communication — a technique researchers call “EtherHiding” — making takedown nearly impossible because you can’t seize a blockchain.&lt;/p&gt;&lt;p&gt;Separately, Palo Alto’s &lt;a href=&quot;https://unit42.paloaltonetworks.com/cve-2025-55182-react-and-cve-2025-66478-next/&quot;&gt;Unit 42 documented&lt;/a&gt; a backdoor called &lt;strong&gt;KSwapDoor&lt;/strong&gt; that masquerades as &lt;code&gt;[kswapd1]&lt;/code&gt; on infected Linux systems, blending into process lists alongside the legitimate &lt;code&gt;kswapd0&lt;/code&gt; kernel swap daemon; their analysis confirms KSwapDoor uses RC4 encryption to protect its internal strings and configuration data, while C2 communications run over AES-256-CFB with &lt;a href=&quot;https://en.wikipedia.org/wiki/Diffie%C3%A2%E2%82%AC%E2%80%9CHellman_key_exchange&quot;&gt;Diffie-Hellman key exchange&lt;/a&gt; across a P2P mesh network. The speed and sophistication of these campaigns — state-sponsored actors deploying novel implants through a single unauthenticated HTTP request — underscores why a CVSS 10.0 in a deserialization layer demands immediate patching, not triage.&lt;/p&gt;&lt;h2 id=&quot;the-fix&quot;&gt;The Fix&lt;/h2&gt;&lt;p&gt;The React team’s patch is clean and targeted. The core change caches the genuine &lt;code&gt;hasOwnProperty&lt;/code&gt; method at module load time:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var hasOwnProperty = Object.prototype.hasOwnProperty;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then every property check in the deserialization path uses &lt;code&gt;.call()&lt;/code&gt; to invoke the cached reference:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;hasOwnProperty.call(value, i);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Even if an attacker shadows &lt;code&gt;hasOwnProperty&lt;/code&gt; on a malicious object, the check uses the original prototype method. The prototype chain traversal that powered the gadget chain is blocked. This fix shipped in React 19.0.1, 19.1.2, and 19.2.1.&lt;/p&gt;&lt;p&gt;The fix is correct. But reading through the patches, I noticed the React team hardened ownership checks while leaving the property traversal model intact. The &lt;code&gt;$:&lt;/code&gt; prefix still walks colon-separated paths; it just validates each step now. I think exposing arbitrary property traversal through a network protocol was a design mistake, and the patch treats the symptom. If future bugs emerge, they’ll likely come from this same area.&lt;/p&gt;&lt;p&gt;The framework patch closes the known gadget chain, but it doesn’t change the fundamental dynamic: the Flight protocol still reconstructs behavior — executable references, module imports, RPC endpoints, async state — from a stream of text. That reconstruction happens before your application code runs, before your validation logic fires, before your auth middleware even sees the request. Relying solely on the framework to protect your Server Components means trusting that every edge case in a complex deserialization parser has been found and fixed. The defenses that follow are the practical steps you can take to limit the blast radius on your own.&lt;/p&gt;&lt;h2 id=&quot;defenses-ranked-by-impact&quot;&gt;Defenses, Ranked By Impact&lt;/h2&gt;&lt;p&gt;Some of these close real attack paths. Others mostly make you feel safer than you are. I’ve ranked these from most-to-least impactful based on what I’ve seen in the vulnerability research. If you only have time for one change, start at the top.&lt;/p&gt;&lt;h3 id=&quot;1-input-validation-on-server-actions-zod-valibot&quot;&gt;1. Input Validation On Server Actions (Zod, Valibot)&lt;/h3&gt;&lt;p&gt;This is the single most impactful thing you can do at the application level. The Flight deserializer processes raw, unvalidated network input before your code takes control. Strict schema validation is your primary defense against whatever the protocol reconstructs.&lt;/p&gt;&lt;p&gt;Put a schema validation call at the very top of every Server Action, before any business logic runs — and I mean before &lt;em&gt;anything&lt;/em&gt;, including logging. If you log an argument before validating it, and that argument triggers the stringification bug from CVE-2025-55183, you’ve leaked source code before your validation even had a chance to run.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://zod.dev/&quot;&gt;Zod&lt;/a&gt; and &lt;a href=&quot;https://valibot.dev/&quot;&gt;Valibot&lt;/a&gt; both work well for this. Validate types, shapes, string lengths, numeric bounds, and enumerated values. Reject anything that doesn’t match. Use &lt;code&gt;.safeParse()&lt;/code&gt;, not &lt;code&gt;.parse()&lt;/code&gt; — the throwing variant can surface internal error details in the response if you’re not careful with your error boundaries.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&quot;use server&quot;
import { z } from &quot;zod&quot;

const UpdateProfileSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum([&quot;user&quot;, &quot;editor&quot;]),
})

export async function updateProfile(formData: FormData) {
  const parsed = UpdateProfileSchema.safeParse({
    name: formData.get(&quot;name&quot;),
    email: formData.get(&quot;email&quot;),
    role: formData.get(&quot;role&quot;),
  })
  if (!parsed.success) return { error: &quot;Invalid input&quot; }
  // proceed with parsed.data, this is now the only shape
  // your business logic ever sees
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;One important nuance&lt;/strong&gt;: If your Server Action accepts a plain object argument (not &lt;code&gt;FormData&lt;/code&gt;), validate the whole argument — don’t destructure first and validate fields individually. Destructuring before validation means you’re already accessing properties on the unvalidated input, which is exactly the kind of operation the Flight deserializer can exploit.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&quot;use server&quot;
import { z } from &quot;zod&quot;

const CommentSchema = z.object({
  postId: z.string().uuid(),
  body: z.string().min(1).max(5000),
})

// Good: validate the raw argument first
export async function addComment(data: unknown) {
  const parsed = CommentSchema.safeParse(data)
  if (!parsed.success) return { error: &quot;Invalid input&quot; }
  await db.comments.create(parsed.data)
}

// Bad: destructuring before validation
export async function addCommentUnsafe(
  { postId, body }: { postId: string; body: string }
) {
  // by the time this runs, you&#39;ve already accessed properties
  // on the deserialized input
  const parsed = CommentSchema.safeParse({ postId, body })
  // ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If your Server Action doesn’t start with a schema parse, it’s a vulnerability waiting to happen. I’d argue this should be a lint rule — and if you’re running &lt;code&gt;eslint-plugin-react&lt;/code&gt;, consider writing a custom rule that flags any &lt;code&gt;&quot;use server&quot;&lt;/code&gt; export without a validation call in its first statement.&lt;/p&gt;&lt;h3 id=&quot;2-the-server-only-package&quot;&gt;2. The &lt;code&gt;server-only&lt;/code&gt; Package&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;server-only&lt;/code&gt; package is straightforward and effective.&lt;/p&gt;&lt;p&gt;Import &lt;code&gt;server-only&lt;/code&gt; at the top of any file that contains database credentials, raw API calls, internal business logic, or anything else that should never cross the server-client boundary. If a Client Component tries to import that file (directly or transitively), the build fails with a clear error.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import &quot;server-only&quot;
import { db } from &quot;./database&quot;

export async function getUser(id: string) {
  return db.query(&quot;SELECT * FROM users WHERE id = $1&quot;, [id])
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The failure mode to watch for is &lt;a href=&quot;https://www.educative.io/answers/what-are-barrel-files&quot;&gt;barrel files&lt;/a&gt;. If you re-export a server-only function through an &lt;code&gt;index.ts&lt;/code&gt; that also exports client-safe utilities, any Client Component importing from that barrel will pull in the &lt;code&gt;server-only&lt;/code&gt; module transitively and break the build — or worse, if the barrel doesn’t include the &lt;code&gt;server-only&lt;/code&gt; import itself, it may silently let server code through. Keep server-only modules in separate files with their own import paths.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Don&#39;t do this: barrel re-export mixes boundaries
// src/utils/index.ts
export { getUser } from &quot;./users&quot;     // has &quot;server-only&quot;
export { formatDate } from &quot;./dates&quot;  // client-safe

// Do this: separate import paths
// Client Component imports from &quot;src/utils/dates&quot; directly
// Server Component imports from &quot;src/utils/users&quot; directly
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It also won’t protect you from data leaking through &lt;em&gt;return values&lt;/em&gt;. If a Server Component calls &lt;code&gt;getUser()&lt;/code&gt; and passes the full user object (including &lt;code&gt;passwordHash&lt;/code&gt; or &lt;code&gt;internalRole&lt;/code&gt;) as props to a Client Component, that data rides the Flight stream to the browser. The &lt;code&gt;server-only&lt;/code&gt; guard prevents the &lt;em&gt;code&lt;/em&gt; from crossing the boundary, not the &lt;em&gt;data&lt;/em&gt; the code returns. You must explicitly filter your return shapes.&lt;/p&gt;&lt;h3 id=&quot;3-csrf-protections&quot;&gt;3. CSRF Protections&lt;/h3&gt;&lt;p&gt;After CVE-2026-27978, relying solely on Next.js’s built-in &lt;code&gt;Origin&lt;/code&gt; vs. &lt;code&gt;Host&lt;/code&gt; header check isn’t enough. The &lt;code&gt;Origin: null&lt;/code&gt; bypass showed that framework-level CSRF protection has edge cases.&lt;/p&gt;&lt;p&gt;For state-changing Server Actions (anything that writes data, deletes records, or modifies permissions), layer your own protections on top of the framework’s defaults.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Cookie configuration.&lt;/strong&gt;&lt;br&gt;Set &lt;code&gt;SameSite=Strict&lt;/code&gt; or &lt;code&gt;SameSite=Lax&lt;/code&gt; on session cookies. If you’re using &lt;code&gt;next-auth&lt;/code&gt; or a custom session library, verify this is set explicitly — don’t rely on browser defaults, which vary.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// next.config.js or your auth configuration
cookies: {
  sessionToken: {
    name: &quot;__session&quot;,
    options: {
      httpOnly: true,
      sameSite: &quot;strict&quot;,
      secure: process.env.NODE_ENV === &quot;production&quot;,
      path: &quot;/&quot;,
    },
  },
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Explicit CSRF tokens.&lt;/strong&gt;&lt;br&gt;For high-value operations (password changes, role assignments, payment actions), generate a per-session CSRF token on the server, embed it in a hidden form field or custom header, and validate it in the Server Action before proceeding.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&quot;use server&quot;
import { cookies } from &quot;next/headers&quot;
import { validateCsrfToken } from &quot;@/lib/csrf&quot;

export async function deleteAccount(formData: FormData) {
  const token = formData.get(&quot;csrf_token&quot;) as string
  const sessionToken = (await cookies()).get(&quot;csrf_secret&quot;)?.value
  if (!validateCsrfToken(token, sessionToken)) {
    return { error: &quot;Invalid request&quot; }
  }
  // proceed with deletion
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;The &lt;code&gt;allowedOrigins&lt;/code&gt; gotcha.&lt;/strong&gt;&lt;br&gt;Never, under any circumstances, add &lt;code&gt;&#39;null&#39;&lt;/code&gt; to &lt;code&gt;experimental.serverActions.allowedOrigins&lt;/code&gt; in your Next.js config (even if the &lt;a href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-27978&quot;&gt;officially advisory&lt;/a&gt; is more nuanced, saying &lt;em&gt;“unless intentionally required and additionally protected”&lt;/em&gt;). That string literal matches &lt;code&gt;Origin: null&lt;/code&gt; — the exact header that sandboxed iframes send — and it reopens the CVE-2026-27978 bypass. If you’re seeing CSRF failures from legitimate requests, the fix is to configure your reverse proxy to set the correct &lt;code&gt;Origin&lt;/code&gt; and &lt;code&gt;Host&lt;/code&gt; headers, not to weaken the validation.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Never do this
module.exports = {
  experimental: {
    serverActions: {
      allowedOrigins: [&quot;null&quot;],  // reopens CSRF bypass
    },
  },
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;4-the-hasownproperty-patch&quot;&gt;4. The &lt;code&gt;hasOwnProperty&lt;/code&gt; Patch&lt;/h3&gt;&lt;p&gt;I covered this in detail in the &lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#the-mechanics-of-react2shell&quot;&gt;React2Shell section&lt;/a&gt;. The fix is correct, and it completely neutralizes the known gadget chain. It shipped fast, which I respect.&lt;/p&gt;&lt;p&gt;The action item here is to verify you’re actually running a patched version. The RCE fix landed in React 19.0.1, 19.1.2, and 19.2.1. Check your lockfile:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# npm
npm ls react react-dom react-server-dom-webpack

# pnpm
pnpm ls react react-dom react-server-dom-webpack

# yarn
yarn why react-server-dom-webpack
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you see 19.0.0, 19.1.0â€“19.1.1, or 19.2.0, you’re vulnerable to the RCE. Update immediately. And don’t stop there: the DoS fixes (CVE-2025-55184, CVE-2025-67779, CVE-2026-23864) require 19.0.4+, 19.1.5+, or 19.2.4+. If you updated after React2Shell and then stopped paying attention, you may still be running a version vulnerable to the DoS variants.&lt;/p&gt;&lt;p&gt;It’s a reactive patch, not a structural redesign.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;em&gt;More on that in &lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#where-this-goes-next&quot;&gt;Where This Goes Next&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;&lt;h3 id=&quot;5-the-taint-api&quot;&gt;5. The Taint API&lt;/h3&gt;&lt;p&gt;React’s &lt;code&gt;taintObjectReference&lt;/code&gt; and &lt;code&gt;taintUniqueValue&lt;/code&gt; functions register objects or strings with the runtime. If tainted data tries to pass through the Flight serializer, it throws an error. The idea is to prevent sensitive data — user records, API keys, tokens — from accidentally leaking into the client.&lt;/p&gt;&lt;p&gt;Here’s how it looks in practice:&lt;/p&gt;&lt;p&gt;If a Server Component passes the tainted &lt;code&gt;user&lt;/code&gt; object as props to a Client Component, React throws it at serialization time with your custom error message. That’s genuinely useful as a development-time guardrail.&lt;/p&gt;&lt;p&gt;The catch — and it’s a significant one — is that taint tracks &lt;em&gt;object references&lt;/em&gt;, not data content. Any derivation breaks the tracking:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const user = await getUserRecord(id)

// taint is lost. Spread creates a new object.
&amp;lt;ClientProfile user={{ ...user }} /&amp;gt;

// taint is lost. Individual properties aren&#39;t tracked.
&amp;lt;ClientProfile token={user.apiToken} /&amp;gt;

// taint is lost. Serialization round-trip creates new refs.
&amp;lt;ClientProfile user={JSON.parse(JSON.stringify(user))} /&amp;gt;

// taint fires. Same object reference.
&amp;lt;ClientProfile user={user} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;taintUniqueValue&lt;/code&gt; works on specific strings (like API keys), but it’s also reference-based. If the same key value appears in a different variable, the taint doesn’t follow.&lt;/p&gt;&lt;p&gt;Think of taint as a development guardrail, not a security boundary. It catches honest mistakes: a developer accidentally passing a full user object to the client. It won’t stop an attacker who can influence what gets serialized, and it won’t survive routine data transformations that your own code performs. It’s a useful defense-in-depth layer, but shouldn’t be your primary boundary.&lt;/p&gt;&lt;h3 id=&quot;6-wafs&quot;&gt;6. WAFs&lt;/h3&gt;&lt;p&gt;Web Application Firewalls can add a detection layer for known attack patterns. They can inspect POST requests carrying the &lt;code&gt;Next-Action&lt;/code&gt; header, block payloads containing &lt;code&gt;constructor:constructor&lt;/code&gt; or &lt;code&gt;__proto__&lt;/code&gt; chains, and flag error responses containing &lt;code&gt;E{&quot;digest&quot;&lt;/code&gt; patterns that indicate the server is leaking internal error details.&lt;/p&gt;&lt;p&gt;If you’re running a WAF, here are specific patterns worth adding:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Block prototype pollution attempts in request bodies
Rule: body contains &quot;__proto__&quot; OR &quot;constructor:constructor&quot;
Action: BLOCK
Scope: POST requests with header &quot;Next-Action&quot;

# Flag potential Flight error leakage in responses
Rule: response body matches /E\{&quot;digest&quot;:&quot;[^&quot;]+&quot;/
Action: LOG + ALERT
Scope: responses with Content-Type &quot;text/x-component&quot;

# Block excessively large Server Action payloads
Rule: Content-Length &amp;gt; 1MB for POST with &quot;Next-Action&quot; header
Action: BLOCK (mitigates CVE-2026-23864 zipbomb vector)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But attackers know about WAF inspection buffers, and they’re usually around 128KB. Prepend 130KB of padding before the malicious payload, and the WAF inspects the padding, finds nothing, and lets the request through. Chunked Transfer-Encoding tricks accomplish the same thing.&lt;/p&gt;&lt;p&gt;The failure mode is treating WAF coverage as a security boundary rather than a noise-reduction layer. WAFs catch automated scanners and low-effort attacks, and that has real value. But a motivated attacker will bypass them with padding or encoding tricks. The defenses that actually stop sophisticated attacks are the ones earlier in this list: validating input before it reaches your business logic, keeping sensitive code off the wire, and staying on patched versions.&lt;/p&gt;&lt;h2 id=&quot;what-came-after-react2shell&quot;&gt;What Came After React2Shell&lt;/h2&gt;&lt;p&gt;React2Shell wasn’t the end of it. The security audits that followed the December 2025 disclosure shook out a series of related vulnerabilities in the same deserialization surface. None of them are as severe as the original RCE, but they’re worth tracking because some of them required multiple rounds of patching.&lt;/p&gt;&lt;table class=&quot;tablesaw break-out&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;CVE&lt;/th&gt;&lt;th&gt;CVSS&lt;/th&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;th&gt;Fixed In&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;CVE-2025-55184&lt;/td&gt;&lt;td&gt;7.5&lt;/td&gt;&lt;td&gt;DoS&lt;/td&gt;&lt;td&gt;Infinite recursion of nested Promises in Server Function deserialization. Hangs the Node.js event loop.&lt;/td&gt;&lt;td&gt;19.0.2, 19.1.3, 19.2.2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CVE-2025-67779&lt;/td&gt;&lt;td&gt;7.5&lt;/td&gt;&lt;td&gt;DoS&lt;/td&gt;&lt;td&gt;Incomplete fix for CVE-2025-55184. Same loop via edge cases the first patch missed.&lt;/td&gt;&lt;td&gt;19.0.4, 19.1.5, 19.2.4&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CVE-2026-23864&lt;/td&gt;&lt;td&gt;7.5&lt;/td&gt;&lt;td&gt;DoS/OOM&lt;/td&gt;&lt;td&gt;Unbounded request body buffering and zipbomb-style decompression. Memory exhaustion. Disclosed Jan 2026.&lt;/td&gt;&lt;td&gt;19.0.4+, 19.1.5+, 19.2.4+&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CVE-2025-55183&lt;/td&gt;&lt;td&gt;5.3&lt;/td&gt;&lt;td&gt;Info Disclosure&lt;/td&gt;&lt;td&gt;Crafted requests reflect Server Function source code when the function stringifies an argument.&lt;/td&gt;&lt;td&gt;19.0.1, 19.1.2, 19.2.1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CVE-2026-27978&lt;/td&gt;&lt;td&gt;5.3&lt;/td&gt;&lt;td&gt;CSRF Bypass&lt;/td&gt;&lt;td&gt;Next.js treated &lt;code&gt;Origin: null&lt;/code&gt; (sandboxed iframes) as “missing” instead of “cross-origin.”&lt;/td&gt;&lt;td&gt;Next.js 16.1.7&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;The DoS pair (&lt;a href=&quot;https://react.dev/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components&quot;&gt;CVE-2025-55184&lt;/a&gt; and CVE-2025-67779) is a textbook example of why deserialization parsers are hard to patch correctly. The first fix shipped, researchers found edge cases it missed, and a second round was needed. CVE-2026-23864 added a third DoS vector through unbounded memory allocation rather than CPU exhaustion. (See the &lt;a href=&quot;https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/#defenses-ranked-by-impact&quot;&gt;defenses section&lt;/a&gt; above for specific version checks.)&lt;/p&gt;&lt;p&gt;CVE-2025-55183 is the sneaky one. It’s a source code exposure bug that triggers when a Server Function calls &lt;code&gt;JSON.stringify&lt;/code&gt; (or any implicit stringification) on one of its arguments. Developers do this constantly for logging, debugging, or error reporting.&lt;/p&gt;&lt;p&gt;The attacker sends a crafted argument that, when stringified, causes the deserialization parser to reflect the function’s own source code back in the response. Business logic, database queries, and any hardcoded secrets sitting in Server Action files become readable by anyone who can send an HTTP request.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/vercel/next.js/security/advisories/GHSA-mq59-m269-xvcx&quot;&gt;CVE-2026-27978&lt;/a&gt; is a different class of bug entirely. It’s a CSRF bypass in Next.js’s Server Action handling. Next.js validates that the &lt;code&gt;Origin&lt;/code&gt; header matches the &lt;code&gt;Host&lt;/code&gt; header to prevent cross-site request forgery. But when a request comes from a sandboxed &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;, the browser sends &lt;code&gt;Origin: null&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The Next.js parser in &lt;code&gt;action-handler.ts&lt;/code&gt; treated the string &lt;code&gt;&#39;null&#39;&lt;/code&gt; as a missing origin rather than an explicit cross-origin indicator. So an attacker could embed a form inside a sandboxed iframe, submit it, and invoke Server Actions using the victim’s authenticated session cookies. Fixed in Next.js 16.1.7.&lt;/p&gt;&lt;h2 id=&quot;what-s-still-exposed-a-name-whats-still-exposed-a&quot;&gt;What’s Still Exposed &lt;a name=&quot;whats-still-exposed&quot;&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The CVEs above have patches. But some of the risk is structural, baked into how Flight is designed to work.&lt;/p&gt;&lt;h3 id=&quot;man-in-the-middle-mitm-on-the-flight-stream&quot;&gt;Man-In-The-Middle (MITM) On The Flight Stream&lt;/h3&gt;&lt;p&gt;If an attacker can sit between server and client (CDN compromise, cache poisoning, rogue proxy), modifying the Flight stream in transit looks feasible. The format is plain text with a predictable structure.&lt;/p&gt;&lt;p&gt;Assuming stream control, an attacker could alter &lt;code&gt;$I&lt;/code&gt; (Import) rows to redirect component loading to a different module in the webpack chunk map. They could inject &lt;code&gt;$F&lt;/code&gt; (Server Reference) tags to embed hidden RPC triggers in the rendered UI. They could modify &lt;code&gt;D&lt;/code&gt; (Data) rows to change component props, and if the target component uses &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;, that’s a direct XSS vector.&lt;/p&gt;&lt;p&gt;Flight escapes &lt;code&gt;$&lt;/code&gt; prefixes in user-supplied strings to prevent data from being interpreted as protocol instructions. But that only applies to data flowing through the serializer. A MITM attacker writes raw protocol directly into the stream. The escaping doesn’t help.&lt;/p&gt;&lt;h3 id=&quot;server-action-enumeration&quot;&gt;Server Action Enumeration&lt;/h3&gt;&lt;p&gt;Server Action IDs are obfuscated hashes generated at build time. They look random. But &lt;code&gt;server-reference-manifest.json&lt;/code&gt; maps every action ID to its source implementation. A public manifest hands an attacker a complete API map. This exposure usually stems from misconfigured hosting, an exposed &lt;code&gt;.next&lt;/code&gt; directory, or path traversal.&lt;/p&gt;&lt;p&gt;Known action IDs expose Server Actions to standard IDOR and parameter tampering attacks. An attacker can forge direct requests with manipulated arguments. Developers often trust these inputs blindly because they originate from React’s internal machinery. The architectural consequences of that misplaced trust will be the focus of my next piece.&lt;/p&gt;&lt;h3 id=&quot;encrypted-closure-tampering&quot;&gt;Encrypted Closure Tampering&lt;/h3&gt;&lt;p&gt;When a Server Action captures variables from its surrounding scope (closures), Next.js encrypts them before sending to the client. The key is in &lt;code&gt;NEXT_SERVER_ACTIONS_ENCRYPTION_KEY&lt;/code&gt;, AES with a base64-encoded key (16, 24, or 32 bytes). &lt;code&gt;decryptActionBoundArgs&lt;/code&gt; handles decryption on each invocation.&lt;/p&gt;&lt;p&gt;By default, this key regenerates every build. But multi-server setups often use a static key. If an attacker gets file read access (path traversal, SSRF), they extract the key, decrypt the closure state, modify it (changing a &lt;code&gt;userId&lt;/code&gt;, a &lt;code&gt;role&lt;/code&gt;, a query parameter), and re-encrypt. The server accepts the forged closure as legitimate.&lt;/p&gt;&lt;h3 id=&quot;supply-chain-activation-via-module-ids&quot;&gt;Supply Chain Activation via Module IDs&lt;/h3&gt;&lt;p&gt;I haven’t demonstrated this end-to-end, but the theory is straightforward.&lt;/p&gt;&lt;p&gt;Flight references client components by module ID, something like &lt;code&gt;[&quot;360&quot;,&quot;static/chunks/app/page-7f3480.js&quot;]&lt;/code&gt;. The bundler assigns these IDs at build time based on the module graph. A compromised npm package sitting in &lt;code&gt;node_modules&lt;/code&gt; as a transitive dependency gets bundled into a chunk but never loaded because no component references it. Inert.&lt;/p&gt;&lt;p&gt;But if an attacker injects &lt;code&gt;$I&lt;/code&gt; import references into the Flight stream (via MITM, cache poisoning, or server-side injection), the parser should load that dormant module. There may be chunk-level validation I’m not seeing. But if the module ID is valid and present in the manifest, I don’t see what stops it. The attack doesn’t require the package to be imported anywhere in your code. It just needs to exist in the bundle output.&lt;/p&gt;&lt;h2 id=&quot;this-has-happened-before&quot;&gt;This Has Happened Before&lt;/h2&gt;&lt;p&gt;React Flight isn’t the first framework to invent a custom serialization format for server-client communication and then discover it’s an attack surface. And it won’t be the last.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.gwtproject.org/&quot;&gt;Google Web Toolkit (GWT)&lt;/a&gt; used a custom RPC protocol to sync Java objects between browser and server. BishopFox demonstrated that attackers could manipulate the wire format to achieve arbitrary deserialization; GWT eventually disabled binary serialization entirely. It took years.&lt;/p&gt;&lt;p&gt;Java Server Faces (JSF) and ASP.NET both serialized &lt;strong&gt;&lt;code&gt;ViewState&lt;/code&gt;&lt;/strong&gt; to the client as a hidden form field. When cryptographic signing was weak or missing, attackers tampered with the serialized state and achieved remote code execution. Microsoft and Oracle patched it repeatedly. The underlying pattern kept resurfacing.&lt;/p&gt;&lt;p&gt;The pattern is always the same: a framework invents a custom wire format to move rich, stateful, sometimes executable data between server and client. The designers assume the server is the sole producer of that data and the client is a trusted consumer. Then someone demonstrates that the wire format can be manipulated in transit, or that the server can be tricked into deserializing attacker-controlled input. React Flight is the latest entry in this pattern. It is &lt;strong&gt;not an anomaly&lt;/strong&gt;.&lt;/p&gt;&lt;h2 id=&quot;where-this-goes-next&quot;&gt;Where This Goes Next&lt;/h2&gt;&lt;p&gt;The React Flight protocol solves a genuinely hard problem: streaming interactive component trees from server to client in a way that enables progressive hydration, async data loading, and server-driven code splitting. It works. I don’t want to lose sight of that.&lt;/p&gt;&lt;p&gt;But it works by serializing executable references, async state, module pointers, and RPC endpoints over a streaming text protocol, and then trusting the structure of that stream on both ends. The React team has patched the known gadgets. The &lt;code&gt;hasOwnProperty&lt;/code&gt; fix is correct. The DoS fixes are in place. The source code exposure bug is closed.&lt;/p&gt;&lt;p&gt;I think exposing arbitrary property traversal and executable Thenable reconstruction through a network-facing protocol was a &lt;strong&gt;design mistake&lt;/strong&gt;. &lt;code&gt;$:&lt;/code&gt;, &lt;code&gt;$@&lt;/code&gt;, and &lt;code&gt;$B&lt;/code&gt; are powerful internal primitives that were reachable through a parser that didn’t validate ownership of the properties it traversed. One check was missing, and the result was CVSS 10.0.&lt;/p&gt;&lt;blockquote class=&quot;pull-quote&quot;&gt;&lt;p&gt;&lt;a class=&quot;pull-quote__link&quot; aria-label=&quot;Share on Twitter&quot; href=&quot;https://twitter.com/share?text=%0aAs%20more%20frameworks%20adopt%20server-driven%20UI%20patterns,%20the%20industry%20is%20going%20to%20need%20stronger%20primitives%20than%20%e2%80%9cthe%20server%20is%20trusted%e2%80%9d:%20cryptographic%20validation%20of%20serialized%20payloads,%20signed%20component%20trees,%20and%20content%20integrity%20checks%20on%20the%20Flight%20stream%20itself.%0a&amp;amp;url=https://smashingmagazine.com%2f2026%2f07%2fweaponizing-defending-react-flight-protocol%2f&quot;&gt;As more frameworks adopt server-driven UI patterns, the industry is going to need stronger primitives than “the server is trusted”: cryptographic validation of serialized payloads, signed component trees, and content integrity checks on the Flight stream itself.&lt;/a&gt;&lt;/p&gt;&lt;div class=&quot;pull-quote__quotation&quot;&gt;&lt;div class=&quot;pull-quote__bg&quot;&gt;&lt;span class=&quot;pull-quote__symbol&quot;&gt;“&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;Hoping the parser handles every edge case hasn’t worked historically, and I don’t see why it would start working now.&lt;/p&gt;&lt;p&gt;The code is in &lt;code&gt;react-client/src/ReactFlightClient.js&lt;/code&gt;. If you ship Server Components, read it. Know what your framework is trusting on your behalf.&lt;/p&gt;</description><link>https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/</link><guid isPermaLink="false">https://www.smashingmagazine.com/2026/07/weaponizing-defending-react-flight-protocol/</guid><pubDate>Mon, 20 Jul 2026 16:00:00 GMT</pubDate><author>Durgesh Pawar</author><category>React</category><category>Security</category><category>Coding</category></item><item><title>Building Dynamic Forms In React And Next.js</title><description>&lt;h1 id=&quot;main-heading&quot; class=&quot;article-header--title&quot;&gt;Building Dynamic Forms In React And Next.js&lt;/h1&gt;&lt;span id=&quot;article__start&quot; class=&quot;summary__heading&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;Some forms stay UI, while others quietly become rule engines. Here’s why these two different approaches exist and how to choose between them.&lt;ul class=&quot;meta-box meta-box--article&quot;&gt;&lt;li class=&quot;meta-box--item meta-box--published&quot;&gt;14 min read&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--tags&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/category/javascript&quot;&gt;JavaScript&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/react&quot;&gt;React&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/coding&quot;&gt;Coding&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--share&quot;&gt;Share on &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/intent/tweet?text=Building%20Dynamic%20Forms%20In%20React%20And%20Next.js&amp;amp;url=https%3A%2F%2Fwww.smashingmagazine.com%2f2026%2f03%2fbuilding-dynamic-forms-react-next-js%2f&amp;amp;via=smashingmag&quot;&gt;Twitter&lt;/a&gt;,&amp;nbsp;&lt;a rel=&quot;nofollow&quot; href=&quot;https://data.smashing.services/ball?uri=//www.linkedin.com/shareArticle?url=https://www.smashingmagazine.com%2f2026%2f03%2fbuilding-dynamic-forms-react-next-js%2f&amp;amp;title=Building%20Dynamic%20Forms%20In%20React%20And%20Next.js&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;There’s a mental model most React developers share without ever discussing it out loud. That forms are &lt;em&gt;always&lt;/em&gt; supposed to be components. This means a stack like:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;React Hook Form&lt;/strong&gt; for local state (minimal re-renders, ergonomic field registration, imperative interaction).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Zod&lt;/strong&gt; for validation (input correctness, boundary validation, type-safe parsing).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;React Query&lt;/strong&gt; for backend: submission, retries, caching, server sync, and so on.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And for the vast majority of forms — your login screens, your settings pages, your CRUD modals — this works really well. Each piece does its job, they compose cleanly, and you can move on to the parts of your application that actually differentiate your product.&lt;/p&gt;&lt;p&gt;But every once in a while, a form starts accumulating things like visibility rules that depend on earlier answers, or derived values that cascade through three fields. Maybe even entire pages that should be skipped or shown based on a running total.&lt;/p&gt;&lt;p&gt;You handle the first conditional with a &lt;code&gt;useWatch&lt;/code&gt; and an inline branch, which is fine. Then another. &lt;a href=&quot;https://zod.dev/api#superrefine&quot;&gt;Then you’re reaching for &lt;code&gt;superRefine&lt;/code&gt;&lt;/a&gt; to encode cross-field rules that your Zod schema can’t express in the normal way. Then, step navigation starts leaking business logic. At some point, you look at what you’ve built and realize that the form isn’t really UI anymore. It’s more of a decision process, and the component tree is just where you happened to store it.&lt;/p&gt;&lt;p&gt;This is where I think the mental model for forms in React breaks down, and it’s really nobody’s fault. The RHF + Zod stack is excellent at what it was designed for. &lt;strong&gt;The issue is that we tend to keep using it past the point where its abstractions match the problem&lt;/strong&gt; because the alternative requires a different way of thinking about forms entirely.&lt;/p&gt;&lt;p&gt;This article is about that alternative. To show this, we’ll build the exact same multi-step form twice:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;With React Hook Form + Zod wired to React Query for submission,&lt;/li&gt;&lt;li&gt;With SurveyJS, which treats a form as data — a simple JSON schema — rather than a component tree.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Same requirements, same conditional logic, same API call at the end. Then we’ll map exactly what moved and what stayed, and lay out a practical way to decide which model you should use, and when.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The form we’re building:&lt;/strong&gt;&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;798&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png&quot; sizes=&quot;100vw&quot; alt=&quot;Multi-step dynamic form&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;(&lt;a href=&quot;https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;This form will use a 4-step flow:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step 1: Details&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;First name (required),&lt;/li&gt;&lt;li&gt;Email (required, valid format).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Step 2: Order&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Unit price,&lt;/li&gt;&lt;li&gt;Quantity,&lt;/li&gt;&lt;li&gt;Tax rate,&lt;/li&gt;&lt;li&gt;Derived:&lt;ul&gt;&lt;li&gt;Subtotal,&lt;/li&gt;&lt;li&gt;Tax,&lt;/li&gt;&lt;li&gt;Total.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Step 3: Account &amp;amp; Feedback&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Do you have an account? (Yes/No)&lt;ul&gt;&lt;li&gt;If Yes → username + password, both required.&lt;/li&gt;&lt;li&gt;If No → email already collected in step 1.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Satisfaction rating (1–5)&lt;ul&gt;&lt;li&gt;If ≥ 4 → ask “What did you like?”&lt;/li&gt;&lt;li&gt;If ≤ 2 → ask “What can we improve?”&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Step 4: Review&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Only appears if &lt;code&gt;total &amp;gt;= 100&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Final submission.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This is not extreme. But it’s enough to expose architectural differences.&lt;/p&gt;&lt;h2 id=&quot;part-1-component-driven-react-hook-form-zod&quot;&gt;Part 1: Component-Driven (React Hook Form + Zod)&lt;/h2&gt;&lt;h3 id=&quot;installation&quot;&gt;Installation&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install react-hook-form zod @hookform/resolvers @tanstack/react-query
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;zod-schema&quot;&gt;Zod Schema&lt;/h3&gt;&lt;p&gt;Let’s start with the Zod schema, because that’s usually where the shape of the form gets established. For the first two steps — personal details and order inputs — everything is straightforward: required strings, numbers with minimums, and an enum. The interesting part starts when you try to express the conditional rules.&lt;/p&gt;&lt;p&gt;Notice that &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; are typed as &lt;code&gt;optional()&lt;/code&gt; even though they’re conditionally required because Zod’s type-level schema describes the &lt;em&gt;shape&lt;/em&gt; of the object, not the rules governing when fields matter.&lt;/p&gt;&lt;p&gt;The conditional requirement has to live inside &lt;code&gt;superRefine&lt;/code&gt;, which runs after the shape is validated and has access to the full object. That separation is not a flaw; it’s just what the tool is designed for: &lt;code&gt;superRefine&lt;/code&gt; is where cross-field logic goes when it can’t be expressed in the schema structure itself.&lt;/p&gt;&lt;p&gt;What’s also notable here is what this schema &lt;em&gt;doesn’t&lt;/em&gt; express. It has no concept of pages, no concept of which fields are visible at which point, and no concept of navigation. All of that will live somewhere else.&lt;/p&gt;&lt;h3 id=&quot;form-component&quot;&gt;Form Component&lt;/h3&gt;&lt;figure class=&quot;break-out&quot;&gt;&lt;p data-height=&quot;480&quot; data-theme-id=&quot;light&quot; data-slug-hash=&quot;gbwwmNO&quot; data-user=&quot;smashingmag&quot; data-default-tab=&quot;result&quot; class=&quot;codepen&quot;&gt;See the Pen [SurveyJS-03-RHF [forked]](https://codepen.io/smashingmag/pen/gbwwmNO) by &lt;a href=&quot;https://codepen.io/sixthextinction&quot;&gt;sixthextinction&lt;/a&gt;.&lt;/p&gt;&lt;figcaption&gt;See the Pen &lt;a href=&quot;https://codepen.io/smashingmag/pen/gbwwmNO&quot;&gt;SurveyJS-03-RHF [forked]&lt;/a&gt; by &lt;a href=&quot;https://codepen.io/sixthextinction&quot;&gt;sixthextinction&lt;/a&gt;.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;There’s quite a lot happening here, and it’s worth slowing down to notice where things ended up.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The derived values — &lt;code&gt;subtotal&lt;/code&gt;, &lt;code&gt;tax&lt;/code&gt;, &lt;code&gt;total&lt;/code&gt; — are computed in the component via &lt;code&gt;useWatch&lt;/code&gt; and &lt;code&gt;useMemo&lt;/code&gt; because they depend on live field values and there’s no other natural place for them.&lt;/li&gt;&lt;li&gt;The visibility rules for &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;positiveFeedback&lt;/code&gt;, and &lt;code&gt;improvementFeedback&lt;/code&gt; live in JSX as inline conditionals.&lt;/li&gt;&lt;li&gt;The step-skipping logic — the review page only appearing when &lt;code&gt;total &amp;gt;= 100&lt;/code&gt; — is embedded into the &lt;code&gt;showSubmit&lt;/code&gt; variable and the render condition on step 3.&lt;/li&gt;&lt;li&gt;Navigation itself is just a &lt;code&gt;useState&lt;/code&gt; counter that we’re manually incrementing.&lt;/li&gt;&lt;li&gt;React Query handles retries, caching, and invalidation. The form just calls &lt;code&gt;mutation.mutate&lt;/code&gt; with validated data.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;None of this is &lt;em&gt;wrong,&lt;/em&gt; per se. This is still idiomatic React, and the component is quite performant thanks to how RHF isolates re-renders.&lt;/p&gt;&lt;p&gt;But if you were to hand this to someone who hadn’t written it and ask them to explain &lt;em&gt;under what conditions the review page appears&lt;/em&gt;, they’d have to trace through &lt;code&gt;showSubmit&lt;/code&gt;, the step 3 render condition, and the nav button logic — three separate places — to reconstruct a rule that could have been stated in one line.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The form works, yes, but the behavior isn’t really inspectable as a system.&lt;/strong&gt; It has to be executed mentally.&lt;/p&gt;&lt;p&gt;More importantly, changing it requires engineering involvement. Even a small tweak, like adjusting when the review step shows up, means editing the component, updating validation, opening a pull request, waiting for review, and deploying again.&lt;/p&gt;&lt;h2 id=&quot;part-2-schema-driven-surveyjs&quot;&gt;Part 2: Schema-Driven (SurveyJS)&lt;/h2&gt;&lt;p&gt;Now let’s build the same flow using a schema.&lt;/p&gt;&lt;h3 id=&quot;installation-1&quot;&gt;Installation&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install survey-core survey-react-ui @tanstack/react-query
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;survey-core&lt;/code&gt;&lt;br&gt;The MIT-licensed platform-independent runtime engine that powers SurveyJS’s form rendering — the part we care about here. It takes a JSON schema, builds an internal model from it, and handles everything that would otherwise live in your React component: evaluating visibility expressions, computing derived values, managing page state, tracking validation, and deciding what “complete” means given which pages were actually shown.&lt;/li&gt;&lt;li&gt;&lt;code&gt;survey-react-ui&lt;/code&gt;&lt;br&gt;The UI / rendering layer that connects that model to React. It’s essentially a &lt;code&gt;&amp;lt;Survey model={model} /&amp;gt;&lt;/code&gt; component that re-renders whenever the engine’s state changes. SurveyJS UI libraries are also available for &lt;a href=&quot;https://www.npmjs.com/package/survey-angular&quot;&gt;Angular&lt;/a&gt;, &lt;a href=&quot;https://www.npmjs.com/package/survey-vue3-ui&quot;&gt;Vue3&lt;/a&gt;, and many other frameworks.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Together, they give you a fully functional, multi-page form runtime without writing a single line of control flow.&lt;/p&gt;&lt;p&gt;The schema format itself is, as said before, just a JSON — no DSL or anything proprietary. You can inline it, import it from a file, fetch it from an API, or store it in a database column and hydrate it at runtime.&lt;/p&gt;&lt;h3 id=&quot;the-same-form-as-data&quot;&gt;The Same Form, As Data&lt;/h3&gt;&lt;p&gt;Here’s the same form, this time expressed as a JSON object. The schema defines everything: structure, validation, visibility rules, derived calculations, page navigation — and hands it to a &lt;code&gt;Model&lt;/code&gt; that evaluates it at runtime. Here’s what that looks like in full:&lt;/p&gt;&lt;p&gt;Compare this to the RHF version for a moment.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The &lt;code&gt;superRefine&lt;/code&gt; block that conditionally required &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; is gone. &lt;code&gt;visibleIf: &quot;{hasAccount} = &#39;Yes&#39;&quot;&lt;/code&gt; combined with &lt;code&gt;isRequired: true&lt;/code&gt; handles both concerns together, on the field itself, where you’d expect to find them.&lt;/li&gt;&lt;li&gt;The &lt;code&gt;useWatch&lt;/code&gt; + &lt;code&gt;useMemo&lt;/code&gt; chain that computed &lt;code&gt;subtotal&lt;/code&gt;, &lt;code&gt;tax&lt;/code&gt;, and &lt;code&gt;total&lt;/code&gt; is replaced by three &lt;code&gt;expression&lt;/code&gt; fields that reference each other by name.&lt;/li&gt;&lt;li&gt;The review page condition, which in the RHF version was reconstructable only by tracing through &lt;code&gt;showSubmit&lt;/code&gt;, the step 3 render branch.&lt;/li&gt;&lt;li&gt;And finally, the nav button logic is a single &lt;code&gt;visibleIf&lt;/code&gt; property on the page object.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The same logic is there. It’s just that the schema gives it a place to live where it’s visible in isolation, rather than spread across the component.&lt;/p&gt;&lt;p&gt;Also, note that the schema uses &lt;code&gt;type: &#39;expression&#39;&lt;/code&gt; for subtotal, tax, and total. &lt;a href=&quot;https://surveyjs.io/form-library/documentation/api-reference/expression-model&quot;&gt;Expression&lt;/a&gt; is read-only and used mainly to display calculated values. SurveyJS also supports &lt;code&gt;type: &#39;html&#39;&lt;/code&gt; for static content, but for calculated values, &lt;code&gt;expression&lt;/code&gt; is the right choice.&lt;/p&gt;&lt;p&gt;Now for the React side.&lt;/p&gt;&lt;h3 id=&quot;rendering-and-submission&quot;&gt;Rendering And Submission&lt;/h3&gt;&lt;p&gt;Very simple. Wire &lt;code&gt;onComplete&lt;/code&gt; to your API the same way — via &lt;code&gt;useMutation&lt;/code&gt; or plain &lt;code&gt;fetch&lt;/code&gt;:&lt;/p&gt;&lt;figure class=&quot;break-out&quot;&gt;&lt;p data-height=&quot;480&quot; data-theme-id=&quot;light&quot; data-slug-hash=&quot;emddWNV&quot; data-user=&quot;smashingmag&quot; data-default-tab=&quot;result&quot; class=&quot;codepen&quot;&gt;See the Pen [SurveyJS-03-SurveyJS [forked]](https://codepen.io/smashingmag/pen/emddWNV) by &lt;a href=&quot;https://codepen.io/sixthextinction&quot;&gt;sixthextinction&lt;/a&gt;.&lt;/p&gt;&lt;figcaption&gt;See the Pen &lt;a href=&quot;https://codepen.io/smashingmag/pen/emddWNV&quot;&gt;SurveyJS-03-SurveyJS [forked]&lt;/a&gt; by &lt;a href=&quot;https://codepen.io/sixthextinction&quot;&gt;sixthextinction&lt;/a&gt;.&lt;/figcaption&gt;&lt;/figure&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;onComplete&lt;/code&gt; fires when the user reaches the end of the last &lt;em&gt;visible&lt;/em&gt; page. So if &lt;code&gt;total&lt;/code&gt; never crosses 100 and the review page is skipped, it still fires correctly because SurveyJS evaluates visibility before deciding what “last page” means.&lt;/li&gt;&lt;li&gt;Then, &lt;code&gt;sender.data&lt;/code&gt; contains all answers along with the calculated values (&lt;code&gt;subtotal&lt;/code&gt;, &lt;code&gt;tax&lt;/code&gt;, &lt;code&gt;total&lt;/code&gt;) as first-class fields, so the API payload is identical to what the RHF version assembled manually in &lt;code&gt;onSubmit&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;The &lt;code&gt;mutationRef&lt;/code&gt; pattern is the same one you’d reach for anywhere you need a stable event handler over a value that changes on every render — nothing SurveyJS-specific about it.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The React component no longer contains any business logic at all. There’s no &lt;code&gt;useWatch&lt;/code&gt;, no conditional JSX, no step counter, no &lt;code&gt;useMemo&lt;/code&gt; chain, no &lt;code&gt;superRefine&lt;/code&gt;. React is doing what it’s actually good at: rendering a component and wiring it to an API call.&lt;/p&gt;&lt;h2 id=&quot;what-moved-out-of-react&quot;&gt;What Moved Out Of React?&lt;/h2&gt;&lt;table class=&quot;tablesaw break-out&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Concern&lt;/th&gt;&lt;th&gt;RHF Stack&lt;/th&gt;&lt;th&gt;SurveyJS&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Visibility&lt;/td&gt;&lt;td&gt;JSX branches&lt;/td&gt;&lt;td&gt;&lt;code&gt;visibleIf&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Derived values&lt;/td&gt;&lt;td&gt;&lt;code&gt;useWatch&lt;/code&gt; / &lt;code&gt;useMemo&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;expression&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cross-field rules&lt;/td&gt;&lt;td&gt;&lt;code&gt;superRefine&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Schema conditions&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Navigation&lt;/td&gt;&lt;td&gt;&lt;code&gt;step&lt;/code&gt; state&lt;/td&gt;&lt;td&gt;Page &lt;code&gt;visibleIf&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Rule location&lt;/td&gt;&lt;td&gt;Distributed across files&lt;/td&gt;&lt;td&gt;Centralized in the schema&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;What stays in React is layout, styling, submission wiring, and app integration, which is to say, &lt;strong&gt;the things React is actually designed for&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Everything else moved into the schema, and because the schema is just a JSON object, it can be stored in a database, versioned independently of your application code, or edited through internal tooling without requiring a deploy.&lt;/p&gt;&lt;p&gt;A product manager who needs to change the threshold that triggers the review page can do that without touching the component. That’s a meaningful operational difference for teams where form behavior evolves frequently and isn’t always driven by engineers.&lt;/p&gt;&lt;h2 id=&quot;when-to-use-each-approach&quot;&gt;When To Use Each Approach?&lt;/h2&gt;&lt;p&gt;Here’s a good rule of thumb that works for me: &lt;strong&gt;imagine deleting the form entirely&lt;/strong&gt;. What would you lose?&lt;/p&gt;&lt;ul&gt;&lt;li&gt;If it’s screens, you want component-driven forms.&lt;/li&gt;&lt;li&gt;If it’s business logic, like thresholds, branching rules, and conditional requirements that encode real decisions, you want a schema engine.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Similarly, if the changes coming your way are mostly about labels, fields, and layout, RHF will serve you fine. If they’re about conditions, outcomes, and rules that your ops or legal team might need to adjust on a Tuesday afternoon without filing a ticket, the schema model with SurveyJS is the more honest fit.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;These two approaches are not really in competition with each other.&lt;/strong&gt; They address different classes of problems, and the mistake worth avoiding is mismatching the abstraction to the weight of the logic — treating a rule system like a component because that’s the familiar tool, or reaching for a policy engine because a form grew to three steps and acquired a conditional field.&lt;/p&gt;&lt;p&gt;The form we built here sits near the boundary deliberately, complex enough to expose the difference but not so extreme that the comparison feels rigged. Most real forms that have gotten unwieldy in your codebase probably sit near that same boundary, and the question is usually just whether anyone has named what they actually are.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Use React Hook Form + Zod when:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Forms are CRUD-oriented;&lt;/li&gt;&lt;li&gt;Logic is shallow and UI-driven;&lt;/li&gt;&lt;li&gt;Engineers own all behavior;&lt;/li&gt;&lt;li&gt;Backend remains the source of truth.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Use SurveyJS when:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Forms encode business decisions;&lt;/li&gt;&lt;li&gt;Rules evolve independently of UI;&lt;/li&gt;&lt;li&gt;Logic must be visible, auditable, or versioned;&lt;/li&gt;&lt;li&gt;Non-engineers influence behavior;&lt;/li&gt;&lt;li&gt;The same form must run across multiple frontends.&lt;/li&gt;&lt;/ul&gt;</description><link>https://www.smashingmagazine.com/2026/03/building-dynamic-forms-react-next-js/</link><guid isPermaLink="false">https://www.smashingmagazine.com/2026/03/building-dynamic-forms-react-next-js/</guid><pubDate>Mon, 20 Jul 2026 16:00:00 GMT</pubDate><author>Sunil Sandhu</author><category>JavaScript</category><category>React</category><category>Coding</category></item><item><title>Adaptive Video Streaming With Dash.js In React</title><description>&lt;h1 id=&quot;main-heading&quot; class=&quot;article-header--title&quot;&gt;Adaptive Video Streaming With Dash.js In React&lt;/h1&gt;&lt;span id=&quot;article__start&quot; class=&quot;summary__heading&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;HTML &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; is the de facto element we turn to for embedding video content, but it comes with constraints. For example, it downloads the video file linearly over HTTP, which leads to performance hiccups, especially for large videos consumed on slower connections. But with adaptive bitrate streaming, we can split the video into multiple segments at different bitrates and resolutions.&lt;ul class=&quot;meta-box meta-box--article&quot;&gt;&lt;li class=&quot;meta-box--item meta-box--published&quot;&gt;9 min read&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--tags&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/category/video&quot;&gt;Video&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/techniques&quot;&gt;Techniques&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/tools&quot;&gt;Tools&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/react&quot;&gt;React&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/coding&quot;&gt;Coding&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--share&quot;&gt;Share on &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/intent/tweet?text=Adaptive%20Video%20Streaming%20With%20Dash.js%20In%20React&amp;amp;url=https%3A%2F%2Fwww.smashingmagazine.com%2f2025%2f03%2fadaptive-video-streaming-dashjs-react%2f&amp;amp;via=smashingmag&quot;&gt;Twitter&lt;/a&gt;,&amp;nbsp;&lt;a rel=&quot;nofollow&quot; href=&quot;https://data.smashing.services/ball?uri=//www.linkedin.com/shareArticle?url=https://www.smashingmagazine.com%2f2025%2f03%2fadaptive-video-streaming-dashjs-react%2f&amp;amp;title=Adaptive%20Video%20Streaming%20With%20Dash.js%20In%20React&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I was recently tasked with creating video reels that needed to be played smoothly under a slow network or on low-end devices. I started with the native HTML5 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag but quickly hit a wall — it just doesn’t cut it when connections are slow or devices are underpowered.&lt;/p&gt;&lt;p&gt;After some research, I found that &lt;strong&gt;adaptive bitrate streaming&lt;/strong&gt; was the solution I needed. But here’s the frustrating part: finding a comprehensive, beginner-friendly guide was so difficult. The resources on MDN and other websites were helpful but lacked the end-to-end tutorial I was looking for.&lt;/p&gt;&lt;p&gt;That’s why I’m writing this article: to provide you with the step-by-step guide I wish I had found. I’ll bridge the gap between writing FFmpeg scripts, encoding video files, and implementing the DASH-compatible video player (&lt;a href=&quot;https://dashjs.org/&quot;&gt;Dash.js&lt;/a&gt;) with code examples you can follow.&lt;/p&gt;&lt;h2 id=&quot;going-beyond-the-native-html5-video-tag&quot;&gt;Going Beyond The Native HTML5 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; Tag&lt;/h2&gt;&lt;p&gt;You might be wondering why you can’t simply rely on the HTML &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element. There’s a good reason for that. Let’s compare the difference between a native &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element and adaptive video streaming in browsers.&lt;/p&gt;&lt;h3 id=&quot;progressive-download&quot;&gt;Progressive Download&lt;/h3&gt;&lt;p&gt;With progressive downloading, your browser downloads the video file linearly from the server over HTTP and starts playback as long as it has buffered enough data. This is the default behavior of the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;video src=&quot;rabbit320.mp4&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When you play the video, check your browser’s network tab, and you’ll see multiple requests with the &lt;code&gt;206 Partial Content&lt;/code&gt; status code.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;140&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png&quot; sizes=&quot;100vw&quot; alt=&quot;HTTP 206 Range Requests&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;(&lt;a href=&quot;https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;It uses &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests&quot;&gt;HTTP 206 Range Requests&lt;/a&gt; to fetch the video file in chunks. The server sends specific byte ranges of the video to your browser. When you seek, the browser will make more range requests asking for new byte ranges (e.g., “Give me bytes 1,000,000–2,000,000”).&lt;/p&gt;&lt;p class=&quot;c-pre-sidenote--left&quot;&gt;In other words, it doesn’t fetch the entire file all at once. Instead, it delivers partial byte ranges from the single MP4 video file on demand. This is still considered a &lt;strong&gt;progressive download&lt;/strong&gt; because only a single file is fetched over HTTP — there is no bandwidth or quality adaptation.&lt;/p&gt;&lt;p class=&quot;c-sidenote c-sidenote--right&quot;&gt;If the server or browser doesn’t support range requests, the entire video file will be downloaded in a single request, returning a &lt;code&gt;200 OK&lt;/code&gt; status code. In that case, the video can only begin playing once the entire file has finished downloading.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The problems?&lt;/strong&gt; If you’re on a slow connection trying to watch high-resolution video, you’ll be waiting a long time before playback starts.&lt;/p&gt;&lt;h3 id=&quot;adaptive-bitrate-streaming&quot;&gt;Adaptive Bitrate Streaming&lt;/h3&gt;&lt;p&gt;Instead of serving one single video file, &lt;strong&gt;adaptive bitrate (ABR) streaming&lt;/strong&gt; splits the video into multiple segments at different bitrates and resolutions. During playback, the ABR algorithm will automatically select the highest quality segment that can be downloaded in time for smooth playback based on your network connectivity, bandwidth, and other device capabilities. It continues adjusting throughout to adapt to changing conditions.&lt;/p&gt;&lt;p&gt;This magic happens through two key browser technologies:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Media Source Extension (MSE)&lt;/strong&gt;&lt;br&gt;It allows passing a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/MediaSource&quot;&gt;MediaSource&lt;/a&gt; object to the &lt;code&gt;src&lt;/code&gt; attribute in &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;, enabling sending multiple &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer&quot;&gt;SourceBuffer&lt;/a&gt; objects that represent video segments.&lt;br&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Media Capabilities API&lt;/strong&gt;&lt;br&gt;It provides information on your device’s video decoding and encoding abilities, enabling ABR to make informed decisions about which resolution to deliver.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Together, they enable the core functionality of ABR, serving video chunks optimized for your specific device limitations in real time.&lt;/p&gt;&lt;h2 id=&quot;streaming-protocols-mpeg-dash-vs-hls&quot;&gt;Streaming Protocols: MPEG-DASH Vs. HLS&lt;/h2&gt;&lt;p&gt;As mentioned above, to stream media adaptively, a video is split into chunks at different quality levels across various time points. We need to facilitate the process of switching between these segments adaptively in real time. To achieve this, ABR streaming relies on specific protocols. The two most common ABR protocols are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;MPEG-DASH,&lt;/li&gt;&lt;li&gt;HTTP Live Streaming (HLS).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Both of these protocols utilize HTTP to send video files. Hence, they are compatible with HTTP web servers.&lt;/p&gt;&lt;p&gt;This article focuses on MPEG-DASH. However, it’s worth noting that DASH isn’t supported by Apple devices or browsers, as mentioned in &lt;a href=&quot;https://www.mux.com/articles/hls-vs-dash-what-s-the-difference-between-the-video-streaming-protocols&quot;&gt;Mux’s article&lt;/a&gt;.&lt;/p&gt;&lt;h3 id=&quot;mpeg-dash&quot;&gt;MPEG-DASH&lt;/h3&gt;&lt;p&gt;MPEG-DASH enables adaptive streaming through:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;A Media Presentation Description (MPD) file&lt;/strong&gt;&lt;br&gt;This XML manifest file contains information on how to select and manage streams based on adaptive rules.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Segmented Media Files&lt;/strong&gt;&lt;br&gt;Video and audio files are divided into segments at different resolutions and durations using MPEG-DASH-compliant codecs and formats.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;On the client side, a DASH-compliant video player reads the MPD file and continuously monitors network bandwidth. Based on available bandwidth, the player selects the appropriate bitrate and requests the corresponding video chunk. This process repeats throughout playback, ensuring smooth, optimal quality.&lt;/p&gt;&lt;p&gt;Now that you understand the fundamentals, let’s build our adaptive video player!&lt;/p&gt;&lt;h2 id=&quot;steps-to-build-an-adaptive-bitrate-streaming-video-player&quot;&gt;Steps To Build an Adaptive Bitrate Streaming Video Player&lt;/h2&gt;&lt;p&gt;Here’s the plan:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Transcode the MP4 video into audio and video renditions at different resolutions and bitrates with FFmpeg.&lt;/li&gt;&lt;li&gt;Generate an MPD file with FFmpeg.&lt;/li&gt;&lt;li&gt;Serve the output files from the server.&lt;/li&gt;&lt;li&gt;Build the DASH-compatible video player to play the video.&lt;/li&gt;&lt;/ol&gt;&lt;h3 id=&quot;install-ffmpeg&quot;&gt;Install FFmpeg&lt;/h3&gt;&lt;p&gt;For macOS users, install FFmpeg using Brew by running the following command in your terminal:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install ffmpeg
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For other operating systems, please &lt;a href=&quot;https://www.ffmpeg.org/download.html&quot;&gt;refer to FFmpeg’s documentation&lt;/a&gt;.&lt;/p&gt;&lt;h3 id=&quot;generate-audio-rendition&quot;&gt;Generate Audio Rendition&lt;/h3&gt;&lt;p&gt;Next, run the following script to extract the audio track and encode it in WebM format for DASH compatibility:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;-i &quot;input_video.mp4&quot;&lt;/code&gt;: Specifies the input video file.&lt;/li&gt;&lt;li&gt;&lt;code&gt;-vn&lt;/code&gt;: Disables the video stream (audio-only output).&lt;/li&gt;&lt;li&gt;&lt;code&gt;-acodec libvorbis&lt;/code&gt;: Uses the &lt;a href=&quot;https://ffmpeg.org/ffmpeg-codecs.html#libvorbis&quot;&gt;&lt;strong&gt;libvorbis&lt;/strong&gt;&lt;/a&gt; codec to encode audio.&lt;/li&gt;&lt;li&gt;&lt;code&gt;-ab 128k&lt;/code&gt;: Sets the audio bitrate to &lt;strong&gt;128 kbps&lt;/strong&gt;.&lt;/li&gt;&lt;li&gt;&lt;code&gt;&quot;audio.webm&quot;&lt;/code&gt;: Specifies the output audio file in WebM format.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;generate-video-renditions&quot;&gt;Generate Video Renditions&lt;/h3&gt;&lt;p&gt;Run this script to create three video renditions with varying resolutions and bitrates. The largest resolution should match the input file size. For example, if the input video is &lt;strong&gt;576×1024&lt;/strong&gt; at 30 frames per second (fps), the script generates renditions optimized for vertical video playback.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;-c:v libvpx-vp9&lt;/code&gt;: Uses the &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/VP9&quot;&gt;&lt;strong&gt;libvpx-vp9&lt;/strong&gt;&lt;/a&gt; as the VP9 video encoder for WebM.&lt;/li&gt;&lt;li&gt;&lt;code&gt;-keyint_min 150&lt;/code&gt; and &lt;code&gt;-g 150&lt;/code&gt;: Set a &lt;strong&gt;150-frame keyframe interval&lt;/strong&gt; (approximately every 5 seconds at 30 fps). This allows bitrate switching every 5 seconds.&lt;/li&gt;&lt;li&gt;&lt;code&gt;-tile-columns 4&lt;/code&gt; and &lt;code&gt;-frame-parallel 1&lt;/code&gt;: Optimize encoding performance through parallel processing.&lt;/li&gt;&lt;li&gt;&lt;code&gt;-f webm&lt;/code&gt;: Specifies the output format as WebM.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In each rendition:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;-an&lt;/code&gt;: Excludes audio (video-only output).&lt;/li&gt;&lt;li&gt;&lt;code&gt;-vf scale=576:1024&lt;/code&gt;: Scales the video to a resolution of &lt;strong&gt;576x1024&lt;/strong&gt; pixels.&lt;/li&gt;&lt;li&gt;&lt;code&gt;-b:v 1500k&lt;/code&gt;: Sets the video bitrate to &lt;strong&gt;1500 kbps&lt;/strong&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;WebM is chosen as the output format, as they are smaller in size and optimized yet widely compatible with most web browsers.&lt;/p&gt;&lt;h3 id=&quot;generate-mpd-manifest-file&quot;&gt;Generate MPD Manifest File&lt;/h3&gt;&lt;p&gt;Combine the video renditions and audio track into a DASH-compliant MPD manifest file by running the following script:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;-f webm_dash_manifest -i &quot;…&quot;&lt;/code&gt;: Specifies the inputs so that the ASH video player will switch between them dynamically based on network conditions.&lt;/li&gt;&lt;li&gt;&lt;code&gt;-map 0 -map 1 -map 2 -map 3&lt;/code&gt;: Includes all video (0, 1, 2) and audio (3) in the final manifest.&lt;/li&gt;&lt;li&gt;&lt;code&gt;-adaptation_sets&lt;/code&gt;: Groups streams into adaptation sets:&lt;ul&gt;&lt;li&gt;&lt;code&gt;id=0,streams=0,1,2&lt;/code&gt;: Groups the video renditions into a single adaptation set.&lt;/li&gt;&lt;li&gt;&lt;code&gt;id=1,streams=3&lt;/code&gt;: Assigns the audio track to a separate adaptation set.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The resulting MPD file (&lt;code&gt;input_video_manifest.mpd&lt;/code&gt;) describes the streams and enables adaptive bitrate streaming in MPEG-DASH.&lt;/p&gt;&lt;p&gt;After completing these steps, you’ll have:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Three video renditions (&lt;code&gt;576x1024&lt;/code&gt;, &lt;code&gt;480x854&lt;/code&gt;, &lt;code&gt;360x640&lt;/code&gt;),&lt;/li&gt;&lt;li&gt;One audio track, and&lt;/li&gt;&lt;li&gt;An MPD manifest file.&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;input_video.mp4
audio.webm
input_video_576x1024_1500k.webm
input_video_480x854_1000k.webm
input_video_360x640_750k.webm
input_video_manifest.mpd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The original video &lt;code&gt;input_video.mp4&lt;/code&gt; should also be kept to serve as a fallback video source later.&lt;/p&gt;&lt;h3 id=&quot;serve-the-output-files&quot;&gt;Serve The Output Files&lt;/h3&gt;&lt;p&gt;These output files can now be uploaded to cloud storage (e.g., AWS S3 or Cloudflare R2) for playback. While they can be served directly from a local folder, I highly recommend storing them in cloud storage and leveraging a CDN to cache the assets for better performance. Both AWS and Cloudflare support HTTP range requests out of the box.&lt;/p&gt;&lt;h3 id=&quot;building-the-dash-compatible-video-player-in-react&quot;&gt;Building The DASH-Compatible Video Player In React&lt;/h3&gt;&lt;p&gt;There’s nothing like a real-world example to help understand how everything works. There are different ways we can implement a DASH-compatible video player, but I’ll focus on an approach using React.&lt;/p&gt;&lt;p&gt;First, install the &lt;a href=&quot;https://github.com/Dash-Industry-Forum/dash.js&quot;&gt;Dash.js&lt;/a&gt; npm package by running:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i dashjs
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, create a component called &lt;code&gt;&amp;lt;DashVideoPlayer /&amp;gt;&lt;/code&gt; and initialize the Dash &lt;a href=&quot;https://cdn.dashjs.org/latest/jsdoc/module-MediaPlayer.html&quot;&gt;MediaPlayer&lt;/a&gt; instance by pointing it to the MPD file when the component mounts.&lt;/p&gt;&lt;p class=&quot;c-pre-sidenote--left&quot;&gt;The ref callback function runs upon the component mounting, and within the callback function, &lt;code&gt;playerRef&lt;/code&gt; will refer to the actual Dash &lt;a href=&quot;https://cdn.dashjs.org/latest/jsdoc/module-MediaPlayer.html&quot;&gt;MediaPlayer&lt;/a&gt; instance and be bound with event listeners. We also include the original MP4 URL in the &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; element as a fallback if the browser doesn’t support MPEG-DASH.&lt;/p&gt;&lt;p class=&quot;c-sidenote c-sidenote--right&quot;&gt;If you’re using &lt;strong&gt;Next.js app router&lt;/strong&gt;, remember to add the &lt;code&gt;‘use client’&lt;/code&gt; directive to enable client-side hydration, as the video player is only initialized on the client side.&lt;/p&gt;&lt;p&gt;Here is the full example:&lt;/p&gt;&lt;h3 id=&quot;result&quot;&gt;Result&lt;/h3&gt;&lt;p&gt;Observe the changes in the video file when the network connectivity is adjusted from Fast 4G to 3G using Chrome DevTools. It switches from 480p to 360p, showing how the experience is optimized for more or less available bandwidth.&lt;/p&gt;&lt;figure class=&quot;video-embed-container break-out&quot;&gt;&lt;div class=&quot;video-embed-container--wrapper&quot;&gt;&lt;iframe class=&quot;video-embed-container--wrapper-iframe&quot; src=&quot;https://player.vimeo.com/video/1070045535&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; fullscreen; picture-in-picture&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;figcaption&gt;ABR example&lt;/figcaption&gt;&lt;/figure&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;That’s it! We just implemented a working DASH-compatible video player in React to establish a video with adaptive bitrate streaming. Again, the benefits of this are rooted in &lt;strong&gt;performance&lt;/strong&gt;. When we adopt ABR streaming, we’re requesting the video in smaller chunks, allowing for more immediate playback than we’d get if we needed to fully download the video file first. And we’ve done it in a way that supports multiple versions of the same video, allowing us to serve the best format for the user’s device.&lt;/p&gt;&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;“&lt;a href=&quot;https://www.zeng.dev/post/2023-http-range-and-play-mp4-in-browser/&quot;&gt;Http Range Request And MP4 Video Play In Browser&lt;/a&gt;,” Zeng Xu&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Media/Audio_and_video_delivery/Setting_up_adaptive_streaming_media_sources&quot;&gt;Setting up adaptive streaming media sources&lt;/a&gt; (Mozilla Developer Network)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Media/DASH_Adaptive_Streaming_for_HTML_5_Video&quot;&gt;DASH Adaptive Streaming for HTML video&lt;/a&gt; (Mozilla Developer Network)&lt;/li&gt;&lt;/ul&gt;</description><link>https://www.smashingmagazine.com/2025/03/adaptive-video-streaming-dashjs-react/</link><guid isPermaLink="false">https://www.smashingmagazine.com/2025/03/adaptive-video-streaming-dashjs-react/</guid><pubDate>Mon, 20 Jul 2026 16:00:00 GMT</pubDate><author>Teng Wei Herr</author><category>Video</category><category>Techniques</category><category>Tools</category><category>React</category><category>Coding</category></item><item><title>The Forensics Of React Server Components (RSCs)</title><description>&lt;h1 id=&quot;main-heading&quot; class=&quot;article-header--title&quot;&gt;The Forensics Of React Server Components (RSCs)&lt;/h1&gt;&lt;span id=&quot;article__start&quot; class=&quot;summary__heading&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;We love client-side rendering for the way it relieves the server of taxing operations, but serving an empty HTML page often leads to taxing user experiences during the initial page load. We love server-side rendering because it allows us to serve static assets on speedy CDNs, but they’re unfit for large-scale projects with dynamic content. React Server Components (RSCs) combine the best of both worlds, and author Lazar Nikolov thoroughly examines how we got here with a deep look at the impact that RSCs have on the page load timeline.&lt;ul class=&quot;meta-box meta-box--article&quot;&gt;&lt;li class=&quot;meta-box--item meta-box--published&quot;&gt;24 min read&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--tags&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/category/react&quot;&gt;React&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/javascript&quot;&gt;JavaScript&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/coding&quot;&gt;Coding&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/performance&quot;&gt;Performance&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--share&quot;&gt;Share on &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/intent/tweet?text=The%20Forensics%20Of%20React%20Server%20Components%20%28RSCs%29&amp;amp;url=https%3A%2F%2Fwww.smashingmagazine.com%2f2024%2f05%2fforensics-react-server-components%2f&amp;amp;via=smashingmag&quot;&gt;Twitter&lt;/a&gt;,&amp;nbsp;&lt;a rel=&quot;nofollow&quot; href=&quot;https://data.smashing.services/ball?uri=//www.linkedin.com/shareArticle?url=https://www.smashingmagazine.com%2f2024%2f05%2fforensics-react-server-components%2f&amp;amp;title=The%20Forensics%20Of%20React%20Server%20Components%20%28RSCs%29&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In this article, we’re going to look deeply at React Server Components (RSCs). They are the latest innovation in React’s ecosystem, leveraging both server-side and client-side rendering as well as &lt;a href=&quot;https://en.wikipedia.org/wiki/Chunked_transfer_encoding&quot;&gt;streaming HTML&lt;/a&gt; to deliver content as fast as possible.&lt;/p&gt;&lt;p&gt;We will get really nerdy to get a full understanding of how RSCs fit into the React picture, the level of control they offer over the rendering lifecycle of components, and what page loads look like with RSCs in place.&lt;/p&gt;&lt;p&gt;But before we dive into all of that, I think it’s worth looking back at how React has rendered websites up until this point to set the context for why we need RSCs in the first place.&lt;/p&gt;&lt;h2 id=&quot;the-early-days-react-client-side-rendering&quot;&gt;The Early Days: React Client-Side Rendering&lt;/h2&gt;&lt;p&gt;The first React apps were rendered on the client side, i.e., in the browser. As developers, we wrote apps with JavaScript classes as components and packaged everything up using bundlers, like Webpack, in a nicely compiled and tree-shaken heap of code ready to ship in a production environment.&lt;/p&gt;&lt;p&gt;The HTML that returned from the server contained a few things, including:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;An HTML document with metadata in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; and a blank &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; in the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; used as a hook to inject the app into the DOM;&lt;/li&gt;&lt;li&gt;JavaScript resources containing React’s core code and the actual code for the web app, which would generate the user interface and populate the app inside of the empty &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;566&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg&quot; sizes=&quot;100vw&quot; alt=&quot;Diagram of the client-side rendering process of a React app, starting with a blank loading page in the browser followed by a series of processes connected to CDNs and APIs to produce content on the loading page.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 1. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/1-client-side-rendering-process.jpg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A web app under this process is only fully interactive once JavaScript has fully completed its operations. You can probably already see the tension here that comes with an &lt;strong&gt;improved developer experience (DX) that negatively impacts the user experience (UX)&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;The truth is that there were (and are) pros and cons to CSR in React. Looking at the positives, web applications delivered &lt;strong&gt;smooth, quick transitions&lt;/strong&gt; that reduced the overall time it took to load a page, thanks to reactive components that update with user interactions without triggering page refreshes. CSR lightens the server load and allows us to serve assets from speedy content delivery networks (CDNs) capable of delivering content to users from a server location geographically closer to the user for even more optimized page loads.&lt;/p&gt;&lt;p&gt;There are also not-so-great consequences that come with CSR, most notably perhaps that components could fetch data independently, leading to &lt;a href=&quot;https://blog.sentry.io/fetch-waterfall-in-react/&quot;&gt;&lt;strong&gt;waterfall network requests&lt;/strong&gt;&lt;/a&gt; that dramatically slow things down. This may sound like a minor nuisance on the UX side of things, but the damage can actually be quite large on a human level. Eric Bailey’s “&lt;a href=&quot;https://ericwbailey.design/published/modern-health-frameworks-performance-and-harm/&quot;&gt;Modern Health, frameworks, performance, and harm&lt;/a&gt;” should be a cautionary tale for all CSR work.&lt;/p&gt;&lt;p&gt;Other negative CSR consequences are not quite as severe but still lead to damage. For example, it used to be that an HTML document containing nothing but metadata and an empty &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; was illegible to search engine crawlers that never get the fully-rendered experience. While that’s solved today, the SEO hit at the time was an anchor on company sites that rely on search engine traffic to generate revenue.&lt;/p&gt;&lt;h2 id=&quot;the-shift-server-side-rendering-ssr&quot;&gt;The Shift: Server-Side Rendering (SSR)&lt;/h2&gt;&lt;p&gt;Something needed to change. CSR presented developers with a powerful new approach for constructing speedy, interactive interfaces, but users everywhere were inundated with blank screens and loading indicators to get there. The solution was to move the rendering experience from the &lt;strong&gt;client&lt;/strong&gt; to the &lt;strong&gt;server&lt;/strong&gt;. I know it sounds funny that we needed to improve something by going back to the way it was before.&lt;/p&gt;&lt;p&gt;So, yes, React gained server-side rendering (SSR) capabilities. At one point, SSR was such a topic in the React community that &lt;a href=&quot;https://sentry.io/resources/moving-to-server-side-rendering/&quot;&gt;it had a moment&lt;/a&gt; in the spotlight. The move to SSR brought significant changes to app development, specifically in how it influenced React behavior and how content could be delivered by way of servers instead of browsers.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;600&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg&quot; sizes=&quot;100vw&quot; alt=&quot;Diagram of the server-side rendering process of a React app, starting with a blank loading page in the browser followed by a screen of un-interactive content, then a fully interactive page of content.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 2. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/2-diagram-server-side-rendering-process.jpg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3 id=&quot;addressing-csr-limitations&quot;&gt;Addressing CSR Limitations&lt;/h3&gt;&lt;p&gt;Instead of sending a blank HTML document with SSR, we rendered the initial HTML on the server and sent it to the browser. The browser was able to immediately start displaying the content without needing to show a loading indicator. This significantly improves the &lt;a href=&quot;https://docs.sentry.io/product/performance/web-vitals/web-vitals-concepts/#first-contentful-paint-fcp&quot;&gt;First Contentful Paint (FCP) performance metric in Web Vitals&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Server-side rendering also fixed the SEO issues that came with CSR. Since the crawlers received the content of our websites directly, they were then able to index it right away. The data fetching that happens initially also takes place on the server, which is a plus because it’s closer to the data source and can eliminate fetch waterfalls &lt;a href=&quot;https://blog.sentry.io/fetch-waterfall-in-react/#fetch-data-on-server-to-avoid-a-fetch-waterfall&quot;&gt;&lt;em&gt;if done properly&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;&lt;h3 id=&quot;hydration&quot;&gt;Hydration&lt;/h3&gt;&lt;p&gt;SSR has its own complexities. For React to make the static HTML received from the server interactive, it needs to &lt;strong&gt;hydrate&lt;/strong&gt; it. Hydration is the process that happens when React reconstructs its Virtual Document Object Model (DOM) on the client side based on what was in the DOM of the initial HTML.&lt;/p&gt;&lt;blockquote&gt;&lt;strong&gt;Note&lt;/strong&gt;: React maintains its own &lt;a href=&quot;https://legacy.reactjs.org/docs/faq-internals.html&quot;&gt;Virtual DOM&lt;/a&gt; because it’s faster to figure out updates on it instead of the actual DOM. It synchronizes the actual DOM with the Virtual DOM when it needs to update the UI but performs the diffing algorithm on the Virtual DOM.&lt;/blockquote&gt;&lt;p&gt;We now have two flavors of Reacts:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;A server-side flavor&lt;/strong&gt; that knows how to render static HTML from our component tree,&lt;/li&gt;&lt;li&gt;&lt;strong&gt;A client-side flavor&lt;/strong&gt; that knows how to make the page interactive.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;We’re still shipping React and code for the app to the browser because — in order to hydrate the initial HTML — React needs the same components on the client side that were used on the server. During hydration, &lt;a href=&quot;https://css-tricks.com/how-react-reconciliation-works/&quot;&gt;React performs a process called&lt;/a&gt; &lt;a href=&quot;https://css-tricks.com/how-react-reconciliation-works/&quot;&gt;&lt;em&gt;reconciliation&lt;/em&gt;&lt;/a&gt; in which it compares the server-rendered DOM with the client-rendered DOM and tries to identify differences between the two. If there are differences between the two DOMs, React attempts to fix them by rehydrating the component tree and updating the component hierarchy to match the server-rendered structure. And if there are &lt;em&gt;still&lt;/em&gt; inconsistencies that cannot be resolved, React will throw errors to indicate the problem. This problem is commonly known as a &lt;em&gt;hydration error&lt;/em&gt;.&lt;/p&gt;&lt;h3 id=&quot;ssr-drawbacks&quot;&gt;SSR Drawbacks&lt;/h3&gt;&lt;p&gt;SSR is not a silver bullet solution that addresses CSR limitations. SSR comes with its own drawbacks. Since we moved the initial HTML rendering and data fetching to the server, those servers are now experiencing a much greater load than when we loaded everything on the client.&lt;/p&gt;&lt;p&gt;Remember when I mentioned that SSR generally improves the FCP performance metric? That may be true, but the &lt;a href=&quot;https://docs.sentry.io/product/performance/web-vitals/web-vitals-concepts/#time-to-first-byte-ttfb&quot;&gt;Time to First Byte (TTFB) performance metric&lt;/a&gt; took a negative hit with SSR. The browser literally has to wait for the server to fetch the data it needs, generate the initial HTML, and send the first byte. And while TTFB is not a Core Web Vital metric in itself, it influences the metrics. A negative TTFB leads to negative Core Web Vitals metrics.&lt;/p&gt;&lt;p&gt;Another drawback of SSR is that the entire page is unresponsive until client-side React has finished hydrating it. Interactive elements cannot listen and “react” to user interactions before React hydrates them, i.e., React attaches the intended event listeners to them. The hydration process is typically fast, but the internet connection and hardware capabilities of the device in use can slow down rendering by a noticeable amount.&lt;/p&gt;&lt;h2 id=&quot;the-present-a-hybrid-approach&quot;&gt;The Present: A Hybrid Approach&lt;/h2&gt;&lt;p&gt;So far, we have covered two different flavors of React rendering: CSR and SSR. While the two were attempts to improve one another, we now get the best of both worlds, so to speak, as SSR has branched into three additional React flavors that offer a hybrid approach in hopes of reducing the limitations that come with CSR and SSR.&lt;/p&gt;&lt;p&gt;We’ll look at the first two — &lt;strong&gt;static site generation&lt;/strong&gt; and &lt;strong&gt;incremental static regeneration&lt;/strong&gt; — before jumping into an entire discussion on React Server Components, the third flavor.&lt;/p&gt;&lt;h3 id=&quot;static-site-generation-ssg&quot;&gt;Static Site Generation (SSG)&lt;/h3&gt;&lt;p&gt;Instead of regenerating the same HTML code on every request, we came up with SSG. This React flavor compiles and builds the entire app at build time, generating static (as in vanilla HTML and CSS) files that are, in turn, hosted on a speedy CDN.&lt;/p&gt;&lt;p&gt;As you might suspect, this hybrid approach to rendering is a nice fit for smaller projects where the content doesn’t change much, like a marketing site or a personal blog, as opposed to larger projects where content may change with user interactions, like an e-commerce site.&lt;/p&gt;&lt;p&gt;SSG reduces the burden on the server while improving performance metrics related to TTFB because the server no longer has to perform heavy, expensive tasks for re-rendering the page.&lt;/p&gt;&lt;h3 id=&quot;incremental-static-regeneration-isr&quot;&gt;Incremental Static Regeneration (ISR)&lt;/h3&gt;&lt;p&gt;One SSG drawback is having to rebuild all of the app’s code when a content change is needed. The content is set in stone — being static and all — and there’s no way to change just one part of it without rebuilding the whole thing.&lt;/p&gt;&lt;p&gt;The Next.js team created the second hybrid flavor of React that addresses the drawback of complete SSG rebuilds: &lt;strong&gt;incremental static regeneration (ISR)&lt;/strong&gt;. The name says a lot about the approach in that ISR only rebuilds what’s needed instead of the entire thing. We generate the “initial version” of the page statically during build time but are also able to rebuild any page containing stale data &lt;em&gt;after&lt;/em&gt; a user lands on it (i.e., the server request triggers the data check).&lt;/p&gt;&lt;p&gt;From that point on, the server will serve new versions of that page statically in increments when needed. That makes ISR a hybrid approach that is neatly positioned between SSG and traditional SSR.&lt;/p&gt;&lt;p&gt;At the same time, ISR does not address the “stale content” symptom, where users may visit a page before it has finished being generated. Unlike SSG, ISR needs an actual server to regenerate individual pages in response to a user’s browser making a server request. That means we lose the valuable ability to deploy ISR-based apps on a CDN for optimized asset delivery.&lt;/p&gt;&lt;h2 id=&quot;the-future-react-server-components&quot;&gt;The Future: React Server Components&lt;/h2&gt;&lt;p&gt;Up until this point, we’ve juggled between CSR, SSR, SSG, and ISR approaches, where all make some sort of trade-off, negatively affecting performance, development complexity, and user experience. Newly introduced &lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/rendering/server-components&quot;&gt;React Server Components&lt;/a&gt; (RSC) aim to address most of these drawbacks by allowing us — the developer — to &lt;strong&gt;choose the right rendering strategy for each individual React component&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;RSCs can significantly reduce the amount of JavaScript shipped to the client since we can selectively decide which ones to serve statically on the server and which render on the client side. There’s a lot more control and flexibility for striking the right balance for your particular project.&lt;/p&gt;&lt;blockquote&gt;&lt;strong&gt;Note:&lt;/strong&gt; It’s important to keep in mind that as we adopt more advanced architectures, like RSCs, monitoring solutions become invaluable. Sentry offers robust &lt;a href=&quot;https://docs.sentry.io/product/performance/&quot;&gt;performance monitoring&lt;/a&gt; and error-tracking capabilities that help you keep an eye on the real-world performance of your RSC-powered application. Sentry also helps you gain insights into how your releases are performing and how stable they are, which is yet another crucial feature to have while migrating your existing applications to RSCs. Implementing Sentry in an RSC-enabled framework like &lt;a href=&quot;https://sentry.io/for/nextjs/&quot;&gt;Next.js&lt;/a&gt; is as easy as running a single terminal command.&lt;/blockquote&gt;&lt;p&gt;But what exactly &lt;em&gt;is&lt;/em&gt; an RSC? Let’s pick one apart to see how it works under the hood.&lt;/p&gt;&lt;h2 id=&quot;the-anatomy-of-react-server-components&quot;&gt;The Anatomy of React Server Components&lt;/h2&gt;&lt;p&gt;This new approach introduces two types of rendering components: &lt;strong&gt;Server Components&lt;/strong&gt; and &lt;strong&gt;Client Components&lt;/strong&gt;. The differences between these two are not &lt;em&gt;how&lt;/em&gt; they function but &lt;em&gt;where&lt;/em&gt; they execute and the environments they’re designed for. At the time of this writing, the only way to use RSCs is through React frameworks. And at the moment, there are only three frameworks that support them: &lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/rendering/server-components&quot;&gt;Next.js&lt;/a&gt;, &lt;a href=&quot;https://www.gatsbyjs.com/docs/conceptual/partial-hydration/&quot;&gt;Gatsby&lt;/a&gt;, and &lt;a href=&quot;https://redwoodjs.com/blog/rsc-now-in-redwoodjs&quot;&gt;RedwoodJS&lt;/a&gt;.&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;763&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg&quot; sizes=&quot;100vw&quot; alt=&quot;Wire diagram showing connected server components and client components represented as gray and blue dots, respectively.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 3: Example of an architecture consisting of Server Components and Client Components. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/3-wire-diagram-server-client-components.jpg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3 id=&quot;server-components&quot;&gt;Server Components&lt;/h3&gt;&lt;p&gt;Server Components are designed to be executed on the server, and their code is never shipped to the browser. The HTML output and any props they might be accepting are the only pieces that are served. This approach has multiple performance benefits and user experience enhancements:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Server Components allow for large dependencies to remain on the server side.&lt;/strong&gt;&lt;br&gt;Imagine using a large library for a component. If you’re executing the component on the client side, it means that you’re also shipping the full library to the browser. With Server Components, you’re only taking the static HTML output and avoiding having to ship any JavaScript to the browser. Server Components are truly static, and they remove the whole hydration step.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Server Components are located much closer to the data sources — e.g., databases or file systems — they need to generate code.&lt;/strong&gt;&lt;br&gt;They also leverage the server’s computational power to speed up compute-intensive rendering tasks and send only the generated results back to the client. They are also generated in a single pass, which &lt;a href=&quot;https://blog.sentry.io/fetch-waterfall-in-react/#fetch-data-on-server-to-avoid-a-fetch-waterfall&quot;&gt;avoids request waterfalls and HTTP round trips&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Server Components safely keep sensitive data and logic away from the browser.&lt;/strong&gt;&lt;br&gt;That’s thanks to the fact that personal tokens and API keys are executed on a secure server rather than the client.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The rendering results can be cached and reused between subsequent requests and even across different sessions.&lt;/strong&gt;&lt;br&gt;This significantly reduces rendering time, as well as the overall amount of data that is fetched for each request.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This architecture also makes use of &lt;strong&gt;HTML streaming&lt;/strong&gt;, which means the server defers generating HTML for specific components and instead renders a fallback element in their place while it works on sending back the generated HTML. Streaming Server Components wrap components in &lt;a href=&quot;https://react.dev/reference/react/Suspense&quot;&gt;&lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;&lt;/a&gt; tags that provide a fallback value. The implementing framework uses the fallback initially but streams the newly generated content when it‘s ready. We’ll talk more about streaming, but let’s first look at Client Components and compare them to Server Components.&lt;/p&gt;&lt;h3 id=&quot;client-components&quot;&gt;Client Components&lt;/h3&gt;&lt;p&gt;Client Components are the components we already know and love. They’re executed on the client side. Because of this, Client Components are capable of handling user interactions and have access to the browser APIs like &lt;code&gt;localStorage&lt;/code&gt; and geolocation.&lt;/p&gt;&lt;p&gt;The term “Client Component” doesn’t describe anything new; they merely are given the label to help distinguish the “old” CSR components from Server Components. Client Components are defined by a &lt;a href=&quot;https://react.dev/reference/react/use-server&quot;&gt;&lt;code&gt;&quot;use client&quot;&lt;/code&gt;&lt;/a&gt; directive at the top of their files.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&quot;use client&quot;
export default function LikeButton() {
  const likePost = () =&amp;gt; {
    // ...
  }
  return (
    &amp;lt;button onClick={likePost}&amp;gt;Like&amp;lt;/button&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In Next.js, all components are Server Components by default. That’s why we need to explicitly define our Client Components with &lt;code&gt;&quot;use client&quot;&lt;/code&gt;. There’s also a &lt;code&gt;&quot;use server&quot;&lt;/code&gt; directive, but it’s used for Server Actions (which are RPC-like actions that invoked from the client, but executed on the server). You don’t use it to define your Server Components.&lt;/p&gt;&lt;p&gt;You might (rightfully) assume that Client Components are only rendered on the client, but Next.js renders Client Components on the server to generate the initial HTML. As a result, browsers can immediately start rendering them and then perform hydration later.&lt;/p&gt;&lt;h3 id=&quot;the-relationship-between-server-components-and-client-components&quot;&gt;The Relationship Between Server Components and Client Components&lt;/h3&gt;&lt;p&gt;Client Components can only &lt;em&gt;explicitly&lt;/em&gt; import other Client Components. In other words, we’re unable to import a Server Component into a Client Component because of re-rendering issues. But we can have Server Components in a Client Component’s subtree — only passed through the &lt;code&gt;children&lt;/code&gt; prop. Since Client Components live in the browser and they handle user interactions or define their own state, they get to re-render often. When a Client Component re-renders, so will its subtree. But if its subtree contains Server Components, how would they re-render? They don’t live on the client side. That’s why the React team put that limitation in place.&lt;/p&gt;&lt;p&gt;But hold on! We actually &lt;em&gt;can&lt;/em&gt; import Server Components into Client Components. It’s just not a direct one-to-one relationship because the Server Component will be converted into a Client Component. If you’re using server APIs that you can’t use in the browser, you’ll get an error; if not — you’ll have a Server Component whose code gets “leaked” to the browser.&lt;/p&gt;&lt;p&gt;This is an incredibly important nuance to keep in mind as you work with RSCs.&lt;/p&gt;&lt;h2 id=&quot;the-rendering-lifecycle&quot;&gt;The Rendering Lifecycle&lt;/h2&gt;&lt;p&gt;Here’s the order of operations that Next.js takes to stream contents:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The app router matches the page’s URL to a Server Component, builds the component tree, and instructs the server-side React to render that Server Component and all of its children components.&lt;/li&gt;&lt;li&gt;During render, React generates an “RSC Payload”. The RSC Payload informs Next.js about the page and what to expect in return, as well as what to fall back to during a &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;If React encounters a suspended component, it pauses rendering that subtree and uses the suspended component’s fallback value.&lt;/li&gt;&lt;li&gt;When React loops through the last static component, Next.js prepares the generated HTML and the RSC Payload before streaming it back to the client through one or multiple chunks.&lt;/li&gt;&lt;li&gt;The client-side React then uses the instructions it has for the RSC Payload and client-side components to render the UI. It also hydrates each Client Component as they load.&lt;/li&gt;&lt;li&gt;The server streams in the suspended Server Components as they become available as an RSC Payload. Children of Client Components are also hydrated at this time if the suspended component contains any.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;We will look at the RSC rendering lifecycle from the browser’s perspective momentarily. For now, the following figure illustrates the outlined steps we covered.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;489&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg&quot; sizes=&quot;100vw&quot; alt=&quot;Wire diagram of the RSC rendering lifecycle going from a blank page to a page shell to a complete page.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 4: Diagram of the RSC Rendering Lifecycle. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/4-wire-diagram-rsc-rendering-lifecycle.jpg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;We’ll see this operation flow from the browser’s perspective in just a bit.&lt;/p&gt;&lt;h2 id=&quot;rsc-payload&quot;&gt;RSC Payload&lt;/h2&gt;&lt;p&gt;The RSC payload is a special data format that the server generates as it renders the component tree, and it includes the following:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The rendered HTML,&lt;/li&gt;&lt;li&gt;Placeholders where the Client Components should be rendered,&lt;/li&gt;&lt;li&gt;References to the Client Components’ JavaScript files,&lt;/li&gt;&lt;li&gt;Instructions on which JavaScript files it should invoke,&lt;/li&gt;&lt;li&gt;Any props passed from a Server Component to a Client Component.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;There’s no reason to worry much about the RSC payload, but it’s worth understanding what exactly the RSC payload contains. Let’s examine an example (truncated for brevity) from a &lt;a href=&quot;https://github.com/nikolovlazar/rsc-forensics&quot;&gt;demo app I created&lt;/a&gt;:&lt;/p&gt;&lt;p&gt;To find this code in the demo app, open your browser’s developer tools at the Elements tab and look at the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags at the bottom of the page. They’ll contain lines like:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;self.__next_f.push([1,&quot;PAYLOAD_STRING_HERE&quot;]).
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Every line from the snippet above is an individual RSC payload. You can see that each line starts with a number or a letter, followed by a colon, and then an array that’s sometimes prefixed with letters. We won’t get into too deep in detail as to what they mean, but in general:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;HL&lt;/code&gt; payloads&lt;/strong&gt; are called “hints” and link to specific resources like CSS and fonts.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;I&lt;/code&gt; payloads&lt;/strong&gt; are called “modules,” and they invoke specific scripts. This is how Client Components are being loaded as well. If the Client Component is part of the main bundle, it’ll execute. If it’s not (meaning it’s lazy-loaded), a fetcher script is added to the main bundle that fetches the component’s CSS and JavaScript files when it needs to be rendered. There’s going to be an &lt;code&gt;I&lt;/code&gt; payload sent from the server that invokes the fetcher script when needed.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;&quot;$&quot;&lt;/code&gt; payloads&lt;/strong&gt; are DOM definitions generated for a certain Server Component. They are usually accompanied by actual static HTML streamed from the server. That’s what happens when a suspended component becomes ready to be rendered: the server generates its static HTML and RSC Payload and then streams both to the browser.&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;streaming&quot;&gt;Streaming&lt;/h2&gt;&lt;p&gt;Streaming allows us to progressively render the UI from the server. With RSCs, each component is capable of fetching its own data. Some components are fully static and ready to be sent immediately to the client, while others require more work before loading. Based on this, Next.js splits that work into multiple chunks and streams them to the browser as they become ready. So, when a user visits a page, the server invokes all Server Components, generates the initial HTML for the page (i.e., the page shell), replaces the “suspended” components’ contents with their fallbacks, and streams all of that through one or multiple chunks back to the client.&lt;/p&gt;&lt;p&gt;The server returns a &lt;code&gt;Transfer-Encoding: chunked&lt;/code&gt; header that lets the browser know to expect streaming HTML. This prepares the browser for receiving multiple chunks of the document, rendering them as it receives them. We can actually see the header when opening Developer Tools at the Network tab. Trigger a refresh and click on the document request.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;238&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg&quot; sizes=&quot;100vw&quot; alt=&quot;Response header output highlighting the line containing the chunked transfer endcoding&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 5: Providing a hint to the browser to expect HTML streaming. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/5-streaming-header.jpeg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;We can also debug the way Next.js sends the chunks in a terminal with the &lt;code&gt;curl&lt;/code&gt; command:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -D - --raw localhost:3000 &amp;gt; chunked-response.txt
&lt;/code&gt;&lt;/pre&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;416&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg&quot; sizes=&quot;100vw&quot; alt=&quot;Headers and chunked HTML payloads.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 6. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/6-chunked-response.jpeg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;You probably see the pattern. For each chunk, the server responds with the chunk’s size before sending the chunk’s contents. Looking at the output, we can see that the server streamed the entire page in 16 different chunks. At the end, the server sends back a zero-sized chunk, indicating the end of the stream.&lt;/p&gt;&lt;p&gt;The first chunk starts with the &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/code&gt; declaration. The second-to-last chunk, meanwhile, contains the closing &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;/html&amp;gt;&lt;/code&gt; tags. So, we can see that the server streams the entire document from top to bottom, then pauses to wait for the suspended components, and finally, at the end, closes the body and HTML before it stops streaming.&lt;/p&gt;&lt;p&gt;Even though the server hasn’t completely finished streaming the document, the browser’s fault tolerance features allow it to draw and invoke whatever it has at the moment without waiting for the closing &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;/html&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;&lt;h3 id=&quot;suspending-components&quot;&gt;Suspending Components&lt;/h3&gt;&lt;p&gt;We learned from the render lifecycle that when a page is visited, Next.js matches the RSC component for that page and asks React to render its subtree in HTML. When React stumbles upon a suspended component (i.e., async function component), it grabs its fallback value from the &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; component (or the &lt;code&gt;loading.js&lt;/code&gt; file if it’s a Next.js route), renders that instead, then continues loading the other components. Meanwhile, the RSC invokes the async component in the background, which is streamed later as it finishes loading.&lt;/p&gt;&lt;p&gt;At this point, Next.js has returned a full page of static HTML that includes either the components themselves (rendered in static HTML) or their fallback values (if they’re suspended). It takes the static HTML and RSC payload and streams them back to the browser through one or multiple chunks.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg&quot; sizes=&quot;100vw&quot; alt=&quot;Showing suspended component fallbacks&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 7. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/7-fallbacks-suspended-components.jpeg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;As the suspended components finish loading, React generates HTML recursively while looking for other nested &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; boundaries, generates their RSC payloads and then lets Next.js stream the HTML and RSC Payload back to the browser as new chunks. When the browser receives the new chunks, it has the HTML and RSC payload it needs and is ready to replace the fallback element from the DOM with the newly-streamed HTML. And so on.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;399&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg&quot; sizes=&quot;100vw&quot; alt=&quot;Static HTML and RSC Payload replacing suspended fallback values.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 8. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/8-suspended-components-html.jpeg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;In Figures 7 and 8, notice how the fallback elements have a unique ID in the form of &lt;code&gt;B:0&lt;/code&gt;, &lt;code&gt;B:1&lt;/code&gt;, and so on, while the actual components have a similar ID in a similar form: &lt;code&gt;S:0&lt;/code&gt; and &lt;code&gt;S:1&lt;/code&gt;, and so on.&lt;/p&gt;&lt;p&gt;Along with the first chunk that contains a suspended component’s HTML, the server also ships an &lt;code&gt;$RC&lt;/code&gt; function (i.e., &lt;code&gt;completeBoundary&lt;/code&gt; from &lt;a href=&quot;https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js#L46&quot;&gt;React’s source code&lt;/a&gt;) that knows how to find the &lt;code&gt;B:0&lt;/code&gt; fallback element in the DOM and replace it with the &lt;code&gt;S:0&lt;/code&gt; template it received from the server. That’s the “replacer” function that lets us see the component contents when they arrive in the browser.&lt;/p&gt;&lt;p&gt;The entire page eventually finishes loading, chunk by chunk.&lt;/p&gt;&lt;h3 id=&quot;lazy-loading-components&quot;&gt;Lazy-Loading Components&lt;/h3&gt;&lt;p&gt;If a suspended Server Component contains a lazy-loaded Client Component, Next.js will also send an RSC payload chunk containing instructions on how to fetch and load the lazy-loaded component’s code. This represents a &lt;em&gt;significant performance improvement&lt;/em&gt; because the page load isn’t dragged out by JavaScript, which might not even be loaded during that session.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;442&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg&quot; sizes=&quot;100vw&quot; alt=&quot;Fetching additional JavaScript and CSS files for a lazy-loaded Client Component, as shown in developer tools.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 9. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/9-fetching-lazy-loaded-scripts.jpeg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;At the time I’m writing this, the dynamic method to lazy-load a Client Component in a Server Component in Next.js does not work as you might expect. To effectively lazy-load a Client Component, put it in a &lt;a href=&quot;https://github.com/nikolovlazar/rsc-forensics/blob/main/src/components/addToCartWrapper.js&quot;&gt;“wrapper” Client Component&lt;/a&gt; that uses the &lt;code&gt;dynamic&lt;/code&gt; method itself to lazy-load the actual Client Component. The wrapper will be turned into a script that fetches and loads the Client Component’s JavaScript and CSS files at the time they’re needed.&lt;/p&gt;&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;&lt;p&gt;I know that’s a lot of plates spinning and pieces moving around at various times. What it boils down to, however, is that a page visit triggers Next.js to render as much HTML as it can, using the fallback values for any suspended components, and then sends that to the browser. Meanwhile, Next.js triggers the suspended async components and gets them formatted in HTML and contained in RSC Payloads that are streamed to the browser, one by one, along with an &lt;code&gt;$RC&lt;/code&gt; script that knows how to swap things out.&lt;/p&gt;&lt;h2 id=&quot;the-page-load-timeline&quot;&gt;The Page Load Timeline&lt;/h2&gt;&lt;p&gt;By now, we should have a solid understanding of how RSCs work, how Next.js handles their rendering, and how all the pieces fit together. In this section, we’ll zoom in on what exactly happens when we visit an RSC page in the browser.&lt;/p&gt;&lt;h3 id=&quot;the-initial-load&quot;&gt;The Initial Load&lt;/h3&gt;&lt;p&gt;As we mentioned in the TL;DR section above, when visiting a page, Next.js will render the initial HTML minus the suspended component and stream it to the browser as part of the first streaming chunks.&lt;/p&gt;&lt;p&gt;To see everything that happens during the page load, we’ll visit the “Performance” tab in Chrome DevTools and click on the “reload” button to reload the page and capture a profile. Here’s what that looks like:&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;442&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg&quot; sizes=&quot;100vw&quot; alt=&quot;Showing the first chunks of HTML streamed at the beginning of the timeline in DevTools.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 10. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/10-first-chunks-being-streamed.jpeg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;When we zoom in at the very beginning, we can see the first “Parse HTML” span. That’s the server streaming the first chunks of the document to the browser. The browser has just received the initial HTML, which contains the page shell and a few links to resources like fonts, CSS files, and JavaScript. The browser starts to invoke the scripts.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;442&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg&quot; sizes=&quot;100vw&quot; alt=&quot;The first frames appear, and parts of the page are rendered&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 11. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/11-first-frames.jpeg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;After some time, we start to see the page’s first frames appear, along with the initial JavaScript scripts being loaded and hydration taking place. If you look at the frame closely, you’ll see that the whole page shell is rendered, and “loading” components are used in the place where there are suspended Server Components. You might notice that this takes place around 800ms, while the browser started to get the first HTML at 100ms. During those 700ms, the browser is continuously receiving chunks from the server.&lt;/p&gt;&lt;p&gt;Bear in mind that this is a Next.js demo app running locally in development mode, so it’s going to be slower than when it’s running in production mode.&lt;/p&gt;&lt;h3 id=&quot;the-suspended-component&quot;&gt;The Suspended Component&lt;/h3&gt;&lt;p&gt;Fast forward few seconds and we see another “Parse HTML” span in the page load timeline, but this one it indicates that a suspended Server Component finished loading and is being streamed to the browser.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;442&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg&quot; sizes=&quot;100vw&quot; alt=&quot;The suspended component’s HTML and RSC Payload are streamed to the browser, as shown in the developer tools Network tab.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 12. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/12-suspended-component.jpeg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;We can also see that a lazy-loaded Client Component is discovered at the same time, and it contains CSS and JavaScript files that need to be fetched. These files weren’t part of the initial bundle because the component isn’t needed until later on; the code is split into their own files.&lt;/p&gt;&lt;p&gt;This way of code-splitting certainly improves the performance of the initial page load. It also makes sure that the Client Component’s code is shipped only if it’s needed. If the Server Component (which acts as the Client Component’s parent component) throws an error, then the Client Component does not load. It doesn’t make sense to load all of its code before we know whether it will load or not.&lt;/p&gt;&lt;p&gt;Figure 12 shows the &lt;code&gt;DOMContentLoaded&lt;/code&gt; event is reported at the end of the page load timeline. And, just before that, we can see that the &lt;code&gt;localhost&lt;/code&gt; HTTP request comes to an end. That means the server has likely sent the last zero-sized chunk, indicating to the client that the data is fully transferred and that the streaming communication can be closed.&lt;/p&gt;&lt;h3 id=&quot;the-end-result&quot;&gt;The End Result&lt;/h3&gt;&lt;p&gt;The main &lt;code&gt;localhost&lt;/code&gt; HTTP request took around five seconds, but thanks to streaming, we began seeing page contents load much earlier than that. If this was a traditional SSR setup, we would likely be staring at a blank screen for those five seconds before anything arrives. On the other hand, if this was a traditional CSR setup, we would likely have shipped &lt;em&gt;a lot&lt;/em&gt; more of JavaScript and put a heavy burden on both the browser and network.&lt;/p&gt;&lt;p&gt;This way, however, the app was fully interactive in those five seconds. We were able to navigate between pages and interact with Client Components that have loaded as part of the initial main bundle. This is a pure win from a user experience standpoint.&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;RSCs mark a significant evolution in the React ecosystem. They leverage the strengths of server-side and client-side rendering while embracing HTML streaming to speed up content delivery. This approach not only addresses the SEO and loading time issues we experience with CSR but also improves SSR by reducing server load, thus enhancing performance.&lt;/p&gt;&lt;p&gt;I’ve refactored the same RSC app I shared earlier so that it uses the Next.js Page router with SSR. The improvements in RSCs are significant:&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg&quot; sizes=&quot;100vw&quot; alt=&quot;Comparing Next.js Page Router and App Router, side-by-side.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Figure 13. (&lt;a href=&quot;https://files.smashing.media/articles/forensics-react-server-components/13-ssr-vs-rscs.jpeg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Looking at these two reports I pulled from Sentry, we can see that streaming allows the page to start loading its resources before the actual request finishes. This significantly improves the Web Vitals metrics, which we see when comparing the two reports.&lt;/p&gt;&lt;p&gt;The conclusion: &lt;strong&gt;Users enjoy faster, more reactive interfaces with an architecture that relies on RSCs.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The RSC architecture introduces two new component types: Server Components and Client Components. This division helps React and the frameworks that rely on it — like Next.js — streamline content delivery while maintaining interactivity.&lt;/p&gt;&lt;p&gt;However, this setup also introduces new challenges in areas like state management, authentication, and component architecture. Exploring those challenges is a great topic for another blog post!&lt;/p&gt;&lt;p&gt;Despite these challenges, the benefits of RSCs present a compelling case for their adoption. We definitely will see guides published on how to address RSC’s challenges as they mature, but, in my opinion, they already look like the future of rendering practices in modern web development.&lt;/p&gt;</description><link>https://www.smashingmagazine.com/2024/05/forensics-react-server-components/</link><guid isPermaLink="false">https://www.smashingmagazine.com/2024/05/forensics-react-server-components/</guid><pubDate>Mon, 20 Jul 2026 16:00:00 GMT</pubDate><author>Lazar Nikolov</author><category>React</category><category>JavaScript</category><category>Coding</category><category>Performance</category></item><item><title>The End Of My Gatsby Journey</title><description>&lt;h1 id=&quot;main-heading&quot; class=&quot;article-header--title&quot;&gt;The End Of My Gatsby Journey&lt;/h1&gt;&lt;span id=&quot;article__start&quot; class=&quot;summary__heading&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/&quot;&gt;“Gatsby headaches”&lt;/a&gt; are over. Juan Diego Rodríguez reflects on his decision to stop using Gatsby as his go-to framework. Through a detailed examination of its strengths and weaknesses, he provides valuable insights and alternative options for developers navigating their tooling choices.&lt;ul class=&quot;meta-box meta-box--article&quot;&gt;&lt;li class=&quot;meta-box--item meta-box--published&quot;&gt;16 min read&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--tags&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/category/javascript&quot;&gt;JavaScript&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/gatsby&quot;&gt;Gatsby&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/frameworks&quot;&gt;Frameworks&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/react&quot;&gt;React&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--share&quot;&gt;Share on &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/intent/tweet?text=The%20End%20Of%20My%20Gatsby%20Journey&amp;amp;url=https%3A%2F%2Fwww.smashingmagazine.com%2f2024%2f03%2fend-of-gatsby-journey%2f&amp;amp;via=smashingmag&quot;&gt;Twitter&lt;/a&gt;,&amp;nbsp;&lt;a rel=&quot;nofollow&quot; href=&quot;https://data.smashing.services/ball?uri=//www.linkedin.com/shareArticle?url=https://www.smashingmagazine.com%2f2024%2f03%2fend-of-gatsby-journey%2f&amp;amp;title=The%20End%20Of%20My%20Gatsby%20Journey&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;A fun fact about me is that my birthday is on Valentine’s Day. This year, I wanted to celebrate by launching a simple website that lets people receive anonymous letters through a personal link. The idea came up to me at the beginning of February, so I wanted to finish the project as soon as possible since time was of the essence.&lt;/p&gt;&lt;p&gt;Having that in mind, I decided not to do SSR/SSG with Gatsby for the project but rather go with a single-page application (SPA) using Vite and React — a rather hard decision considering my extensive experience with Gatsby. Years ago, when I started using React and learning more and more about today’s &lt;a href=&quot;https://www.smashingmagazine.com/2024/02/web-development-getting-too-complex/#comments-web-development-getting-too-complex&quot;&gt;intricate web landscape&lt;/a&gt;, I picked up &lt;a href=&quot;https://gatsbyjs.com/&quot;&gt;Gatsby.js&lt;/a&gt; as my render framework of choice because SSR/SSG was necessary for every website, right?&lt;/p&gt;&lt;p&gt;I used it for &lt;em&gt;everything&lt;/em&gt;, from the most basic website to the most over-engineered project. I absolutely loved it and thought it was the best tool, and I was incredibly confident in my decision since I was getting perfect Lighthouse scores in the process.&lt;/p&gt;&lt;p&gt;The years passed, and I found myself constantly fighting with Gatsby plugins, resorting to &lt;em&gt;hacky&lt;/em&gt; solutions for them and even spending more time waiting for the server to start. It felt like I was fixing more than making. I even &lt;a href=&quot;https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/&quot;&gt;started a series for this magazine all about the “Gatsby headaches” I experienced most&lt;/a&gt; and how to overcome them.&lt;/p&gt;&lt;p&gt;It was like Gatsby got tougher to use with time because of lots of unaddressed issues: outdated dependencies, cold starts, slow builds, and stale plugins, to name a few. Starting a Gatsby project became tedious for me, and perfect Lighthouse scores couldn’t make up for that.&lt;/p&gt;&lt;p&gt;So, I’ve decided to stop using Gatsby as my go-to framework.&lt;/p&gt;&lt;p&gt;To my surprise, the Vite + React combination I mentioned earlier turned out to be a lot more efficient than I expected while maintaining almost the same great performance measures as Gatsby. It’s a hard conclusion to stomach after years of Gatsby’s loyalty.&lt;/p&gt;&lt;p&gt;I mean, I still think Gatsby is extremely useful for plenty of projects, and I plan on talking about those in a bit. But Gatsby has undergone a series of recent unfortunate events after Netlify acquired it, the impacts of which can be seen in &lt;a href=&quot;https://2022.stateofjs.com/en-US/libraries/rendering-frameworks&quot;&gt;down-trending results from the most recent State of JavaScript survey&lt;/a&gt;. The likelihood of a developer picking up Gatsby again after using it for other projects plummeted from 89% to a meager 38% between 2019 and 2022 alone.&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;708&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png&quot; sizes=&quot;100vw&quot; alt=&quot;A ranking of the rendering frameworks retention.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;A ranking of the rendering frameworks retention. (&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Although Gatsby was still the second most-used rendering framework as recently as 2022 — we are still expecting results from the 2023 survey — my prediction is that the decline will continue and dip well below 38%.&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;708&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png&quot; sizes=&quot;100vw&quot; alt=&quot;A ranking of the usage of the rendering framework.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;A ranking of the usage of the rendering framework. (&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/rendering_frameworks_experience_ranking_usage.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Seeing as this is my personal farewell to Gatsby, I wanted to write about where, in my opinion, it went wrong, where it is still useful, and how I am handling my future projects.&lt;/p&gt;&lt;h2 id=&quot;gatsby-a-retrospective&quot;&gt;Gatsby: A Retrospective&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/gatsbyjs/gatsby/releases/tag/0.2.0&quot;&gt;Kyle Mathews started working on what would eventually become Gatsby&lt;/a&gt; in late 2015. Thanks to its unique data layer and SSG approach, it was hyped for success and achieved a &lt;a href=&quot;https://changelog.com/founderstalk/59&quot;&gt;$3.8 million funding seed round in 2018&lt;/a&gt;. Despite initial doubts, Gatsby remained steadfast in its commitment and became a frontrunner in the &lt;a href=&quot;https://css-tricks.com/what-makes-a-site-jamstack/&quot;&gt;Jamstack&lt;/a&gt; community by consistently enhancing its open-source framework and bringing new and better changes with each version.&lt;/p&gt;&lt;p&gt;So… where did it all go wrong?&lt;/p&gt;&lt;p&gt;I’d say it was the introduction of &lt;a href=&quot;https://www.gatsbyjs.com/docs/reference/cloud/what-is-gatsby-cloud/&quot;&gt;Gatsby Cloud&lt;/a&gt; in 2019, as Gatsby aimed at generating continuous revenue and solidifying its business model. Many (myself included) pinpoint Gatsby’s downfall to Gatsby Cloud, as it would end up cutting resources from the main framework and even making it harder to host in other cloud providers.&lt;/p&gt;&lt;p&gt;The core framework had been optimized in a way that using Gatsby and Gatsby Cloud together required no additional hosting configurations, which, as a consequence, made deployments in other platforms much more difficult, both by neglecting to provide documentation for third-party deployments and by releasing exclusive features, like &lt;a href=&quot;https://www.gatsbyjs.com/blog/what-are-incremental-cloud-builds-on-gatsby/#gatsby-skip-here&quot;&gt;incremental builds&lt;/a&gt;, that were only available to Gatsby users who had committed to using Gatsby Cloud. In short, hosting projects on anything but Gatsby Cloud felt like a penalty.&lt;/p&gt;&lt;p&gt;As a framework, Gatsby lost users to Next.js, as shown in both surveys and npm trends, while Gatsby Cloud struggled to compete with the likes of Vercel and Netlify; the former &lt;a href=&quot;https://www.gatsbyjs.com/blog/gatsby-is-joining-netlify/&quot;&gt;acquiring Gatsby in February of 2023&lt;/a&gt;.&lt;/p&gt;&lt;blockquote&gt;“It [was] clear after a while that [Gatsby] weren’t winning the framework battle against Vercel, as a general purpose framework [...] And they were probably a bit boxed in by us in terms of building a cloud platform.”&lt;br&gt;&lt;br&gt;— &lt;a href=&quot;https://thenewstack.io/netlify-acquires-gatsby-its-struggling-jamstack-competitor/&quot;&gt;Matt Biilmann&lt;/a&gt;, Netlify CEO&lt;/blockquote&gt;&lt;p&gt;The Netlify acquisition was the last straw in an already tumbling framework haystack. The migration from Gatsby Cloud to Netlify wasn’t pretty for customers either; some teams were charged 120% more — or had &lt;a href=&quot;https://www.reddit.com/r/webdev/comments/1b14bty/netlify_just_sent_me_a_104k_bill_for_a_simple/&quot;&gt;incurred extraneous fees&lt;/a&gt; — after converting from Gatsby Cloud to Netlify, even with the same Gatsby Cloud plan they had! Many key Gatsby Cloud features, specifically incremental builds that reduced build times of small changes from minutes to seconds, were simply no longer available in Netlify, despite Kyle Mathews &lt;a href=&quot;https://www.gatsbyjs.com/blog/gatsby-is-joining-netlify/&quot;&gt;saying they would be ported over to Netlify&lt;/a&gt;:&lt;/p&gt;&lt;blockquote&gt;“Many performance innovations specifically for large, content-heavy websites, preview, and collaboration workflows, will be incorporated into the Netlify platform and, where relevant, made available across frameworks.”&lt;br&gt;&lt;br&gt;— Kyle Mathews&lt;/blockquote&gt;&lt;p&gt;However, in a Netlify forum thread dated August 2023, a mere six months after the acquisition, a Netlify support engineer contradicted Mathews’s statement, saying &lt;a href=&quot;https://answers.netlify.com/t/how-to-enable-gatsby-incremental-build-2023/99488/4&quot;&gt;there were no plans to add incremental features in Netlify&lt;/a&gt;.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png&quot; sizes=&quot;100vw&quot; alt=&quot;Netlify forum message from a support engineer.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Netlify forum message from a support engineer. (&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-engineer.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;That left no significant reason to remain with Gatsby. And I think &lt;a href=&quot;https://answers.netlify.com/t/how-to-enable-gatsby-incremental-build-2023/99488/4&quot;&gt;this comment on the same thread&lt;/a&gt; perfectly sums up the community’s collective sentiment:&lt;/p&gt;&lt;blockquote&gt;“Yikes. Huge blow to Gatsby Cloud customers. The incremental build speed was exactly why we switched from Netlify to Gatsby Cloud in the first place. It’s really unfortunate to be forced to migrate while simultaneously introducing a huge regression in performance and experience.”&lt;/blockquote&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png&quot; sizes=&quot;100vw&quot; alt=&quot;Netlify forum message from a user&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Netlify forum message from a user. (&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/netlify-forum-message-from-user.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Netlify’s acquisition also brought about a &lt;em&gt;company restructuring&lt;/em&gt; that substantially reduced the headcount of Gatsby’s engineering team, followed by a complete stop in commit activities. A report in an ominous tweet by Astro co-founder Fred Schott further exacerbated concerns about Gatsby’s future.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;269&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png&quot; sizes=&quot;100vw&quot; alt=&quot;Fred Schott’s tweet reading, ‘There have been zero commits to the Gatsby repo in the last 24 days.’&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;(&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/tweet-fredkschott.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Lennart Jörgens, former full-stack developer at Gatsby and Netlify, replied, insinuating there was only one person left after the layoffs:&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;548&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png&quot; sizes=&quot;100vw&quot; alt=&quot;Lennart Jörgens tweet reading, ‘Don’t expect the one person remaining to do all the work.’&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;(&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/tweet-on-one-person-on-the-team.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;You can see all these factors contributing to Gatsby’s usage downfall in the &lt;a href=&quot;https://survey.stackoverflow.co/2023#section-most-popular-technologies-web-frameworks-and-technologies&quot;&gt;2023 Stack Overflow survey&lt;/a&gt;.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png&quot; sizes=&quot;100vw&quot; alt=&quot;Stacks overflow ranking of the rendering framework usage.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Stacks overflow ranking of the rendering framework usage. (&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/stack-overflow-survey-rendering-frameworks.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Biilmann addressed the community’s concerns about Gatsby’s viability in an &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/issues/38696#issuecomment-1817064739&quot;&gt;open issue from the Gatsby repository&lt;/a&gt;:&lt;/p&gt;&lt;blockquote&gt;“While we don’t plan for Gatsby to be where the main innovation in the framework ecosystem takes place, it will be a safe, robust and reliable choice to build production quality websites and e-commerce stores, and will gain new powers by ways of great complementary tools.”&lt;br&gt;&lt;br&gt;— Matt Biilmann&lt;/blockquote&gt;&lt;p&gt;He also shed light on Gatsby’s future focus:&lt;/p&gt;&lt;blockquote&gt;&lt;ul&gt;&lt;li&gt;“First, ensure stability, predictability, and good performance.&lt;/li&gt;&lt;li&gt;Second, give it new powers by strong integration with all new tooling that we add to our Composable Web Platform (for more on what’s all that, you can check out our homepage).&lt;/li&gt;&lt;li&gt;Third, make Gatsby more open by decoupling some parts of it that were closely tied to proprietary cloud infrastructure. The already-released Adapters feature is part of that effort.”&lt;/li&gt;&lt;/ul&gt;&lt;br&gt;— Matt Biilmann&lt;/blockquote&gt;&lt;p&gt;So, Gatsby gave up competing against Next.js on innovation, and instead, it will focus on keeping the existing framework clean and steady in its current state. Frankly, this seems like the most reasonable course of action considering today’s state of affairs.&lt;/p&gt;&lt;h2 id=&quot;why-did-people-stop-using-gatsby&quot;&gt;Why Did People Stop Using Gatsby?&lt;/h2&gt;&lt;p&gt;Yes, Gatsby Cloud ended abruptly, but as a framework independent of its cloud provider, other aspects encouraged developers to look for alternatives to Gatsby.&lt;/p&gt;&lt;p&gt;As far as I am concerned, Gatsby’s developer experience (&lt;abbr&gt;DX&lt;/abbr&gt;) became more of a burden than a help, and there are two main culprits where I lay the blame: &lt;strong&gt;dependency hell&lt;/strong&gt; and &lt;strong&gt;slow bundling times&lt;/strong&gt;.&lt;/p&gt;&lt;h3 id=&quot;dependency-hell&quot;&gt;Dependency Hell&lt;/h3&gt;&lt;p&gt;Go ahead and start a new Gatsby project:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-shellsession&quot;&gt;gatsby new
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After waiting a couple of minutes you will get your brand new Gatsby site. You’d rightly expect to have a clean slate with zero vulnerabilities and outdated dependencies with this out-of-the-box setup, but here’s what you will find in the terminal once you run &lt;code&gt;npm audit&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-shellsession&quot;&gt;18 vulnerabilities (11 moderate, 6 high, 1 critical)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That looks concerning — and it is — not so much from a security perspective but as an indication of decaying DX. As a static site generator (SSG), Gatsby will, unsurprisingly, deliver a static and safe site that (normally) doesn’t have access to a database or server, making it immune to most cyber attacks. Besides, lots of those vulnerabilities are in the developer tools and never reach the end user. Alas, relying on &lt;code&gt;npm audit&lt;/code&gt; to assess your site security is &lt;a href=&quot;https://overreacted.io/npm-audit-broken-by-design/&quot;&gt;a naive choice at best&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;However, those vulnerabilities reveal an underlying issue: the whopping number of dependencies Gatsby uses is 168(!) at the time I’m writing this. For the sake of comparison, Next.js uses 16 dependencies. A lot of Gatsby’s dependencies are outdated, hence the warnings, but trying to update them to their latest versions will likely unleash a dependency hell full of additional npm warnings and errors.&lt;/p&gt;&lt;p&gt;In a &lt;a href=&quot;https://www.reddit.com/r/gatsbyjs/comments/woccnn/is_it_possible_to_have_a_gatsby_project_without/&quot;&gt;related subreddit&lt;/a&gt; from 2022, a user asked, “Is it possible to have a Gatsby site without vulnerabilities?”&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png&quot; sizes=&quot;100vw&quot; alt=&quot;Reddit comment, ‘Is it possible to have a Gatsby site without vulnerabilities?’&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;(&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/is-it-possible-to-have-a-gatsby-site-without-vulnerabilities.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The real answer is disappointing, but as of March 2024, it remains true.&lt;/p&gt;&lt;p&gt;A Gatsby site should work completely fine, even with that many dependencies, and extending your project shouldn’t be a problem, whether through its plugin ecosystem or other packages. However, when trying to upgrade any existing dependency you will find that you can’t! Or at least you can’t do it without introducing breaking changes to one of the 168 dependencies, many of which rely on outdated versions of other libraries that also cannot be updated.&lt;/p&gt;&lt;p&gt;It’s that inception-like roundabout of dependencies that I call &lt;strong&gt;dependency hell&lt;/strong&gt;.&lt;/p&gt;&lt;h3 id=&quot;slow-build-and-development-times&quot;&gt;Slow Build And Development Times&lt;/h3&gt;&lt;p&gt;To me, one of the most important aspects of choosing a development tool is how comfortable it feels to use it and how fast it is to get a project up and running. &lt;a href=&quot;https://www.smashingmagazine.com/2024/02/web-development-getting-too-complex/&quot;&gt;As I’ve said before&lt;/a&gt;, users don’t care or know what a “tech stack” is or what framework is in use; they want a good-looking website that helps them achieve the task they came for. Many developers don’t even question what tech stack is used on each site they visit; at least, I hope not.&lt;/p&gt;&lt;p&gt;With that in mind, choosing a framework boils down to how efficiently you can use it. If your development server constantly experiences cold starts and crashes and is unable to quickly reflect changes, that’s a poor DX and a signal that there may be a better option.&lt;/p&gt;&lt;p&gt;That’s the main reason I won’t automatically reach for Gatsby from here on out. Installation is no longer a trivial task; the dependencies are firing off warnings, and it takes the development server upwards of 30 seconds to boot. I’ve even found that the longer the server runs, the slower it gets; this happens constantly to me, though I admittedly have not heard similar gripes from other developers. Regardless, I get infuriated having to constantly restart my development server every time I make a change to &lt;code&gt;gatsby-config.js&lt;/code&gt;, &lt;code&gt;gatsby-node.js&lt;/code&gt; files, or any other data source.&lt;/p&gt;&lt;p&gt;This new reality is particularly painful, knowing that a Vite.js + React setup can start a server within 500ms &lt;a href=&quot;https://esbuild.github.io/&quot;&gt;thanks to the use of esbuild&lt;/a&gt;.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;181&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png&quot; sizes=&quot;100vw&quot; alt=&quot;Esbuild time to craft a production bundle of 10 copies of the three.js library from scratch using default settings.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Esbuild time to craft a production bundle of 10 copies of the three.js library from scratch using default settings. (Image source: &lt;a href=&quot;https://esbuild.github.io/&quot;&gt;esbuild&lt;/a&gt;) (&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/bundlers-time.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Running &lt;code&gt;gatsby build&lt;/code&gt; gets worse. Build times for larger projects normally take some number of minutes, which is understandable when we consider all of the pages, data sources, and optimizations Gatsby does behind the scenes. However, even a small content edit to a page triggers a full build and deployment process, and the endless waiting is not only exhausting but downright distracting for getting things done. That’s what incremental builds were designed to solve and the reason many people switched from Netlify to Gatsby Cloud when using Gatsby. It’s a shame we no longer have that as an available option.&lt;/p&gt;&lt;p&gt;The moment Gatsby Cloud was discontinued along with incremental builds, the incentives for continuing to use Gatsby became pretty much non-existent. The &lt;strong&gt;slow build times&lt;/strong&gt; are simply too costly to the development workflow.&lt;/p&gt;&lt;h2 id=&quot;what-gatsby-did-awesomely-well&quot;&gt;What Gatsby Did Awesomely Well&lt;/h2&gt;&lt;p&gt;I still believe that Gatsby has awesome things that other rendering frameworks don’t, and that’s why I will keep using it, albeit for specific cases, such as my personal website. It just isn’t my go-to framework for everything, mainly because Gatsby (and the Jamstack) wasn’t meant for every project, even if Gatsby was marketed as a general-purpose framework.&lt;/p&gt;&lt;p&gt;Here’s where I see Gatsby still leading the competition:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;The GraphQL data layer.&lt;/strong&gt;&lt;br&gt;In Gatsby, all the configured data is available in the same place, a &lt;em&gt;data layer&lt;/em&gt; that’s easy to access using GraphQL queries in any part of your project. This is by far the best Gatsby feature, and it trivializes the process of building static pages from data, e.g., a blog from a content management system API or documentation from Markdown files.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Client performance.&lt;/strong&gt;&lt;br&gt;While Gatsby’s developer experience is questionable, I believe it delivers one of the best user experiences for navigating a website. Static pages and assets deliver the fastest possible load times, and using React Router with pre-rendering of proximate links offers one of the smoothest experiences navigating between pages. We also have to note Gatsby’s amazing image API, which optimizes images to all extents.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The plugin ecosystem (kinda).&lt;/strong&gt;&lt;br&gt;There is typically a Gatsby plugin for everything. This is awesome when using a CMS as a data source since you could just install its specific plugin and have all the necessary data in your data layer. However, a lot of plugins went unmaintained and grew outdated, introducing unsolvable dependency issues that come with dependency hell.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I briefly glossed over the good parts of Gatsby in contrast to the bad parts. Does that mean that Gatsby has more bad parts? Absolutely not; you just won’t find the bad parts in any documentation. The bad parts also aren’t deal breakers in isolation, but they snowball into a tedious and lengthy developer experience that pushes away its advocates to other solutions or rendering frameworks.&lt;/p&gt;&lt;h3 id=&quot;do-we-need-ssr-ssg-for-everything&quot;&gt;Do We Need SSR/SSG For Everything?&lt;/h3&gt;&lt;p&gt;I’ll go on record saying that I am not replacing Gatsby with another rendering framework, like Next.js or Remix, but just avoiding them altogether. I’ve found they aren’t actually needed in a lot of cases.&lt;/p&gt;&lt;p&gt;Think, why do we use any type of rendering framework in the first place? I’d say it’s for two main reasons: &lt;strong&gt;crawling bots&lt;/strong&gt; and &lt;strong&gt;initial loading time&lt;/strong&gt;.&lt;/p&gt;&lt;h4 id=&quot;seo-and-crawling-bots&quot;&gt;SEO And Crawling Bots&lt;/h4&gt;&lt;p&gt;Most React apps start with a hollow body, only having an empty &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; alongside &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags. The JavaScript code then runs in the browser, where React creates the Virtual DOM and injects the rendered user interface into the browser.&lt;/p&gt;&lt;p&gt;Over slow networks, users may notice a white screen before the page is actually rendered, which is just mildly annoying at best (but &lt;a href=&quot;https://ericwbailey.design/published/modern-health-frameworks-performance-and-harm/&quot;&gt;devastating at worst&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;However, search engines like Google and Bing deploy bots that only see an empty page and decide not to crawl the content. Or, if you are linking up a post on social media, you may not get OpenGraph benefits like a link preview.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
  &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;

  &amp;lt;script type=&quot;module&quot; src=&quot;/src/main.tsx&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This was the case years ago, making SSR/SSG necessary for getting noticed by Google bots. Nowadays, &lt;a href=&quot;https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics&quot;&gt;Google can run JavaScript&lt;/a&gt; and render the content to crawl your website. While using SSR or SSG does make this process faster, not all bots can run JavaScript. It’s a tradeoff you can make for a lot of projects and one you can minimize on your cloud provider by &lt;a href=&quot;https://docs.netlify.com/site-deploys/post-processing/prerendering/&quot;&gt;pre-rendering&lt;/a&gt; your content.&lt;/p&gt;&lt;h4 id=&quot;initial-loading-time&quot;&gt;Initial Loading Time&lt;/h4&gt;&lt;p&gt;Pre-rendered pages load faster since they deliver static content that relieves the browser from having to run expensive JavaScript.&lt;/p&gt;&lt;p&gt;It’s especially useful when loading pages that are behind authentication; in a client-side rendered (CSR) page, we would need to display a loading state while we check if the user is logged in, while an SSR page can perform the check on the server and send back the correct static content. I have found, however, that this trade-off is an uncompelling argument for using a rendering framework over a CSR React app.&lt;/p&gt;&lt;p&gt;In any case, my SPA built on React + Vite.js gave me a perfect Lighthouse score for the landing page. Pages that fetch data behind authentication resulted in near-perfect Core Web Vitals scores.&lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;356&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png&quot; sizes=&quot;100vw&quot; alt=&quot;Near-perfect Lighthouse scores, 99% for performance, 79% for accessibility, 100% for best practices, and 100% for SEO.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Lighthouse scores for the app landing page. (&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/landing-page-lighthouse-score.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;356&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png&quot; sizes=&quot;100vw&quot; alt=&quot;Perfect Lighthouse scores of 100% for performance, accessibility, best practices, and SEO.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Lighthouse scores for pages guarded by authentication. (&lt;a href=&quot;https://files.smashing.media/articles/end-of-gatsby-journey/page-guarded-by-auth-lighthouse-score.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;h2 id=&quot;what-projects-gatsby-is-still-good-for&quot;&gt;What Projects Gatsby Is Still Good For&lt;/h2&gt;&lt;p&gt;Gatsby and rendering frameworks are excellent for programmatically creating pages from data and, specifically, for blogs, e-commerce, and documentation.&lt;/p&gt;&lt;p&gt;Don’t be disappointed, though, if it isn’t the right tool for &lt;em&gt;every&lt;/em&gt; use case, as that is akin to blaming a screwdriver for not being a good hammer. It still has good uses, though fewer than it could due to all the reasons we discussed before.&lt;/p&gt;&lt;p&gt;But Gatsby is still a useful tool. If you are a Gatsby developer the main reason you’d reach for it is because you &lt;em&gt;know&lt;/em&gt; Gatsby. Not using it might be considered an &lt;strong&gt;opportunity cost&lt;/strong&gt; in economic terms:&lt;/p&gt;&lt;blockquote&gt;“Opportunity cost is the value of the next-best alternative when a decision is made; it’s what is given up.”&lt;/blockquote&gt;&lt;p&gt;Imagine a student who spends an hour and $30 attending a yoga class the evening before a deadline. The opportunity cost encompasses the time that could have been dedicated to completing the project and the $30 that could have been used for future expenses.&lt;/p&gt;&lt;p&gt;As a Gatsby developer, I could start a new project using another rendering framework like Next.js. Even if Next.js has faster server starts, I would need to factor in my learning curve to use it as efficiently as I do Gatsby. That’s why, for my latest project, I decided to avoid rendering frameworks altogether and use Vite.js + React — I wanted to avoid the opportunity cost that comes with spending time learning how to use an “unfamiliar” framework.&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;So, is Gatsby dead? Not at all, or at least I don’t think Netlify will let it go away any time soon. The acquisition and subsequent changes to Gatsby Cloud may have taken a massive toll on the core framework, but Gatsby is very much still breathing, even if the current slow commits pushed to the repo look like it’s barely alive or hibernating.&lt;/p&gt;&lt;p&gt;I will most likely stick to Vite.js + React for my future endeavors and only use rendering frameworks when I actually need them. What are the tradeoffs? Sacrificing negligible page performance in favor of a faster and more pleasant DX that maintains my sanity? I’ll take that deal every day.&lt;/p&gt;&lt;p&gt;And, of course, this is &lt;em&gt;my&lt;/em&gt; experience as a long-time Gatsby loyalist. Your experience is likely to differ, so the mileage of everything I’m saying may vary depending on your background using Gatsby on your own projects.&lt;/p&gt;&lt;p&gt;That’s why I’d love for you to comment below: if you see it differently, please tell me! Is your current experience using Gatsby different, better, or worse than it was a year ago? What’s different to you, if anything? It would be awesome to get other perspectives in here, perhaps from someone who has been involved in maintaining the framework.&lt;/p&gt;&lt;h4 id=&quot;further-reading-on-smashingmag&quot;&gt;Further Reading On SmashingMag&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/&quot;&gt;Gatsby Headaches And How To Cure Them: i18n (Part 1)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-2/&quot;&gt;Gatsby Headaches And How To Cure Them: i18n (Part 2)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/&quot;&gt;Gatsby Headaches: Working With Media (Part 1)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/&quot;&gt;Gatsby Headaches: Working With Media (Part 2)&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</description><link>https://www.smashingmagazine.com/2024/03/end-of-gatsby-journey/</link><guid isPermaLink="false">https://www.smashingmagazine.com/2024/03/end-of-gatsby-journey/</guid><pubDate>Mon, 20 Jul 2026 16:00:00 GMT</pubDate><author>Juan Diego Rodríguez</author><category>JavaScript</category><category>Gatsby</category><category>Frameworks</category><category>React</category></item><item><title>Making Sense Of “Senseless” JavaScript Features</title><description>&lt;h1 id=&quot;main-heading&quot; class=&quot;article-header--title&quot;&gt;Making Sense Of “Senseless” JavaScript Features&lt;/h1&gt;&lt;span id=&quot;article__start&quot; class=&quot;summary__heading&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;JavaScript may be the most popular client-side language in the world, but it’s far from perfect and not without its quirks. Juan Diego Rodriguez examines several “absurd” JavaScript eccentricities and explains how they made it into the language as well as how to avoid them in your own code.&lt;ul class=&quot;meta-box meta-box--article&quot;&gt;&lt;li class=&quot;meta-box--item meta-box--published&quot;&gt;14 min read&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--tags&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/category/javascript&quot;&gt;JavaScript&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/react&quot;&gt;React&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--share&quot;&gt;Share on &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/intent/tweet?text=Making%20Sense%20Of%20%e2%80%9cSenseless%e2%80%9d%20JavaScript%20Features&amp;amp;url=https%3A%2F%2Fwww.smashingmagazine.com%2f2023%2f12%2fmaking-sense-of-senseless-javascript-features%2f&amp;amp;via=smashingmag&quot;&gt;Twitter&lt;/a&gt;,&amp;nbsp;&lt;a rel=&quot;nofollow&quot; href=&quot;https://data.smashing.services/ball?uri=//www.linkedin.com/shareArticle?url=https://www.smashingmagazine.com%2f2023%2f12%2fmaking-sense-of-senseless-javascript-features%2f&amp;amp;title=Making%20Sense%20Of%20%e2%80%9cSenseless%e2%80%9d%20JavaScript%20Features&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Why does JavaScript have so many eccentricities!? Like, why does &lt;code&gt;0.2 + 0.1&lt;/code&gt; equals &lt;code&gt;0.30000000000000004&lt;/code&gt;? Or, why does &lt;code&gt;&quot;&quot; == false&lt;/code&gt; evaluate to &lt;code&gt;true&lt;/code&gt;?&lt;/p&gt;&lt;p&gt;There are a lot of mind-boggling decisions in JavaScript that seem pointless; some are misunderstood, while others are direct missteps in the design. Regardless, it’s worth knowing &lt;em&gt;what&lt;/em&gt; these strange things are and &lt;em&gt;why&lt;/em&gt; they are in the language. I’ll share what I believe are some of the quirkiest things about JavaScript and make sense of them.&lt;/p&gt;&lt;h2 id=&quot;0-1-0-2-and-the-floating-point-format&quot;&gt;&lt;code&gt;0.1 + 0.2&lt;/code&gt; And The Floating Point Format&lt;/h2&gt;&lt;p&gt;Many of us have mocked JavaScript by writing &lt;code&gt;0.1 + 0.2&lt;/code&gt; in the console and watching it resoundingly fail to get &lt;code&gt;0.3&lt;/code&gt;, but rather a funny-looking &lt;code&gt;0.30000000000000004&lt;/code&gt; value.&lt;/p&gt;&lt;p&gt;What many developers might not know is that the weird result is not really JavaScript’s fault! JavaScript is merely adhering to the &lt;a href=&quot;https://ieeexplore.ieee.org/document/8766229&quot;&gt;&lt;strong&gt;IEEE Standard for Floating-Point Arithmetic&lt;/strong&gt;&lt;/a&gt; that nearly every other computer and programming language uses to represent numbers.&lt;/p&gt;&lt;p&gt;But what exactly is the Floating-Point Arithmetic?&lt;/p&gt;&lt;p&gt;Computers have to represent numbers in all sizes, from the distance between planets and even between atoms. On paper, it’s easy to write a massive number or a minuscule quantity without worrying about the size it will take. Computers don’t have that luxury since they have to save all kinds of numbers in binary and a small space in memory.&lt;/p&gt;&lt;p&gt;Take an 8-bit integer, for example. In binary, it can hold integers ranging from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;255&lt;/code&gt;.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg&quot; sizes=&quot;100vw&quot; alt=&quot;8-bit integers showing 0 and 255.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;8-bit integers showing 0 and 255. (&lt;a href=&quot;https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/1-8-bit-integers-showing-0-255.jpg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The keyword here is &lt;em&gt;integers&lt;/em&gt;. It can’t represent any decimals between them. To fix this, we could add an imaginary decimal point somewhere along our 8-bit so the bits before the point are used to represent the integer part and the rest are used for the decimal part. Since the point is always in the same imaginary spot, it’s called a &lt;strong&gt;fixed point decimal&lt;/strong&gt;. But it comes with a great cost since the range is reduced from &lt;strong&gt;&lt;code&gt;0&lt;/code&gt; to &lt;code&gt;255&lt;/code&gt;&lt;/strong&gt; to exactly &lt;strong&gt;&lt;code&gt;0&lt;/code&gt; to &lt;code&gt;15.9375&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg&quot; sizes=&quot;100vw&quot; alt=&quot;Decimals with a fixed point.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Decimals with a fixed point. (&lt;a href=&quot;https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/2-decimals-fixed-point.jpg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Having greater precision means sacrificing range, and vice versa. We also have to take into consideration that computers need to please a large number of users with different requirements. An engineer building a bridge doesn’t worry too much if the measurements are off by just a little, say a hundredth of a centimeter. But, on the other hand, that same hundredth of a centimeter can end up costing much more for someone making a microchip. The precision that’s needed is different, and the consequences of a mistake can vary.&lt;/p&gt;&lt;p&gt;Another consideration is the size where numbers are stored in memory since storing long numbers in something like a megabyte isn’t feasible.&lt;/p&gt;&lt;p&gt;The &lt;em&gt;floating-point&lt;/em&gt; format was born from this need to represent both large and small quantities with precision and efficiency. It does so in three parts:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;A single bit that represents whether or not the number is positive or negative (&lt;code&gt;0&lt;/code&gt; for positive, &lt;code&gt;1&lt;/code&gt; for negative).&lt;/li&gt;&lt;li&gt;A &lt;a href=&quot;https://mathworld.wolfram.com/Significand.html&quot;&gt;significand&lt;/a&gt; or &lt;a href=&quot;https://mathworld.wolfram.com/Mantissa.html&quot;&gt;mantissa&lt;/a&gt; that contains the number’s digits.&lt;/li&gt;&lt;li&gt;An &lt;strong&gt;exponent&lt;/strong&gt; specifies where the decimal (or binary) point is placed relative to the beginning of the mantissa, similar to how scientific notation works. Consequently, the point can move around to any position, hence the &lt;em&gt;floating&lt;/em&gt; point.&lt;/li&gt;&lt;/ol&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg&quot; sizes=&quot;100vw&quot; alt=&quot;Decimals with a floating point.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Decimals with a floating point. (&lt;a href=&quot;https://files.smashing.media/articles/making-sense-of-senseless-javascript-features/3-decimals-floating-point.jpg&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;An 8-bit floating-point format can represent numbers between &lt;code&gt;0.0078&lt;/code&gt; to &lt;code&gt;480&lt;/code&gt; (and its negatives), but notice that the floating-point representation can’t represent all of the numbers in that range. It’s impossible since 8 bits can represent only 256 distinct values. Inevitably, many numbers cannot be accurately represented. There are &lt;em&gt;gaps&lt;/em&gt; along the range. Computers, of course, work with more bits to increase accuracy and range, commonly with 32-bits and 64-bits, but it’s impossible to represent all numbers accurately, a small price to pay if we consider the range we gain and the memory we save.&lt;/p&gt;&lt;p&gt;The exact dynamics are far more complex, but for now, we only have to understand that while this format allows us to express numbers in a large range, it loses precision (the gaps between representable values get bigger) when they become too big. For example, JavaScript numbers are presented in a double-precision floating-point format, i.e., each number is represented in 64 bits in memory, leaving 53 bits to represent the mantissa. That means JavaScript can only safely represent integers between –(2&lt;sup&gt;53&lt;/sup&gt; — 1) and 2&lt;sup&gt;53&lt;/sup&gt; — 1 without losing precision. Beyond that, the arithmetic stops making sense. That’s why we have the &lt;code&gt;Number.MAX_SAFE_INTEGER&lt;/code&gt; static data property to represent the maximum safe integer in JavaScript, which is (2&lt;sup&gt;53&lt;/sup&gt; — 1) or &lt;code&gt;9007199254740991&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;But &lt;code&gt;0.3&lt;/code&gt; is obviously below the &lt;code&gt;MAX_SAFE_INTEGER&lt;/code&gt; threshold, so why can’t we get it when adding &lt;code&gt;0.1&lt;/code&gt; and &lt;code&gt;0.2&lt;/code&gt;? The floating-point format struggles with some fractional numbers. It isn’t a problem with the floating-point format, but it certainly is across any number system.&lt;/p&gt;&lt;p&gt;To see this, let’s represent one-third (&lt;sup&gt;1&lt;/sup&gt;⁄&lt;sub&gt;3&lt;/sub&gt;) in base-10.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0.3
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0.33
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0.3333333 [...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No matter how many digits we try to write, the result will never be &lt;em&gt;exactly&lt;/em&gt; one-third. In the same way, we cannot accurately represent some fractional numbers in base-2 or binary. Take, for example, &lt;code&gt;0.2&lt;/code&gt;. We can write it with no problem in base-10, but if we try to write it in binary we get a recurring &lt;code&gt;1001&lt;/code&gt; at the end that repeats infinitely.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0.001 1001 1001 1001 1001 1001 10 [...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We obviously can’t have an infinitely large number, so at some point, the mantissa has to be truncated, making it impossible not to lose precision in the process. If we try to convert &lt;code&gt;0.2&lt;/code&gt; from double-precision floating-point back to base-10, we will see the actual value saved in memory:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0.200000000000000011102230246251565404236316680908203125
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It isn’t 0.2! We cannot represent an awful lot of fractional values — not only in JavaScript but in almost all computers. So why does running &lt;code&gt;0.2 + 0.2&lt;/code&gt; correctly compute &lt;code&gt;0.4&lt;/code&gt;? In this case, the imprecision is so small that it gets rounded by Javascript (at the 16&lt;sup&gt;th&lt;/sup&gt; decimal), but sometimes the imprecision is enough to escape the rounding mechanism, as is the case with &lt;code&gt;0.2 + 0.1&lt;/code&gt;. We can see what’s happening under the hood if we try to sum the actual values of &lt;code&gt;0.1&lt;/code&gt; and &lt;code&gt;0.2&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;This is the actual value saved when writing &lt;code&gt;0.1&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0.1000000000000000055511151231257827021181583404541015625
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we manually sum up the actual values of &lt;code&gt;0.1&lt;/code&gt; and &lt;code&gt;0.2&lt;/code&gt;, we will see the culprit:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0.3000000000000000444089209850062616169452667236328125
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That value is rounded to &lt;code&gt;0.30000000000000004&lt;/code&gt;. You can check the real values saved at &lt;a href=&quot;https://float.exposed/0x3fb999999999999a&quot;&gt;float.exposed&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Floating-point has its known flaws, but its positives outweigh them, and it’s standard around the world. In that sense, it’s actually a relief when all modern systems will give us the same &lt;code&gt;0.30000000000000004&lt;/code&gt; result across architectures. It might not be the result you expect, but it’s a result you can predict.&lt;/p&gt;&lt;h2 id=&quot;type-coercion&quot;&gt;Type Coercion&lt;/h2&gt;&lt;p&gt;JavaScript is a dynamically typed language, meaning we don’t have to declare a variable’s type, and it can be changed later in the code.&lt;/p&gt;&lt;blockquote class=&quot;pull-quote&quot;&gt;&lt;p&gt;&lt;a class=&quot;pull-quote__link&quot; aria-label=&quot;Share on Twitter&quot; href=&quot;https://twitter.com/share?text=%0aI%20find%20dynamically%20typed%20languages%20liberating%20since%20we%20can%20focus%20more%20on%20the%20substance%20of%20the%20code.%0a&amp;amp;url=https://smashingmagazine.com%2f2023%2f12%2fmaking-sense-of-senseless-javascript-features%2f&quot;&gt;I find dynamically typed languages liberating since we can focus more on the substance of the code.&lt;/a&gt;&lt;/p&gt;&lt;div class=&quot;pull-quote__quotation&quot;&gt;&lt;div class=&quot;pull-quote__bg&quot;&gt;&lt;span class=&quot;pull-quote__symbol&quot;&gt;“&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;The issue comes from being weakly typed since there are many occasions where the language will try to do an implicit conversion between different types, e.g., from strings to numbers or &lt;em&gt;falsy&lt;/em&gt; and &lt;em&gt;truthy&lt;/em&gt; values. This is specifically true when using the equality ( &lt;code&gt;==&lt;/code&gt;) and plus sign (&lt;code&gt;+&lt;/code&gt;) operators. The rules for type coercion are intricate, hard to remember, and even incorrect in certain situations. It’s better to avoid using &lt;code&gt;==&lt;/code&gt; and always prefer the strict equality operator (&lt;code&gt;===&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;For example, JavaScript will coerce a string to a number when compared with another number:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;console.log(&quot;2&quot; == 2); // true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The inverse applies to the plus sign operator (&lt;code&gt;+&lt;/code&gt;). It will try to coerce a number into a string when possible:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;console.log(2 + &quot;2&quot;); // &quot;22&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That’s why we should only use the plus sign operator (&lt;code&gt;+&lt;/code&gt;) if we are sure that the values are numbers. When concatenating strings, it’s better to use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat&quot;&gt;&lt;code&gt;concat()&lt;/code&gt;&lt;/a&gt; method or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals&quot;&gt;template literals&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The reason such coercions are in the language is actually absurd. When JavaScript creator Brendan Eich was asked what &lt;a href=&quot;https://thenewstack.io/brendan-eich-on-creating-javascript-in-10-days-and-what-hed-do-differently-today#:~:text=notorious&quot;&gt;he would have done differently&lt;/a&gt; in JavaScript’s design, his answer was to be more meticulous in the implementations early users of the language wanted:&lt;/p&gt;&lt;blockquote&gt;“I would have avoided some of the compromises that I made when I first got early adopters, and they said, “Can you change this?”&lt;br&gt;&lt;br&gt;— Brendan Eich&lt;/blockquote&gt;&lt;p&gt;The most glaring example is the reason why we have two equality operators, &lt;code&gt;==&lt;/code&gt; and &lt;code&gt;===&lt;/code&gt;. When an early JavaScript user prompted his need to compare a number to a string without having to change his code to make a conversion, Brendan added the loose equality operator to satisfy those needs.&lt;/p&gt;&lt;p&gt;There are a lot of other rules governing the loose equality operator (and other statements checking for a condition) that make JavaScript developers scratch their heads. They are complex, tedious, and senseless, so we should avoid the loose equality operator (&lt;code&gt;==&lt;/code&gt;) at all costs and replace it with its strict homonym (&lt;code&gt;===&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;Why do we have two equality operators in the first place? A lot of factors, but we can point a finger at Guy L. Steele, co-creator of the Scheme programming language. He assured Eich that we could always add another equality operator since there were dialects with five distinct equality operators in the Lisp language! This mentality is dangerous, and nowadays, all features have to be rigorously analyzed because we can always add new features, but once they are in the language, they cannot be removed.&lt;/p&gt;&lt;h2 id=&quot;automatic-semicolon-insertion&quot;&gt;Automatic Semicolon Insertion&lt;/h2&gt;&lt;p&gt;When writing code in JavaScript, a semicolon (&lt;code&gt;;&lt;/code&gt;) is required at the end of some statements, including:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;var&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt;;&lt;/li&gt;&lt;li&gt;Expression statements;&lt;/li&gt;&lt;li&gt;&lt;code&gt;do...while&lt;/code&gt;;&lt;/li&gt;&lt;li&gt;&lt;code&gt;continue&lt;/code&gt;, &lt;code&gt;break&lt;/code&gt;, &lt;code&gt;return&lt;/code&gt;, &lt;code&gt;throw&lt;/code&gt;;&lt;/li&gt;&lt;li&gt;&lt;code&gt;debugger&lt;/code&gt;;&lt;/li&gt;&lt;li&gt;Class field declarations (public or private);&lt;/li&gt;&lt;li&gt;&lt;code&gt;import&lt;/code&gt;, &lt;code&gt;export&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;That said, we don’t necessarily have to insert a semicolon every time since JavaScript can automatically insert semicolons in a process unsurprisingly known as &lt;strong&gt;Automatic Semicolon Insertion&lt;/strong&gt; (ASI). It was intended to make coding easier for beginners who didn’t know where a semicolon was needed, but it isn’t a reliable feature, and we should stick to explicitly typing where a semicolon goes. Linters and formatters add a semicolon where ASI would, but they aren’t completely reliable either.&lt;/p&gt;&lt;p&gt;ASI can make some code work, but most of the time it doesn’t. Take the following code:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const a = 1
(1).toString()

const b = 1
[1, 2, 3].forEach(console.log)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can probably see where the semicolons go, and if we formatted it correctly, it would end up as:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const a = 1;

(1).toString();

const b = 1;

[(1, 2, 3)].forEach(console.log);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But if we feed the prior code directly to JavaScript, all kinds of exceptions would be thrown since it would be the same as writing this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const a = 1(1).toString();

const b = (1)[(1, 2, 3)].forEach(console.log);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In conclusion, know your semicolons.&lt;/p&gt;&lt;h2 id=&quot;why-so-many-bottom-values&quot;&gt;Why So Many Bottom Values?&lt;/h2&gt;&lt;p&gt;The term “bottom” is often used to represent a value that does not exist or is undefined. But why do we have two kinds of bottom values in JavaScript?&lt;/p&gt;&lt;p&gt;Everything in JavaScript can be considered an object, except the two bottom values &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;undefined&lt;/code&gt; (despite &lt;code&gt;typeof null&lt;/code&gt; returning &lt;code&gt;object&lt;/code&gt;). Attempting to get a property value from them raises an exception.&lt;/p&gt;&lt;p&gt;Note that, strictly speaking, &lt;strong&gt;all primitive values aren’t objects&lt;/strong&gt;. But only &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;undefined&lt;/code&gt; aren’t subjected to &lt;a href=&quot;https://stackoverflow.com/questions/34067261/is-boxing-coercion-in-javascript&quot;&gt;&lt;em&gt;boxing&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;We can even think of &lt;code&gt;NaN&lt;/code&gt; as a third bottom value that represents the absence of a number. The abundance of bottom values should be regarded as a design error. There isn’t a straightforward reason that explains the existence of two bottom values, but we can see a difference in how JavaScript employs them.&lt;/p&gt;&lt;p&gt;&lt;code&gt;undefined&lt;/code&gt; is the bottom value that JavaScript uses by default, so it’s considered good practice to use it exclusively in your code. When we define a variable without an initial value, attempting to retrieve it assigns the &lt;code&gt;undefined&lt;/code&gt; value. The same thing happens when we try to access a non-existing property from an object. To match JavaScript’s behavior as closely as possible, use &lt;code&gt;undefined&lt;/code&gt; to denote an existing property or variable that doesn’t have a value.&lt;/p&gt;&lt;p&gt;On the other hand, &lt;code&gt;null&lt;/code&gt; is used to represent the absence of an object (hence, its &lt;code&gt;typeof&lt;/code&gt; returns an &lt;code&gt;object&lt;/code&gt; even though it isn’t). However, this is considered a design blunder because &lt;code&gt;undefined&lt;/code&gt; could fulfill its purposes as effectively. It’s used by JavaScript to denote the end of a recursive data structure. More specifically, it’s used in the prototype chain to denote its end. Most of the time, you can use &lt;code&gt;undefined&lt;/code&gt; over &lt;code&gt;null&lt;/code&gt;, but there are some occasions where only &lt;code&gt;null&lt;/code&gt; can be used, as is the case with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create&quot;&gt;&lt;code&gt;Object.create&lt;/code&gt;&lt;/a&gt; in which we can only create an object without a prototype passing &lt;code&gt;null&lt;/code&gt;; using &lt;code&gt;undefined&lt;/code&gt; returns a &lt;code&gt;TypeError&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;code&gt;null&lt;/code&gt; and &lt;code&gt;undefined&lt;/code&gt; both suffer from the path problem. When trying to access a property from a bottom value — as if they were objects — exceptions are raised.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let user;

let userName = user.name; // Uncaught TypeError

let userNick = user.name.nick; // Uncaught TypeError
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There is no way around this unless we check for each property value before trying to access the next one, either using the logical AND (&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;) or optional chaining (&lt;code&gt;?&lt;/code&gt;).&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let user;

let userName = user?.name;

let userNick = user &amp;amp;&amp;amp; user.name &amp;amp;&amp;amp; user.name.nick;

console.log(userName); // undefined

console.log(userNick); // undefined
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I said that &lt;code&gt;NaN&lt;/code&gt; can be considered a bottom value, but it has its own confusing place in JavaScript since it represents numbers that aren’t actual numbers, usually due to a failed string-to-number conversion (which is another reason to avoid it). &lt;code&gt;NaN&lt;/code&gt; has its own shenanigans because it isn’t equal to itself! To test if a value is &lt;code&gt;NaN&lt;/code&gt; or not, use &lt;code&gt;Number.isNaN()&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;We can check for all three bottom values with the following test:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function stringifyBottom(bottomValue) {
  if (bottomValue === undefined) {
    return &quot;undefined&quot;;
  }

  if (bottomValue === null) {
    return &quot;null&quot;;
  }

  if (Number.isNaN(bottomValue)) {
    return &quot;NaN&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;increment-and-decrement&quot;&gt;Increment (&lt;code&gt;++&lt;/code&gt;) And Decrement (&lt;code&gt;--&lt;/code&gt;)&lt;/h2&gt;&lt;p&gt;As developers, we tend to spend more time reading code rather than writing it. Whether we are reading documentation, reviewing someone else’s work, or checking our own, &lt;strong&gt;code readability will increase our productivity over brevity&lt;/strong&gt;. In other words, readability saves time in the long run.&lt;/p&gt;&lt;p&gt;That’s why I prefer using &lt;code&gt;+ 1&lt;/code&gt; or &lt;code&gt;- 1&lt;/code&gt; rather than the increment (&lt;code&gt;++&lt;/code&gt;) and decrement (&lt;code&gt;--&lt;/code&gt;) operators.&lt;/p&gt;&lt;p&gt;It’s illogical to have a different syntax exclusively for incrementing a value by one in addition to having a pre-increment form and a post-increment form, depending on where the operator is placed. It is very easy to get them reversed, and that can be difficult to debug. They shouldn’t have a place in your code or even in the language as a whole when we consider where the increment operators come from.&lt;/p&gt;&lt;p&gt;As we saw in a &lt;a href=&quot;https://www.smashingmagazine.com/2023/12/marketing-changed-oop-javascript/&quot;&gt;previous article&lt;/a&gt;, JavaScript syntax is heavily inspired by the C language, which uses pointer variables. Pointer variables were designed to store the memory addresses of other variables, enabling dynamic memory allocation and manipulation. The &lt;code&gt;++&lt;/code&gt; and &lt;code&gt;--&lt;/code&gt; operators were originally crafted for the specific purpose of advancing or stepping back through memory locations.&lt;/p&gt;&lt;p&gt;Nowadays, pointer arithmetic has been proven harmful and can cause accidental access to memory locations beyond the intended boundaries of arrays or buffers, leading to memory errors, a notorious source of bugs and vulnerabilities. Regardless, the syntax made its way to JavaScript and remains there today.&lt;/p&gt;&lt;p&gt;While the use of &lt;code&gt;++&lt;/code&gt; and &lt;code&gt;--&lt;/code&gt; remains a standard among developers, an argument for readability can be made. Opting for &lt;code&gt;+ 1&lt;/code&gt; or &lt;code&gt;- 1&lt;/code&gt; over &lt;code&gt;++&lt;/code&gt; and &lt;code&gt;--&lt;/code&gt; not only aligns with the principles of clarity and explicitness but also avoids having to deal with its pre-increment form and post-increment form.&lt;/p&gt;&lt;p&gt;Overall, it isn’t a life-or-death situation but a nice way to make your code more readable.&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;JavaScript’s seemingly senseless features often arise from historical decisions, compromises, and attempts to cater to all needs. Unfortunately, it’s impossible to make everyone happy, and JavaScript is no exception.&lt;/p&gt;&lt;blockquote class=&quot;pull-quote&quot;&gt;&lt;p&gt;&lt;a class=&quot;pull-quote__link&quot; aria-label=&quot;Share on Twitter&quot; href=&quot;https://twitter.com/share?text=%0aJavaScript%20doesn%e2%80%99t%20have%20the%20responsibility%20to%20accommodate%20all%20developers,%20but%20each%20developer%20has%20the%20responsibility%20to%20understand%20the%20language%20and%20embrace%20its%20strengths%20while%20being%20mindful%20of%20its%20quirks.%0a&amp;amp;url=https://smashingmagazine.com%2f2023%2f12%2fmaking-sense-of-senseless-javascript-features%2f&quot;&gt;JavaScript doesn’t have the responsibility to accommodate all developers, but each developer has the responsibility to understand the language and embrace its strengths while being mindful of its quirks.&lt;/a&gt;&lt;/p&gt;&lt;div class=&quot;pull-quote__quotation&quot;&gt;&lt;div class=&quot;pull-quote__bg&quot;&gt;&lt;span class=&quot;pull-quote__symbol&quot;&gt;“&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;I hope you find it worth your while to keep learning more and more about JavaScript and its history to get a grasp of its misunderstood features and questionable decisions. Take its amazing prototypal nature, for example. It was obscured during development or blunders like the &lt;code&gt;this&lt;/code&gt; keyword and its multipurpose behavior.&lt;/p&gt;&lt;p&gt;Either way, I encourage every developer to research and learn more about the language. And if you’re interested, I go a bit deeper into questionable areas of JavaScript’s design in &lt;a href=&quot;https://www.smashingmagazine.com/2023/12/marketing-changed-oop-javascript/&quot;&gt;another article published here on Smashing Magazine&lt;/a&gt;!&lt;/p&gt;</description><link>https://www.smashingmagazine.com/2023/12/making-sense-of-senseless-javascript-features/</link><guid isPermaLink="false">https://www.smashingmagazine.com/2023/12/making-sense-of-senseless-javascript-features/</guid><pubDate>Mon, 20 Jul 2026 16:00:00 GMT</pubDate><author>Juan Diego Rodríguez</author><category>JavaScript</category><category>React</category></item><item><title>An Introduction To Full Stack Composability</title><description>&lt;h1 id=&quot;main-heading&quot; class=&quot;article-header--title&quot;&gt;An Introduction To Full Stack Composability&lt;/h1&gt;&lt;span id=&quot;article__start&quot; class=&quot;summary__heading&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;A well-designed composable system should not only consider the technical aspects but also take into account the nature of the content it handles. To help us with that, we can use a Headless Content Management system such as &lt;a href=&quot;https://www.storyblok.com/?utm_source=smashing&amp;amp;utm_medium=sponsor&amp;amp;utm_campaign=DGM_DEV_SMA_PLG&amp;amp;utm_content=full-stack-composability-with-storyblok/&quot;&gt;Storyblok&lt;/a&gt;.&lt;ul class=&quot;meta-box meta-box--article&quot;&gt;&lt;li class=&quot;meta-box--item meta-box--published&quot;&gt;9 min read&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--tags&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/category/react&quot;&gt;React&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/javascript&quot;&gt;JavaScript&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/tools&quot;&gt;Tools&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/headless&quot;&gt;Headless&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--share&quot;&gt;Share on &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/intent/tweet?text=An%20Introduction%20To%20Full%20Stack%20Composability&amp;amp;url=https%3A%2F%2Fwww.smashingmagazine.com%2f2023%2f11%2fintroduction-full-stack-composability%2f&amp;amp;via=smashingmag&quot;&gt;Twitter&lt;/a&gt;,&amp;nbsp;&lt;a rel=&quot;nofollow&quot; href=&quot;https://data.smashing.services/ball?uri=//www.linkedin.com/shareArticle?url=https://www.smashingmagazine.com%2f2023%2f11%2fintroduction-full-stack-composability%2f&amp;amp;title=An%20Introduction%20To%20Full%20Stack%20Composability&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Composability is not only about building a design system. We should create and manage reusable components in the frontend and the UX of our website, as well as coordinate with the backend and the content itself.&lt;/p&gt;&lt;p&gt;In this article, we’ll discuss how to go through both the server and the client sides of our projects and how to align with the content that we’ll manage. We’ll also compare how to &lt;strong&gt;implement composable logic into our code&lt;/strong&gt; using different approaches provided by React-based frameworks like Remix and Next.js.&lt;/p&gt;&lt;h2 id=&quot;composable-architecture&quot;&gt;Composable Architecture&lt;/h2&gt;&lt;p&gt;In the dynamic landscape of web development, the concept of composability has emerged as a key player in crafting scalable, maintainable, and efficient systems. It goes beyond merely constructing design systems; it encompasses the creation and management of reusable components across the entire spectrum of web development, from frontend UX to backend coordination and content management.&lt;/p&gt;&lt;p&gt;Composability is the art of &lt;strong&gt;building systems in a modular and flexible way&lt;/strong&gt;. It emphasizes creating components that are not only reusable but can seamlessly fit together, forming a cohesive and adaptable architecture. This approach not only enhances the development process but also promotes consistency and scalability across projects.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;518&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png&quot; sizes=&quot;100vw&quot; alt=&quot;Composable Architecture&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Composable Architecture (&lt;a href=&quot;https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-composable-architecture.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;We define “composable architecture” as the idea of building software systems from small, independent components that you can combine to form a complete system. Think of a system as a set of &lt;a href=&quot;https://www.lego.com/&quot;&gt;LEGO&lt;/a&gt; pieces. Putting them together, you can build cool structures, figures, and other creations. But the cool thing about those blocks is that you can exchange them, &lt;strong&gt;reuse them for other creations&lt;/strong&gt;, and add new pieces to your existing models.&lt;/p&gt;&lt;h2 id=&quot;parts-of-a-composable-architecture&quot;&gt;Parts Of A Composable Architecture&lt;/h2&gt;&lt;p&gt;To manage a composable architecture for our projects, we have to connect two parts:&lt;/p&gt;&lt;h3 id=&quot;modular-components&quot;&gt;Modular Components&lt;/h3&gt;&lt;p&gt;Break down the system into independent, self-contained modules or components. Modular components can be developed, tested, and updated independently, promoting reusability and easier maintenance.&lt;/p&gt;&lt;p&gt;When we talk about modular components, we are referring to units like:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Microservices&lt;/strong&gt;&lt;br&gt;The architectural style for developing software applications as a set of small, independent services that communicate with each other through well-defined APIs. The application is broken down into a collection of loosely coupled and independently deployable services, each responsible for a specific business capability.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Headless Applications&lt;/strong&gt;&lt;br&gt;In a headless architecture, the application’s logic and functionality are decoupled from the presentation layer, allowing it to function independently of a specific user interface.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Packaged Business Capabilities (PBC)&lt;/strong&gt;&lt;br&gt;A set of activities, products, and services bundled together and offered as a complete solution. It is a very common concept in the e-commerce environment.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;apis&quot;&gt;APIs&lt;/h3&gt;&lt;p&gt;As the components of our architecture can manage different types of data, processes, and tasks of different natures, they need a common language to communicate between them. Components should expose consistent and well-documented APIs (Application Programming Interfaces). An API is a set of rules and protocols that allows one software application to interact with another. APIs define the methods and data formats that applications can use to communicate with each other.&lt;/p&gt;&lt;h2 id=&quot;benefits-of-a-composable-architecture&quot;&gt;Benefits Of A Composable Architecture&lt;/h2&gt;&lt;p&gt;When applying a composable approach to the architecture of our projects, we will see some benefits and advantages:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Easy to reuse.&lt;/strong&gt;&lt;br&gt;Components are designed to be modular and independent. This makes it easy to reuse them in different parts of the system or entirely different systems. Reusability can significantly reduce development time and effort, as well as improve consistency across different projects.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Easy to scale.&lt;/strong&gt;&lt;br&gt;When the demand for a particular service or functionality increases, you can scale the system by adding more instances of the relevant components without affecting the entire architecture. This scalability is essential for handling growing workloads and adapting to changing business requirements.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Easy to maintain.&lt;/strong&gt;&lt;br&gt;Each component is self-contained. If there’s a need to update or fix a specific feature, it can be done without affecting the entire system. This modularity makes it easier to identify, isolate, and address issues, reducing the impact of maintenance activities on the overall system.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Independence from vendors.&lt;/strong&gt;&lt;br&gt;This reduces the dependence on specific vendors for components, making it easier to switch or upgrade individual parts without disrupting the entire system.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Faster development and iteration.&lt;/strong&gt;&lt;br&gt;Development teams can work on different components concurrently. This parallel development accelerates the overall development process. Additionally, updates and improvements can be rolled out independently.&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;the-mach-architecture&quot;&gt;The MACH Architecture&lt;/h2&gt;&lt;p&gt;An example of composability is what is called the &lt;a href=&quot;https://machalliance.org/&quot;&gt;MACH architecture&lt;/a&gt;. The MACH acronym breaks down into Microservices, API-first, Cloud-native, and Headless. This approach is focused on applying composability in a way that allows you to mold the entire ecosystem of your projects and organization to make it align with business needs.&lt;/p&gt;&lt;p&gt;One of the main ideas of the MACH approach is to let marketers, designers, and front-end developers do their thing without having to worry about the backend in the process. They can tweak the look and feel on the fly, run tests, and &lt;strong&gt;adapt to what customers want&lt;/strong&gt; without slowing down the whole operation.&lt;/p&gt;&lt;p&gt;With MACH architecture, getting to an MVP (minimum viable product) is like a rocket ride. Developers can whip up quick prototypes, and businesses can test out their big ideas before going all-in.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;518&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png&quot; sizes=&quot;100vw&quot; alt=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;An example of MACH approach: A Headless CMS (&lt;a href=&quot;https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-mach-architecture.png&quot;&gt;Large preview&lt;/a&gt;)” alt=” An example of MACH approach: A Headless CMS&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;MACH also helps to resolve poor web performance, using improved services and tools that apply specifically to each one of the different components of your organization’s ecosystem. For example, in the e-commerce game, time is money — those old-school sites using monolithic platforms lose sales to the speedy and flexible ones built with a composable approach. MACH lets you create a custom IT setup using the hottest tech out there.&lt;/p&gt;&lt;h2 id=&quot;composability-with-react&quot;&gt;Composability With React&lt;/h2&gt;&lt;p&gt;We can dive deeper and transfer the same composability concept to the implementation of the user interface and experience of our application. React encourages developers to break down user interfaces into small, reusable components that can be composed together to &lt;strong&gt;create complex UIs&lt;/strong&gt;. Each React component is designed to encapsulate a specific piece of functionality or user interface element, promoting a modular and composable architecture. This approach facilitates code reuse, as components can be easily shared and integrated into various parts of an application.&lt;/p&gt;&lt;p&gt;React’s component-based architecture simplifies the development process by allowing developers to focus on building small, self-contained units of functionality. These components can then be combined to create more sophisticated features and build new “higher-level” components. The reusability of React components is a key benefit, as it promotes a &lt;strong&gt;more efficient and maintainable codebase&lt;/strong&gt;. With this idea in mind, we can build design systems for our projects, as well as following approaches like the &lt;a href=&quot;https://bradfrost.com/blog/post/atomic-web-design/&quot;&gt;Atomic Design principle&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;going-full-stack&quot;&gt;Going Full Stack&lt;/h2&gt;&lt;p&gt;But we can go even further and apply the composable approach to our backend and server-side logic, too. React-based frameworks, such as Remix and Next.js, provide powerful tools for implementing composability in web applications while getting the best from different rendering approaches, such as &lt;a href=&quot;https://www.smashingmagazine.com/2020/07/differences-static-generated-sites-server-side-rendered-apps/&quot;&gt;server-side rendering and static generation&lt;/a&gt;. Let’s see a couple of concepts applied in both Next.js and Remix that can help us implement server logic while keeping the composability of our code.&lt;/p&gt;&lt;h2 id=&quot;full-stack-composability-with-next-js-react-server-components&quot;&gt;Full Stack Composability with Next.js: React Server Components&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components&quot;&gt;React Server Components&lt;/a&gt; were introduced by the React team to address challenges related to server-rendered content and enhance the composability of React applications. Composability, in the context of React Server Components, refers to the ability to break down a user interface into smaller, reusable units that can be efficiently managed on the server side. React Server Components take the idea of composability a step further by &lt;strong&gt;allowing certain components to be rendered&lt;/strong&gt; and processed on the server rather than the client. This is particularly useful for large-scale applications where rendering everything on the client side might lead to performance bottlenecks.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;, a React-based framework, includes React Server Components as the default approach when creating and managing components on a project, &lt;a href=&quot;https://nextjs.org/blog/next-13#server-components&quot;&gt;since its version 13&lt;/a&gt;. Some of the benefits identified when using &lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/rendering/server-components&quot;&gt;React Server Components&lt;/a&gt; are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Move data fetching operations to the server, closer to the data source. This also reduces the amount of requests the client needs to make while using the power of processing the web servers offer, compared to the processing capacity of the client devices.&lt;/li&gt;&lt;li&gt;Better security when managing and rendering content.&lt;/li&gt;&lt;li&gt;Better caching strategies.&lt;/li&gt;&lt;li&gt;Improved Initial Page Load and First Contentful Paint (FCP).&lt;/li&gt;&lt;li&gt;Reduce bundle sizes.&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;full-stack-composability-with-remix-full-stack-components&quot;&gt;Full Stack Composability With Remix: Full Stack Components&lt;/h2&gt;&lt;p&gt;One of Remix’s key principles is the ability to create composable route components, allowing developers to build complex user interfaces by combining smaller, self-contained pieces.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://twitter.com/kentcdodds&quot;&gt;Kent C. Dodds&lt;/a&gt;, a very valuable member of the dev community, introduced the concept of &lt;a href=&quot;https://www.epicweb.dev/full-stack-components&quot;&gt;Full Stack Components&lt;/a&gt; to describe the way the &lt;a href=&quot;https://remix.run/&quot;&gt;Remix&lt;/a&gt; framework allows the developers to create components, encapsulating the data fetching and processing they require and involve, in a single file or module that manages &lt;strong&gt;both client-side and server-side logic&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;With the help of &lt;a href=&quot;https://remix.run/docs/en/main/route/loader&quot;&gt;Loader&lt;/a&gt; and &lt;a href=&quot;https://remix.run/docs/en/main/route/action&quot;&gt;Action&lt;/a&gt; functions, in addition to the &lt;a href=&quot;https://remix.run/docs/en/1.19.3/guides/resource-routes&quot;&gt;Resource Routes&lt;/a&gt;, Remix allows developers to add server-side logic, like fetching or data mutations, on the same file where we define the visual representation of the component.&lt;/p&gt;&lt;h2 id=&quot;aligning-with-the-content&quot;&gt;Aligning With The Content&lt;/h2&gt;&lt;p&gt;Finally, one of the challenges in web development is aligning the composability of our architecture and presentation layer with the content they manage. A well-designed composable system should not only consider the technical aspects but also take into account the nature of the content it handles. To help us with that, we can use a Headless Content Management system such as Storyblok.&lt;/p&gt;&lt;h2 id=&quot;storyblok-headless-cms-with-a-composable-approach&quot;&gt;Storyblok: Headless CMS With A Composable Approach&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://storyblok.com/?utm_source=smashing&amp;amp;utm_medium=sponsor&amp;amp;utm_campaign=DGM_DEV_SMA_PLG&amp;amp;utm_content=full-stack-composability-with-storyblok/&quot;&gt;Storyblok&lt;/a&gt; is a Headless Content Management System with many features and capabilities that help content editors, marketers, and other types of users to create and manage content efficiently. The content created with Storyblok (or any Headless CMS) &lt;strong&gt;can be presented in any way&lt;/strong&gt;, as these platforms don’t force the developers to use a particular presentation layer logic.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;518&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png&quot; sizes=&quot;100vw&quot; alt=&quot;Storyblok&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;Storyblok allows you to create and reuse content structures. You can even create and nest components without limits, fill them with content, and customize them to your needs. (&lt;a href=&quot;https://files.smashing.media/articles/full-stack-composability-storyblok/storyblok-headless-cms.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The advantage of Storyblok, speaking about composability, is the &lt;a href=&quot;https://www.storyblok.com/docs/guide/essentials/content-structures#component?utm_source=smashing&amp;amp;utm_medium=sponsor&amp;amp;utm_campaign=DGM_DEV_SMA_PLG&amp;amp;utm_content=full-stack-composability-with-storyblok/&quot;&gt;component approach&lt;/a&gt; it uses to manage the content structures. Because of its Headless nature, Storyblok allows you to use the created components (or “blocks”, as they are called on the platform) with any technology or framework. Linking to the previous topic, you can create component structures to manage content in Storyblok while &lt;strong&gt;managing their visual representation&lt;/strong&gt; with React components on the client-side (or server-side) of your application.&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Composability is a powerful paradigm that transforms the way we approach web development. By fostering modularity, reusability, and adaptability, a composable architecture lays the groundwork for building robust and scalable web applications. As we navigate through both server and client sides, aligning with content management, developers can &lt;strong&gt;harness the full potential of composability&lt;/strong&gt; in order to create a cohesive and efficient web development ecosystem.&lt;/p&gt;</description><link>https://www.smashingmagazine.com/2023/11/introduction-full-stack-composability/</link><guid isPermaLink="false">https://www.smashingmagazine.com/2023/11/introduction-full-stack-composability/</guid><pubDate>Mon, 20 Jul 2026 16:00:00 GMT</pubDate><author>Facundo Giuliani</author><category>React</category><category>JavaScript</category><category>Tools</category><category>Headless</category></item><item><title>Gatsby Headaches: Working With Media (Part 2)</title><description>&lt;h1 id=&quot;main-heading&quot; class=&quot;article-header--title&quot;&gt;Gatsby Headaches: Working With Media (Part 2)&lt;/h1&gt;&lt;span id=&quot;article__start&quot; class=&quot;summary__heading&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;In the final part of this two-part series on solving headaches when working with media files in Gatsby projects, Juan Rodriguez demonstrates strategies and techniques for handling various types of documents, including Markdown files, PDFs, and 3D models.&lt;ul class=&quot;meta-box meta-box--article&quot;&gt;&lt;li class=&quot;meta-box--item meta-box--published&quot;&gt;23 min read&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--tags&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/category/react&quot;&gt;React&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/optimization&quot;&gt;Optimization&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/performance&quot;&gt;Performance&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--share&quot;&gt;Share on &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/intent/tweet?text=Gatsby%20Headaches%3a%20Working%20With%20Media%20%28Part%202%29&amp;amp;url=https%3A%2F%2Fwww.smashingmagazine.com%2f2023%2f10%2fgatsby-headaches-working-media-part2%2f&amp;amp;via=smashingmag&quot;&gt;Twitter&lt;/a&gt;,&amp;nbsp;&lt;a rel=&quot;nofollow&quot; href=&quot;https://data.smashing.services/ball?uri=//www.linkedin.com/shareArticle?url=https://www.smashingmagazine.com%2f2023%2f10%2fgatsby-headaches-working-media-part2%2f&amp;amp;title=Gatsby%20Headaches%3a%20Working%20With%20Media%20%28Part%202%29&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Gatsby is a true Jamstack framework. It works with React-powered components that consume APIs before optimizing and bundling everything to serve as static files with bits of reactivity. That includes media files, like images, video, and audio.&lt;/p&gt;&lt;p&gt;The problem is that there’s no “one” way to handle media in a Gatsby project. We have plugins for everything, from making queries off your local filesystem and compressing files to inlining SVGs and serving images in the responsive image format.&lt;/p&gt;&lt;p&gt;Which plugins should be used for certain types of media? How about certain use cases for certain types of media? That’s where you might encounter headaches because there are many plugins — some official and some not — that are capable of handling one or more use cases — some outdated and some not.&lt;/p&gt;&lt;p&gt;That is what this brief two-part series is about. In &lt;a href=&quot;https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/&quot;&gt;Part 1&lt;/a&gt;, we discussed various strategies and techniques for handling images, video, and audio in a Gatsby project.&lt;/p&gt;&lt;p&gt;This time, in Part 2, we are covering a different type of media we commonly encounter: &lt;strong&gt;documents&lt;/strong&gt;. Specifically, we will tackle considerations for Gatsby projects that make use of Markdown and PDF files. And before wrapping up, we will also demonstrate an approach for using 3D models.&lt;/p&gt;&lt;h2 id=&quot;solving-markdown-headaches-in-gatsby&quot;&gt;Solving Markdown Headaches In Gatsby&lt;/h2&gt;&lt;p&gt;In Gatsby, Markdown files are commonly used to programmatically create pages, such as blog posts. You can write content in Markdown, parse it into your GraphQL data layer, source it into your components, and then bundle it as HTML static files during the build process.&lt;/p&gt;&lt;p&gt;Let’s learn how to load, query, and handle the Markdown for an existing page in Gatsby.&lt;/p&gt;&lt;h3 id=&quot;loading-and-querying-markdown-from-graphql&quot;&gt;Loading And Querying Markdown From GraphQL&lt;/h3&gt;&lt;p&gt;The first step on your Gatsby project is to load the project’s Markdown files to the GraphQL data layer. We can do this using the &lt;code&gt;gatsby-source-filesystem&lt;/code&gt; plugin we used to query the local filesystem for image files in &lt;a href=&quot;https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/&quot;&gt;Part 1&lt;/a&gt; of this series.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i gatsby-source-filesystem
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In &lt;code&gt;gatsby-config.js&lt;/code&gt;, we declare the folder where Markdown files will be saved in the project:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `assets`,
        path: `${ __dirname }/src/assets`,
      },
    },
  ],
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let’s say that we have the following Markdown file located in the project’s &lt;code&gt;./src/assets&lt;/code&gt; directory:&lt;/p&gt;&lt;p&gt;This example consists of two main sections: the &lt;strong&gt;frontmatter&lt;/strong&gt; and &lt;strong&gt;body&lt;/strong&gt;. It is a common structure for Markdown files.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Frontmatter&lt;/strong&gt;&lt;br&gt;Enclosed in triple dashes (&lt;code&gt;---&lt;/code&gt;), this is an optional section at the beginning of a Markdown file that contains metadata and configuration settings for the document. In our example, the frontmatter contains information about the page’s &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;date&lt;/code&gt;, which Gatsby can use as GraphQL arguments.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Body&lt;/strong&gt;&lt;br&gt;This is the content that makes up the page’s main body content.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We can use the &lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-transformer-remark/&quot;&gt;&lt;code&gt;gatsby-transformer-remark&lt;/code&gt;&lt;/a&gt; plugin to parse Markdown files to a GraphQL data layer. Once it is installed, we will need to register it in the project’s &lt;code&gt;gatsby-config.js&lt;/code&gt; file:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;module.exports = {
  plugins: [
    {
      resolve: `gatsby-transformer-remark`,
      options: { },
    },
  ],
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Restart the development server and navigate to &lt;code&gt;http://localhost:8000/___graphql&lt;/code&gt; in the browser. Here, we can play around with Gatsby’s data layer and check our Markdown file above by making a query using the &lt;code&gt;title&lt;/code&gt; property (&lt;code&gt;sample-markdown-file&lt;/code&gt;) in the frontmatter:&lt;/p&gt;&lt;p&gt;This should return the following result:&lt;/p&gt;&lt;p&gt;Notice that the &lt;strong&gt;content in the response is formatted in HTML&lt;/strong&gt;. We can also query the original body as &lt;code&gt;rawMarkdownBody&lt;/code&gt; or any of the frontmatter attributes.&lt;/p&gt;&lt;p&gt;Next, let’s turn our attention to approaches for handling Markdown content once it has been queried.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 id=&quot;using-dangerouslysetinnerhtml&quot;&gt;Using &lt;code&gt;DangerouslySetInnerHTML&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; is a React feature that injects raw HTML content into a component’s rendered output by overriding the &lt;code&gt;innerHTML&lt;/code&gt; property of the DOM node. It’s considered &lt;em&gt;dangerous&lt;/em&gt; since it essentially bypasses React’s built-in mechanisms for rendering and sanitizing content, opening up the possibility of &lt;strong&gt;cross-site scripting (XSS)&lt;/strong&gt; attacks without paying special attention.&lt;/p&gt;&lt;p&gt;That said, if you need to render HTML content dynamically but want to avoid the risks associated with &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;, consider using libraries that sanitize HTML input &lt;em&gt;before&lt;/em&gt; rendering it, such as &lt;a href=&quot;https://github.com/cure53/DOMPurify&quot;&gt;&lt;code&gt;dompurify&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; prop takes an &lt;code&gt;__html&lt;/code&gt; object with a single key that should contain the raw HTML content. Here’s an example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const DangerousComponent = () =&amp;gt; {
  const rawHTML = &quot;&amp;lt;p&amp;gt;This is &amp;lt;em&amp;gt;dangerous&amp;lt;/em&amp;gt; content!&amp;lt;/p&amp;gt;&quot;;

  return &amp;lt;div dangerouslySetInnerHTML={ { __html: rawHTML } } /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To display Markdown using &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; in a Gatsby project, we need first to query the HTML string using Gatsby’s &lt;code&gt;useStaticQuery&lt;/code&gt; hook:&lt;/p&gt;&lt;p&gt;Now, the &lt;code&gt;html&lt;/code&gt; property can be injected into the &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; prop.&lt;/p&gt;&lt;p&gt;This might look OK at first, but if we were to open the browser to view the content, we would notice that the image declared in the Markdown file is missing from the output. We never told Gatsby to parse it. We do have two options to include it in the query, each with pros and cons:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Use a plugin to parse Markdown images.&lt;/strong&gt;&lt;br&gt;The &lt;code&gt;gatsby-remark-images&lt;/code&gt; plugin is capable of processing Markdown images, making them available when querying the Markdown from the data layer. The main downside is the extra configuration it requires to set and render the files. Besides, Markdown images parsed with this plugin only will be available as HTML, so we would need to select a package that can render HTML content into React components, such as &lt;a href=&quot;https://www.npmjs.com/package/rehype-react&quot;&gt;&lt;code&gt;rehype-react&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Save images in the &lt;code&gt;static&lt;/code&gt; folder.&lt;/strong&gt;&lt;br&gt;The &lt;code&gt;/static&lt;/code&gt; folder at the root of a Gatsby project can store assets that won’t be parsed by webpack but will be available in the &lt;code&gt;public&lt;/code&gt; directory. Knowing this, we can point Markdown images to the &lt;code&gt;/static&lt;/code&gt; directory, and they will be available anywhere in the client. The disadvantage? We are unable to leverage Gatsby’s image optimization features to minimize the overall size of the bundled package in the build process.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;The &lt;code&gt;gatsby-remark-images&lt;/code&gt; approach is probably most suited for larger projects since it is more manageable&lt;/strong&gt; than saving all Markdown images in the &lt;code&gt;/static&lt;/code&gt; folder.&lt;/p&gt;&lt;p&gt;Let’s assume that we have decided to go with the second approach of saving images to the &lt;code&gt;/static&lt;/code&gt; folder. To reference an image in the &lt;code&gt;/static&lt;/code&gt; directory, we just point to the filename without any special argument on the path.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const StaticImage = () =&amp;gt; {
  return &amp;lt;img src={ &quot;/desert.png&quot; } alt=&quot;Desert&quot; /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;react-markdown&quot;&gt;&lt;code&gt;react-markdown&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;react-markdown&lt;/code&gt; package provides a component that renders markdown into React components, avoiding the risks of using &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;. The component uses a syntax tree to build the virtual DOM, which allows for updating only the changing DOM instead of completely overwriting it. And since it uses &lt;a href=&quot;https://github.com/remarkjs/remark&quot;&gt;&lt;code&gt;remark&lt;/code&gt;&lt;/a&gt;, we can combine &lt;code&gt;react-markdown&lt;/code&gt; with &lt;code&gt;remark&lt;/code&gt;’s vast &lt;a href=&quot;https://github.com/remarkjs/remark/blob/main/doc/plugins.md&quot;&gt;plugin ecosystem&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Let’s install the package:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i react-markdown
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we replace our prior example with the &lt;code&gt;ReactMarkdown&lt;/code&gt; component. However, instead of querying for the &lt;code&gt;html&lt;/code&gt; property this time, we will query for &lt;code&gt;rawMarkdownBody&lt;/code&gt; and then pass the result to &lt;code&gt;ReactMarkdown&lt;/code&gt; to render it in the DOM.&lt;/p&gt;&lt;h3 id=&quot;markdown-to-jsx&quot;&gt;&lt;code&gt;markdown-to-jsx&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;markdown-to-jsx&lt;/code&gt; is the most popular Markdown component — and the lightest since it comes without any dependencies. It’s an excellent tool to consider when aiming for performance, and it does not require &lt;code&gt;remark&lt;/code&gt;’s plugin ecosystem. The plugin works much the same as the &lt;code&gt;react-markdown&lt;/code&gt; package, only this time, we import a &lt;code&gt;Markdown&lt;/code&gt; component instead of &lt;code&gt;ReactMarkdown&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i markdown-to-jsx
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have taken raw Markdown and parsed it as JSX. But what if we don’t necessarily want to parse it at all? We will look at that use case next.&lt;/p&gt;&lt;h3 id=&quot;react-md-editor&quot;&gt;&lt;code&gt;react-md-editor&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Let’s assume for a moment that we are creating a lightweight CMS and want to give users the option to write posts in Markdown. In this case, instead of parsing the Markdown to HTML, we need to query it as-is.&lt;/p&gt;&lt;p&gt;Rather than creating a Markdown editor from scratch to solve this, several packages are capable of handling the raw Markdown for us. My personal favorite is
&lt;a href=&quot;https://github.com/uiwjs/react-md-editor&quot;&gt;&lt;code&gt;react-md-editor&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Let’s install the package:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i @uiw/react-md-editor
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;MDEditor&lt;/code&gt; component can be imported and set up as a &lt;a href=&quot;https://maxschmitt.me/posts/react-components-controlled-uncontrolled&quot;&gt;controlled component&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;
import { useState } from &quot;react&quot;;
import MDEditor from &quot;@uiw/react-md-editor&quot;;

const ReactMDEditor = () =&amp;gt; {
  const [value, setValue] = useState(&quot;**Hello world!!!**&quot;);

  return &amp;lt;MDEditor value={ value } onChange={ setValue } /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The plugin also comes with a built-in &lt;code&gt;MDEditor.Markdown&lt;/code&gt; component used to preview the rendered content:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;
import { useState } from &quot;react&quot;;
import MDEditor from &quot;@uiw/react-md-editor&quot;;

const ReactMDEditor = () =&amp;gt; {
  const [value, setValue] = useState(&quot;**Hello world!**&quot;);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;MDEditor value={value} onChange={ setValue } /&amp;gt;
      &amp;lt;MDEditor.Markdown source={ value } /&amp;gt;
    &amp;lt;/&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;432&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png&quot; sizes=&quot;100vw&quot; alt=&quot;Markdown previewer&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;The plugin includes a feature that shows a preview of the rendered Markdown. (&lt;a href=&quot;https://files.smashing.media/articles/gatsby-headaches-working-media-part2/react-md-editor-result.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;That was a look at various headaches you might encounter when working with Markdown files in Gatsby. Next, we are turning our attention to another type of file, PDF.&lt;/p&gt;&lt;h2 id=&quot;solving-pdf-headaches-in-gatsby&quot;&gt;Solving PDF Headaches In Gatsby&lt;/h2&gt;&lt;p&gt;PDF files handle content with a completely different approach to Markdown files. With Markdown, we simplify the content to its most raw form so it can be easily handled across different front ends. PDFs, however, are the content presented to users on the front end. Rather than extracting the raw content from the file, we want the user to see it as it is, often by making it available for download or embedding it in a way that the user views the contents directly on the page, sort of like a video.&lt;/p&gt;&lt;p&gt;I want to show you four approaches to consider when embedding a PDF file on a page in a Gatsby project.&lt;/p&gt;&lt;h3 id=&quot;using-the-iframe-element&quot;&gt;Using The &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; Element&lt;/h3&gt;&lt;p&gt;The easiest way to embed a PDF into your Gatsby project is perhaps through an &lt;code&gt;iframe&lt;/code&gt; element:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;
import samplePDF from &quot;./assets/lorem-ipsum.pdf&quot;;

const IframePDF = () =&amp;gt; {
  return &amp;lt;iframe src={ samplePDF }&amp;gt;&amp;lt;/iframe&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It’s worth calling out here that the &lt;code&gt;iframe&lt;/code&gt; element supports lazy loading (&lt;code&gt;loading=&quot;lazy&quot;&lt;/code&gt;) to boost performance in instances where it doesn’t need to load right away.&lt;/p&gt;&lt;h3 id=&quot;embedding-a-third-party-viewer&quot;&gt;Embedding A Third-Party Viewer&lt;/h3&gt;&lt;p&gt;There are situations where PDFs are more manageable when stored in a third-party service, such as Drive, which includes a PDF viewer that can embedded directly on the page. In these cases, we can use the same &lt;code&gt;iframe&lt;/code&gt; we used above, but with the source pointed at the service.&lt;/p&gt;&lt;p&gt;It’s a good reminder that you want to trust the third-party content that’s served in an &lt;code&gt;iframe&lt;/code&gt;. If we’re effectively loading a document from someone else’s source that we do not control, your site could become prone to security vulnerabilities should that source become compromised.&lt;/p&gt;&lt;h3 id=&quot;using-react-pdf&quot;&gt;Using &lt;code&gt;react-pdf&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;react-pdf&lt;/code&gt; package provides an interface to render PDFs as React components. It is based on &lt;a href=&quot;https://github.com/mozilla/pdf.js/&quot;&gt;&lt;code&gt;pdf.js&lt;/code&gt;&lt;/a&gt;, a JavaScript library that renders PDFs using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas&quot;&gt;HTML Canvas&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;To display a PDF file on a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;, the &lt;code&gt;react-pdf&lt;/code&gt; library exposes the &lt;code&gt;Document&lt;/code&gt; and &lt;code&gt;Page&lt;/code&gt; components:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;Document&lt;/code&gt;&lt;/strong&gt;: Loads the PDF passed in its &lt;code&gt;file&lt;/code&gt; prop.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;Page&lt;/code&gt;&lt;/strong&gt;: Displays the page passed in its &lt;code&gt;pageNumber&lt;/code&gt; prop. It should be placed inside &lt;code&gt;Document&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We can install to our project:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i react-pdf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Before we put &lt;code&gt;react-pdf&lt;/code&gt; to use, we will need to set up a service worker for &lt;code&gt;pdf.js&lt;/code&gt; to process time-consuming tasks such as parsing and rendering a PDF document.&lt;/p&gt;&lt;p&gt;Now, we can import the &lt;code&gt;Document&lt;/code&gt; and &lt;code&gt;Page&lt;/code&gt; components, passing the PDF file to their props. We can also import the component’s necessary styles while we are at it.&lt;/p&gt;&lt;p&gt;Since accessing the PDF will change the current page, we can add state management by passing the current &lt;code&gt;pageNumber&lt;/code&gt; to the &lt;code&gt;Page&lt;/code&gt; component:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { useState } from &quot;react&quot;;

// ...

const ReactPDF = () =&amp;gt; {
  const [currentPage, setCurrentPage] = useState(1);

  return (
    &amp;lt;Document file={ samplePDF }&amp;gt;
      &amp;lt;Page pageNumber={ currentPage } /&amp;gt;
    &amp;lt;/Document&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One issue is that we have pagination but don’t have a way to navigate between pages. We can change that by adding controls. First, we will need to know the number of pages in the document, which is accessed on the &lt;code&gt;Document&lt;/code&gt; component’s &lt;code&gt;onLoadSuccess&lt;/code&gt; event:&lt;/p&gt;&lt;p&gt;Next, we display the current page number and add “Next” and “Previous” buttons with their respective handlers to change the current page:&lt;/p&gt;&lt;p&gt;This provides us with everything we need to embed a PDF file on a page via the HTML &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element using &lt;code&gt;react-pdf&lt;/code&gt; and &lt;code&gt;pdf.js&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;There is another similar package capable of embedding a PDF file in a viewer, complete with pagination controls. We’ll look at that next.&lt;/p&gt;&lt;h3 id=&quot;using-react-pdf-viewer&quot;&gt;Using &lt;code&gt;react-pdf-viewer&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Unlike &lt;code&gt;react-pdf&lt;/code&gt;, the &lt;code&gt;react-pdf-viewer&lt;/code&gt; package provides built-in customizable controls right out of the box, which makes embedding a multi-page PDF file a lot easier than having to import them separately.&lt;/p&gt;&lt;p&gt;Let’s install it:&lt;/p&gt;&lt;p&gt;Since &lt;code&gt;react-pdf-viewer&lt;/code&gt; also relies on &lt;code&gt;pdf.js&lt;/code&gt;, we will need to create a service worker as we did with &lt;code&gt;react-pdf&lt;/code&gt;, but only if we are not using both packages at the same time. This time, we are using a &lt;code&gt;Worker&lt;/code&gt; component with a &lt;code&gt;workerUrl&lt;/code&gt; prop directed at the worker’s package.&lt;/p&gt;&lt;p&gt;Note that a &lt;strong&gt;worker like this ought to be set just once at the layout level&lt;/strong&gt;. This is especially true if you intend to use the PDF viewer across different pages.&lt;/p&gt;&lt;p&gt;Next, we import the &lt;code&gt;Viewer&lt;/code&gt; component with its styles and point it at the PDF through its &lt;code&gt;fileUrl&lt;/code&gt; prop.&lt;/p&gt;&lt;p&gt;Once again, we need to add controls. We can do that by importing the &lt;code&gt;defaultLayoutPlugin&lt;/code&gt; (including its corresponding styles), making an instance of it, and passing it in the &lt;code&gt;Viewer&lt;/code&gt; component’s &lt;code&gt;plugins&lt;/code&gt; prop.&lt;/p&gt;&lt;p&gt;Again, &lt;code&gt;react-pdf-viewer&lt;/code&gt; is an alternative to &lt;code&gt;react-pdf&lt;/code&gt; that can be a little easier to implement if you don’t need full control over your PDF files, just the embedded viewer.&lt;/p&gt;&lt;p&gt;There is one more plugin that provides an embedded viewer for PDF files. We will look at it, but only briefly, because I personally do not recommend using it in favor of the other approaches we’ve covered.&lt;/p&gt;&lt;h3 id=&quot;why-you-shouldn-t-use-react-file-viewer&quot;&gt;Why You Shouldn’t Use &lt;code&gt;react-file-viewer&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;The last plugin we will check out is &lt;code&gt;react-file-viewer&lt;/code&gt;, a package that offers an embedded viewer with a simple interface but with the capacity to handle a variety of media in addition to PDF files, including images, videos, PDFs, documents, and spreadsheets.&lt;/p&gt;&lt;p&gt;While &lt;code&gt;react-file-viewer&lt;/code&gt; will get the job done, &lt;strong&gt;it is extremely outdated&lt;/strong&gt; and could easily create more headaches than it solves with compatibility issues. I suggest avoiding it in favor of either an &lt;code&gt;iframe&lt;/code&gt;, &lt;code&gt;react-pdf&lt;/code&gt;, or &lt;code&gt;react-pdf-viewer&lt;/code&gt;.&lt;/p&gt;&lt;h2 id=&quot;solving-3d-model-headaches-in-gatsby&quot;&gt;Solving 3D Model Headaches In Gatsby&lt;/h2&gt;&lt;p&gt;I want to cap this brief two-part series with one more media type that might cause headaches in a Gatsby project: &lt;strong&gt;3D models&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;A 3D model file is a digital representation of a three-dimensional object that stores information about the object’s geometry, texture, shading, and other properties of the object. On the web, 3D model files are used to enhance user experiences by bringing interactive and immersive content to websites. You are most likely to encounter them in product visualizations, architectural walkthroughs, or educational simulations.&lt;/p&gt;&lt;p&gt;There is a multitude of 3D model formats, including glTF OBJ, FBX, STL, and so on. We will use glTF models for a demonstration of a headache-free 3D model implementation in Gatsby.&lt;/p&gt;&lt;p&gt;The &lt;a href=&quot;https://github.com/KhronosGroup/glTF&quot;&gt;&lt;strong&gt;GL Transmission Format&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;(glTF)&lt;/strong&gt; was designed specifically for the web and real-time applications, making it ideal for our example. Using glTF files does require a specific webpack loader, so for simplicity’s sake, we will save the glTF model in the &lt;code&gt;/static&lt;/code&gt; folder at the root of our project as we look at two approaches to create the 3D visual with Three.js:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Using a vanilla implementation of Three.js,&lt;/li&gt;&lt;li&gt;Using a package that integrates Three.js as a React component.&lt;/li&gt;&lt;/ol&gt;&lt;h3 id=&quot;using-three-js&quot;&gt;Using Three.js&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://threejs.org/&quot;&gt;Three.js&lt;/a&gt; creates and loads interactive 3D graphics directly on the web with the help of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API&quot;&gt;WebGL&lt;/a&gt;, a JavaScript API for rendering 3D graphics in real-time inside HTML &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; elements.&lt;/p&gt;&lt;p&gt;Three.js is not integrated with React or Gatsby out of the box, so we must modify our code to support it. A Three.js tutorial is out of scope for what we are discussing in this article, although excellent learning resources are available in the &lt;a href=&quot;https://threejs.org/docs/&quot;&gt;Three.js documentation&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;We start by installing the &lt;code&gt;three&lt;/code&gt; library to the Gatsby project:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i three
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we write a function to load the glTF model for Three.js to reference it. This means we need to import a &lt;code&gt;GLTFLoader&lt;/code&gt; add-on to instantiate a new &lt;code&gt;loader&lt;/code&gt; object.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;
import * as THREE from &quot;three&quot;;

import { GLTFLoader } from &quot;three/addons/loaders/GLTFLoader.js&quot;;

const loadModel = async (scene) =&amp;gt; {
  const loader = new GLTFLoader();
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We use the &lt;code&gt;scene&lt;/code&gt; object as a parameter in the &lt;code&gt;loadModel&lt;/code&gt; function so we can attach our 3D model once loaded to the scene.&lt;/p&gt;&lt;p&gt;From here, we use &lt;code&gt;loader.load()&lt;/code&gt; which takes four arguments:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The glTF file location,&lt;/li&gt;&lt;li&gt;A callback when the resource is loaded,&lt;/li&gt;&lt;li&gt;A callback while loading is in progress,&lt;/li&gt;&lt;li&gt;A callback for handling errors.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let’s create a component to host the scene and load the 3D model. We need to know the element’s client &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt;, which we can get using React’s &lt;code&gt;useRef&lt;/code&gt; hook to access the element’s DOM properties.&lt;/p&gt;&lt;p&gt;Since we are using the element’s &lt;code&gt;clientWidth&lt;/code&gt; and &lt;code&gt;clientHeight&lt;/code&gt; properties, we need to create the scene on the client side inside React’s &lt;code&gt;useEffect&lt;/code&gt; hook where we configure the Three.js &lt;code&gt;scene&lt;/code&gt; with its necessary complements, e.g., a camera, the WebGL renderer, and lights.&lt;/p&gt;&lt;p&gt;Now we can invoke the &lt;code&gt;loadModel&lt;/code&gt; function, passing the scene to it as the only argument:&lt;/p&gt;&lt;p&gt;The last part of this vanilla Three.js implementation is to add &lt;code&gt;OrbitControls&lt;/code&gt; that allow users to navigate the model. That might look something like this:&lt;/p&gt;&lt;p&gt;That is a straight Three.js implementation in a Gatsby project. Next is another approach using a library.&lt;/p&gt;&lt;h3 id=&quot;using-react-three-fiber&quot;&gt;Using React Three Fiber&lt;/h3&gt;&lt;p&gt;&lt;code&gt;react-three-fiber&lt;/code&gt; is a library that integrates the Three.js with React. One of its advantages over the vanilla Three.js approach is its ability to manage and update 3D scenes, making it easier to compose scenes without manually handling intricate aspects of Three.js.&lt;/p&gt;&lt;p&gt;We begin by installing the library to the Gatsby project:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i react-three-fiber @react-three/drei
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice that the installation command includes the &lt;code&gt;@react-three/drei&lt;/code&gt; package, which we will use to add controls to the 3D viewer.&lt;/p&gt;&lt;p&gt;I personally love &lt;code&gt;react-three-fiber&lt;/code&gt; for being tremendously self-explanatory. For example, I had a relatively easy time migrating the extensive chunk of code from the vanilla approach to this much cleaner code:&lt;/p&gt;&lt;p&gt;Thanks to &lt;code&gt;react-three-fiber&lt;/code&gt;, we get the same result as a vanilla Three.js implementation but with fewer steps, more efficient code, and a slew of abstractions for managing and updating Three.js scenes.&lt;/p&gt;&lt;h2 id=&quot;two-final-tips&quot;&gt;Two Final Tips&lt;/h2&gt;&lt;p&gt;The last thing I want to leave you with is two final considerations to take into account when working with media files in a Gatsby project.&lt;/p&gt;&lt;h3 id=&quot;bundling-assets-via-webpack-and-the-static-folder&quot;&gt;Bundling Assets Via Webpack And The &lt;code&gt;/static&lt;/code&gt; Folder&lt;/h3&gt;&lt;p&gt;Importing an asset as a module so it can be bundled by webpack is a common strategy to add post-processing and minification, as well as hashing paths on the client. But there are two additional use cases where you might want to avoid it altogether and use the &lt;code&gt;static&lt;/code&gt; folder in a Gatsby project:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Referencing a library outside the bundled code to prevent webpack compatibility issues or a lack of specific loaders.&lt;/li&gt;&lt;li&gt;Referencing assets with a specific name, for example, in a web manifest file.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;You can find a &lt;a href=&quot;https://www.gatsbyjs.com/docs/how-to/images-and-media/static-folder/&quot;&gt;detailed explanation of the &lt;code&gt;static&lt;/code&gt; folder and use it to your advantage&lt;/a&gt; in the Gatsby documentation.&lt;/p&gt;&lt;h3 id=&quot;embedding-files-from-third-party-services&quot;&gt;Embedding Files From Third-Party Services&lt;/h3&gt;&lt;p&gt;Secondly, you can never be too cautious when embedding third-party services on a website. Replaced content elements, like &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;, can introduce various security vulnerabilities, particularly when you do not have control of the source content. By integrating a third party’s scripts, widgets, or content, a website or app is prone to potential vulnerabilities, such as iframe injection or cross-frame scripting.&lt;/p&gt;&lt;p&gt;Moreover, if an integrated third-party service experiences downtime or performance issues, it can directly impact the user experience.&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;This article explored various approaches for working around common headaches you may encounter when working with Markdown, PDF, and 3D model files in a Gatsby project. In the process, we leveraged several React plugins and Gatsby features that handle how content is parsed, embed files on a page, and manage 3D scenes.&lt;/p&gt;&lt;p&gt;This is also the second article in a brief two-part series that addresses common headaches working with a variety of media types in Gatsby. The &lt;a href=&quot;https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/&quot;&gt;first part covers more common media files&lt;/a&gt;, including images, video, and audio.&lt;/p&gt;&lt;p&gt;If you’re looking for more cures to Gatsby headaches, please &lt;a href=&quot;https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/&quot;&gt;check out my other two-part series&lt;/a&gt; that investigates internationalization.&lt;/p&gt;&lt;h2 id=&quot;see-also&quot;&gt;See Also&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;“&lt;a href=&quot;https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/&quot;&gt;Gatsby Headaches And How To Cure Them: i18n (Part 1)&lt;/a&gt;”&lt;/li&gt;&lt;li&gt;“&lt;a href=&quot;https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-2/&quot;&gt;Gatsby Headaches And How To Cure Them: i18n (Part 2)&lt;/a&gt;”&lt;/li&gt;&lt;li&gt;“&lt;a href=&quot;https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/&quot;&gt;Gatsby Headaches And How To Cure Them: Media Files (Part 1)&lt;/a&gt;”&lt;/li&gt;&lt;/ul&gt;</description><link>https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/</link><guid isPermaLink="false">https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/</guid><pubDate>Mon, 20 Jul 2026 16:00:00 GMT</pubDate><author>Juan Diego Rodríguez</author><category>React</category><category>Optimization</category><category>Performance</category></item><item><title>Gatsby Headaches: Working With Media (Part 1)</title><description>&lt;h1 id=&quot;main-heading&quot; class=&quot;article-header--title&quot;&gt;Gatsby Headaches: Working With Media (Part 1)&lt;/h1&gt;&lt;span id=&quot;article__start&quot; class=&quot;summary__heading&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;Gatsby is an exceptionally flexible static site generator with a robust community and ecosystem to extend a Gatsby site with additional features. Did you know that there are more than a dozen image-related plugins alone? Juan Rodriguez shares what he’s learned about optimizing files for improved performance after working with different plugins and techniques.&lt;ul class=&quot;meta-box meta-box--article&quot;&gt;&lt;li class=&quot;meta-box--item meta-box--published&quot;&gt;17 min read&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--tags&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/category/react&quot;&gt;React&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/optimization&quot;&gt;Optimization&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/performance&quot;&gt;Performance&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--share&quot;&gt;Share on &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/intent/tweet?text=Gatsby%20Headaches%3a%20Working%20With%20Media%20%28Part%201%29&amp;amp;url=https%3A%2F%2Fwww.smashingmagazine.com%2f2023%2f10%2fgatsby-headaches-working-media-part1%2f&amp;amp;via=smashingmag&quot;&gt;Twitter&lt;/a&gt;,&amp;nbsp;&lt;a rel=&quot;nofollow&quot; href=&quot;https://data.smashing.services/ball?uri=//www.linkedin.com/shareArticle?url=https://www.smashingmagazine.com%2f2023%2f10%2fgatsby-headaches-working-media-part1%2f&amp;amp;title=Gatsby%20Headaches%3a%20Working%20With%20Media%20%28Part%201%29&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Working with media files in Gatsby might not be as straightforward as expected. I remember starting my first Gatsby project. After consulting Gatsby’s documentation, I discovered I needed to use the &lt;code&gt;gatsby-source-filesystem&lt;/code&gt; plugin to make queries for local files. Easy enough!&lt;/p&gt;&lt;p&gt;That’s where things started getting complicated. Need to use images? Check the docs and install one — or more! — of the many, many plugins available for handling images. How about working with SVG files? There is another plugin for that. Video files? You get the idea.&lt;/p&gt;&lt;p&gt;It’s all great until any of those plugins or packages become outdated and go unmaintained. That’s where the headaches start.&lt;/p&gt;&lt;p&gt;If you are unfamiliar with &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;Gatsby&lt;/a&gt;, it’s a React-based static site generator that uses GraphQL to pull structured data from various sources and uses webpack to bundle a project so it can then be deployed and served as static files. It’s essentially a static site generator with reactivity that can pull data from a vast array of sources.&lt;/p&gt;&lt;p&gt;Like many static site frameworks in the Jamstack, Gatsby has traditionally enjoyed a great reputation as a &lt;a href=&quot;https://www.gatsbyjs.com/blog/comparing-website-performance-gatsby-vs-next-vs-nuxt/&quot;&gt;performant framework&lt;/a&gt;, although &lt;a href=&quot;https://2022.stateofjs.com/en-US/libraries/&quot;&gt;it has taken a hit in recent years&lt;/a&gt;. Based on what I’ve seen, however, it’s not so much that the framework is fast or slow but how the framework is configured to handle many of the sorts of things that impact performance, including media files.&lt;/p&gt;&lt;p&gt;So, let’s solve the headaches you might encounter when working with media files in a Gatsby project. This article is the first of a brief two-part series where we will look specifically at the media you are most likely to use: images, video, and audio. After that, the &lt;a href=&quot;https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/&quot;&gt;second part of this series&lt;/a&gt; will get into different types of files, including Markdown, PDFs, and even 3D models.&lt;/p&gt;&lt;h2 id=&quot;solving-image-headaches-in-gatsby&quot;&gt;Solving Image Headaches In Gatsby&lt;/h2&gt;&lt;p&gt;I think that the process of optimizing images can fall into four different buckets:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Optimize image files.&lt;/strong&gt;&lt;br&gt;Minimizing an image’s file size without losing quality directly leads to shorter fetching times. This can be done manually or during a build process. It’s also possible to use a service, like Cloudinary, to handle the work on demand.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Prioritize images that are part of the First Contentful Paint (FCP).&lt;/strong&gt;&lt;br&gt;FCP is a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/First_contentful_paint&quot;&gt;metric that measures the time&lt;/a&gt; between the point when a page starts loading to when the first bytes of content are rendered. The idea is that fetching assets that are part of that initial render earlier results in faster loading rather than waiting for other assets lower on the chain.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Lazy loading other images.&lt;/strong&gt;&lt;br&gt;We can prevent the rest of the images from render-blocking other assets using the &lt;code&gt;loading=&quot;lazy&quot;&lt;/code&gt; attribute on images.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Load the right image file for the right context.&lt;/strong&gt;&lt;br&gt;With &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images&quot;&gt;responsive images&lt;/a&gt;, we can serve one version of an image file at one screen size and serve another image at a different screen size with the &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; attributes or with the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;These are great principles for any website, not only those built with Gatsby. But how we build them into a Gatsby-powered site can be confusing, which is why I’m writing this article and perhaps why you’re reading it.&lt;/p&gt;&lt;h3 id=&quot;lazy-loading-images-in-gatsby&quot;&gt;Lazy Loading Images In Gatsby&lt;/h3&gt;&lt;p&gt;We can apply an image to a React component in a Gatsby site like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import forest from &quot;./assets/images/forest.jpg&quot;;

const ImageHTML = () =&amp;gt; {
  return &amp;lt;img src={ forest } alt=&quot;Forest trail&quot; /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It’s important to &lt;code&gt;import&lt;/code&gt; the image as a JavaScript module. This lets webpack know to bundle the image and generate a path to its location in the public folder.&lt;/p&gt;&lt;p&gt;This works fine, but when are we ever working with only one image? What if we want to make an image gallery that contains 100 images? If we try to load that many &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags at once, they will certainly slow things down and could affect the FCP. That’s where the third principle that uses the &lt;code&gt;loading=&quot;lazy&quot;&lt;/code&gt; attribute can come into play.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import forest from &quot;./assets/images/forest.jpg&quot;;

const LazyImageHTML = () =&amp;gt; {
  return &amp;lt;img src={ forest } loading=&quot;lazy&quot; alt=&quot;Forest trail&quot; /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can do the opposite with &lt;code&gt;loading=&quot;eager&quot;&lt;/code&gt;. It instructs the browser to load the image as soon as possible, regardless of whether it is onscreen or not.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import forest from &quot;./assets/images/forest.jpg&quot;;

const EagerImageHTML = () =&amp;gt; {
  return &amp;lt;img src={ forest } loading=&quot;eager&quot; alt=&quot;Forest trail&quot; /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;implementing-responsive-images-in-gatsby&quot;&gt;Implementing Responsive Images In Gatsby&lt;/h3&gt;&lt;p&gt;This is a basic example of the HTML for responsive images:&lt;/p&gt;&lt;p&gt;In Gatsby, we must &lt;code&gt;import&lt;/code&gt; the images first and pass them to the &lt;code&gt;srcset&lt;/code&gt; attribute as template literals so webpack can bundle them:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import forest800 from &quot;./assets/images/forest-800.jpg&quot;;

import forest400 from &quot;./assets/images/forest-400.jpg&quot;;

const ResponsiveImageHTML = () =&amp;gt; {
  return (
    &amp;lt;img
      srcSet={`

        ${ forest400 } 400w,

        ${ forest800 } 800w

      `}
      sizes=&quot;(max-width: 500px) 400px, 800px&quot;
      alt=&quot;Forest trail&quot;
    /&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That should take care of any responsive image headaches in the future.&lt;/p&gt;&lt;h3 id=&quot;loading-background-images-in-gatsby&quot;&gt;Loading Background Images In Gatsby&lt;/h3&gt;&lt;p&gt;What about pulling in the URL for an image file to use on the CSS &lt;code&gt;background-url&lt;/code&gt; property? That looks something like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import &quot;./style.css&quot;;

const ImageBackground = () =&amp;gt; {
  return &amp;lt;div className=&quot;banner&quot;&amp;gt;&amp;lt;/div&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* style.css */

.banner {
    aspect-ratio: 16/9;
      background-size: cover;

    background-image: url(&quot;./assets/images/forest-800.jpg&quot;);

  /* etc. */
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is straightforward, but there is still room for optimization! For example, we can do the CSS version of responsive images, which loads the version we want at specific breakpoints.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* style.css */

@media (max-width: 500px) {
  .banner {
    background-image: url(&quot;./assets/images/forest-400.jpg&quot;);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;using-the-gatsby-source-filesystem-plugin&quot;&gt;Using The &lt;code&gt;gatsby-source-filesystem&lt;/code&gt; Plugin&lt;/h3&gt;&lt;p&gt;Before going any further, I think it is worth installing the &lt;code&gt;gatsby-source-filesystem&lt;/code&gt; plugin. It’s an essential part of any Gatsby project because it allows us to &lt;a href=&quot;https://www.gatsbyjs.com/docs/how-to/sourcing-data/sourcing-from-the-filesystem/&quot;&gt;query data from various directories in the local filesystem&lt;/a&gt;, making it simpler to fetch assets, like a folder of optimized images.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i gatsby-source-filesystem
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can add it to our &lt;code&gt;gatsby-config.js&lt;/code&gt; file and specify the directory from which we will query our media assets:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,

      options: {
        name: `assets`,

        path: `${ __dirname }/src/assets`,
      },
    },
  ],
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Remember to restart your development server to see changes from the &lt;code&gt;gatsby-config.js&lt;/code&gt; file.&lt;/p&gt;&lt;p&gt;Now that we have &lt;code&gt;gatsby-source-filesystem&lt;/code&gt; installed, we can continue solving a few other image-related headaches. For example, the next plugin we look at is capable of simplifying the cures we used for lazy loading and responsive images.&lt;/p&gt;&lt;h3 id=&quot;using-the-gatsby-plugin-image-plugin&quot;&gt;Using The &lt;code&gt;gatsby-plugin-image&lt;/code&gt; Plugin&lt;/h3&gt;&lt;p&gt;The &lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-plugin-image/&quot;&gt;&lt;code&gt;gatsby-plugin-image&lt;/code&gt; plugin&lt;/a&gt; (not to be confused with the outdated &lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-image/&quot;&gt;&lt;code&gt;gatsby-image&lt;/code&gt; plugin&lt;/a&gt;) uses techniques that &lt;strong&gt;automatically handle various aspects of image optimization&lt;/strong&gt;, such as lazy loading, responsive sizing, and even generating optimized image formats for modern browsers.&lt;/p&gt;&lt;p&gt;Once installed, we can replace standard &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags with either the &lt;code&gt;&amp;lt;GatsbyImage&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;StaticImage&amp;gt;&lt;/code&gt; components, depending on the use case. These components take advantage of the plugin’s features and use the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; HTML element to ensure the most appropriate image is served to each user based on their device and network conditions.&lt;/p&gt;&lt;p&gt;We can start by installing &lt;code&gt;gatsby-plugin-image&lt;/code&gt; and the other plugins it depends on:&lt;/p&gt;&lt;p&gt;Let’s add them to the &lt;code&gt;gatsby-config.js&lt;/code&gt; file:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// gatsby-config.js

module.exports = {
plugins: [

// other plugins
`gatsby-plugin-image`,
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`],

};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This provides us with some features we will put to use a bit later.&lt;/p&gt;&lt;h3 id=&quot;using-the-staticimage-component&quot;&gt;Using The &lt;code&gt;StaticImage&lt;/code&gt; Component&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;StaticImage&lt;/code&gt; component serves &lt;strong&gt;images that don’t require dynamic sourcing or complex transformations&lt;/strong&gt;. It’s particularly useful for scenarios where you have a fixed image source that doesn’t change based on user interactions or content updates, like logos, icons, or other static images that remain consistent.&lt;/p&gt;&lt;p&gt;The main attributes we will take into consideration are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;src&lt;/code&gt;:&lt;/strong&gt; This attribute is required and should be set to the path of the image you want to display.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;alt&lt;/code&gt;:&lt;/strong&gt; Provides alternative text for the image.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;placeholder&lt;/code&gt;:&lt;/strong&gt; This attribute can be set to either &lt;code&gt;blurred&lt;/code&gt; or &lt;code&gt;dominantColor&lt;/code&gt; to define the type of placeholder to display while the image is loading.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;layout&lt;/code&gt;:&lt;/strong&gt; This defines how the image should be displayed. It can be set to &lt;code&gt;fixed&lt;/code&gt; for, as you might imagine, images with a fixed size, &lt;code&gt;fullWidth&lt;/code&gt; for images that span the entire container, and &lt;code&gt;constrained&lt;/code&gt; for images scaled down to fit their container.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;loading&lt;/code&gt;:&lt;/strong&gt; This determines when the image should start loading while also supporting the &lt;code&gt;eager&lt;/code&gt; and &lt;code&gt;lazy&lt;/code&gt; options.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Using &lt;code&gt;StaticImage&lt;/code&gt; is similar to using a regular HTML &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag. However, &lt;code&gt;StaticImage&lt;/code&gt; requires passing the string directly to the &lt;code&gt;src&lt;/code&gt; attribute so it can be bundled by webpack.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import { StaticImage } from &quot;gatsby-plugin-image&quot;;

const ImageStaticGatsby = () =&amp;gt; {
  return (
    &amp;lt;StaticImage
      src=&quot;./assets/images/forest.jpg&quot;
      placeholder=&quot;blurred&quot;
      layout=&quot;constrained&quot;
      alt=&quot;Forest trail&quot;
      loading=&quot;lazy&quot;
    /&amp;gt;
  );
  };
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;StaticImage&lt;/code&gt; component is great, but you have to take its constraints into account:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;No Dynamically Loading URLs&lt;/strong&gt;&lt;br&gt;One of the most significant limitations is that the &lt;code&gt;StaticImage&lt;/code&gt; component doesn’t support dynamically loading images based on URLs fetched from data sources or APIs.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Compile-Time Image Handling&lt;/strong&gt;&lt;br&gt;The &lt;code&gt;StaticImage&lt;/code&gt; component’s image handling occurs at compile time. This means that the images you specify are processed and optimized when the Gatsby site is built. Consequently, if you have images that need to change frequently based on user interactions or updates, the static nature of this component might not fit your needs.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Limited Transformation Options&lt;/strong&gt;&lt;br&gt;Unlike the more versatile &lt;code&gt;GatsbyImage&lt;/code&gt; component, the &lt;code&gt;StaticImage&lt;/code&gt; component provides fewer transformation options, e.g., there is no way to apply complex transformations like cropping, resizing, or adjusting image quality directly within the component. You may want to consider alternative solutions if you require advanced transformations.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;using-the-gatsbyimage-component&quot;&gt;Using The &lt;code&gt;GatsbyImage&lt;/code&gt; Component&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;GatsbyImage&lt;/code&gt; component is a &lt;strong&gt;more versatile solution&lt;/strong&gt; that addresses the limitations of the &lt;code&gt;StaticImage&lt;/code&gt; component. It’s particularly useful for scenarios involving dynamic image loading, complex transformations, and advanced customization.&lt;/p&gt;&lt;p&gt;Some ideal use cases where &lt;code&gt;GatsbyImage&lt;/code&gt; is particularly useful include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Dynamic Image Loading&lt;/strong&gt;&lt;br&gt;If you need to load images dynamically based on data from APIs, content management systems, or other sources, the &lt;code&gt;GatsbyImage&lt;/code&gt; component is the go-to choice. It can fetch images and optimize their loading behavior.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Complex transformations&lt;/strong&gt;&lt;br&gt;The &lt;code&gt;GatsbyImage&lt;/code&gt; component is well-suited for advanced transformations, using GraphQL queries to apply them.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Responsive images&lt;/strong&gt;&lt;br&gt;For responsive design, the &lt;code&gt;GatsbyImage&lt;/code&gt; component excels by automatically generating multiple sizes and formats of an image, ensuring that users receive an appropriate image based on their device and network conditions.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Unlike the &lt;code&gt;StaticImage&lt;/code&gt; component, which uses a &lt;code&gt;src&lt;/code&gt; attribute, &lt;code&gt;GatsbyImage&lt;/code&gt; has an &lt;code&gt;image&lt;/code&gt; attribute that takes a &lt;code&gt;gatsbyImageData&lt;/code&gt; object. &lt;code&gt;gatsbyImageData&lt;/code&gt; contains the image information and can be queried from GraphQL using the following query.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;query {
  file(name: { eq: &quot;forest&quot; }) {
    childImageSharp {
      gatsbyImageData(width: 800, placeholder: BLURRED, layout: CONSTRAINED)
    }

    name
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you’re following along, you can look around your Gatsby data layer at &lt;code&gt;http://localhost:8000/___graphql&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;From here, we can use the &lt;code&gt;useStaticQuery&lt;/code&gt; hook and the &lt;code&gt;graphql&lt;/code&gt; tag to fetch data from the data layer:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import { useStaticQuery, graphql } from &quot;gatsby&quot;;

import { GatsbyImage, getImage } from &quot;gatsby-plugin-image&quot;;

const ImageGatsby = () =&amp;gt; {
  // Query data here:

  const data = useStaticQue(graphql``);

  return &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we can write the GraphQL query inside of the &lt;code&gt;graphql&lt;/code&gt; tag:&lt;/p&gt;&lt;p&gt;Next, we import the &lt;code&gt;GatsbyImage&lt;/code&gt; component from &lt;code&gt;gatsby-plugin-image&lt;/code&gt; and assign the image’s &lt;code&gt;gatsbyImageData&lt;/code&gt; property to the &lt;code&gt;image&lt;/code&gt; attribute:&lt;/p&gt;&lt;p&gt;Now, we can use the &lt;code&gt;getImage&lt;/code&gt; helper function to make the code easier to read. When given a &lt;code&gt;File&lt;/code&gt; object, the function returns the &lt;code&gt;file.childImageSharp.gatsbyImageData&lt;/code&gt; property, which can be passed directly to the &lt;code&gt;GatsbyImage&lt;/code&gt; component.&lt;/p&gt;&lt;h3 id=&quot;using-the-gatsby-background-image-plugin&quot;&gt;Using The &lt;code&gt;gatsby-background-image&lt;/code&gt; Plugin&lt;/h3&gt;&lt;p&gt;Another plugin we could use to take advantage of Gatsby’s image optimization capabilities is the &lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-background-image/&quot;&gt;&lt;code&gt;gatsby-background-image&lt;/code&gt; plugin&lt;/a&gt;. However, I do not recommend using this plugin since it is outdated and prone to compatibility issues. Instead, &lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-background-image/#gatsby-34--gatsby-plugin-image&quot;&gt;Gatsby suggests&lt;/a&gt; using &lt;code&gt;gatsby-plugin-image&lt;/code&gt; when working with the latest Gatsby version 3 and above.&lt;/p&gt;&lt;p&gt;If this compatibility doesn’t represent a significant problem for your project, you can refer to &lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-background-image/#table-of-contents&quot;&gt;the plugin’s documentation for specific instructions&lt;/a&gt; and use it in place of the CSS &lt;code&gt;background-url&lt;/code&gt; usage I described earlier.&lt;/p&gt;&lt;h2 id=&quot;solving-video-and-audio-headaches-in-gatsby&quot;&gt;Solving Video And Audio Headaches In Gatsby&lt;/h2&gt;&lt;p&gt;Working with videos and audio can be a bit of a mess in Gatsby since it lacks plugins for sourcing and optimizing these types of files. In fact, &lt;a href=&quot;https://www.gatsbyjs.com/docs/how-to/images-and-media/working-with-video/#hosting-your-own-html5-video-files&quot;&gt;Gatsby’s documentation&lt;/a&gt; doesn’t name or recommend any official plugins we can turn to.&lt;/p&gt;&lt;p&gt;That means we will have to use &lt;em&gt;vanilla&lt;/em&gt; methods for videos and audio in Gatsby.&lt;/p&gt;&lt;h3 id=&quot;using-the-html-video-element&quot;&gt;Using The HTML &lt;code&gt;video&lt;/code&gt; Element&lt;/h3&gt;&lt;p&gt;The HTML &lt;code&gt;video&lt;/code&gt; element is capable of serving different versions of the same video using the &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; tag, much like the &lt;code&gt;img&lt;/code&gt; element uses the &lt;code&gt;srset&lt;/code&gt; attribute to do the same for responsive images.&lt;/p&gt;&lt;p&gt;That allows us to not only serve a more performant video format but also to provide a fallback video for older browsers that may not support the bleeding edge:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import natureMP4 from &quot;./assets/videos/nature.mp4&quot;;

import natureWEBM from &quot;./assets/videos/nature.webm&quot;;

const VideoHTML = () =&amp;gt; {
  return (
    &amp;lt;video controls&amp;gt;
      &amp;lt;source src={ natureMP4 } type=&quot;video/mp4&quot; /&amp;gt;

      &amp;lt;source src={ natureWEBM } type=&quot;video/webm&quot; /&amp;gt;
    &amp;lt;/video&amp;gt;
  );
};

P;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can also apply lazy loading to videos like we do for images. While videos do not support the &lt;code&gt;loading=&quot;lazy&quot;&lt;/code&gt; attribute, there is a &lt;code&gt;preload&lt;/code&gt; attribute that is similar in nature. When set to &lt;code&gt;none&lt;/code&gt;, the attribute instructs the browser to load a video and its metadata only when the user interacts with it. In other words, it’s lazy-loaded until the user taps or clicks the video.&lt;/p&gt;&lt;p&gt;We can also set the attribute to &lt;code&gt;metadata&lt;/code&gt; if we want the video’s details, such as its duration and file size, fetched right away.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&amp;lt;video controls preload=&quot;none&quot;&amp;gt;
  &amp;lt;source src={ natureMP4 } type=&quot;video/mp4&quot; /&amp;gt;

  &amp;lt;source src={ natureWEBM } type=&quot;video/webm&quot; /&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;em&gt;I personally do not recommend using the &lt;code&gt;autoplay&lt;/code&gt; attribute since it is disruptive and disregards the &lt;code&gt;preload&lt;/code&gt; attribute, causing the video to load right away.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;And, like images, display a placeholder image for a video while it is loading with the &lt;code&gt;poster&lt;/code&gt; attribute pointing to an image file.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&amp;lt;video controls preload=&quot;none&quot; poster={ forest }&amp;gt;
  &amp;lt;source src={ natureMP4 } type=&quot;video/mp4&quot; /&amp;gt;

  &amp;lt;source src={ natureWEBM } type=&quot;video/webm&quot; /&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;using-the-html-audio-element&quot;&gt;Using The HTML &lt;code&gt;audio&lt;/code&gt; Element&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;audio&lt;/code&gt; and &lt;code&gt;video&lt;/code&gt; elements behave similarly, so adding an &lt;code&gt;audio&lt;/code&gt; element in Gatsby looks nearly identical, aside from the element:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import audioSampleMP3 from &quot;./assets/audio/sample.mp3&quot;;

import audioSampleWAV from &quot;./assets/audio/sample.wav&quot;;

const AudioHTML = () =&amp;gt; {
  return (
    &amp;lt;audio controls&amp;gt;
      &amp;lt;source src={ audioSampleMP3 } type=&quot;audio/mp3&quot; /&amp;gt;

      &amp;lt;source src={ audioSampleWAV } type=&quot;audio/wav&quot; /&amp;gt;
    &amp;lt;/audio&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you might expect, the &lt;code&gt;audio&lt;/code&gt; element also supports the &lt;code&gt;preload&lt;/code&gt; attribute:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&amp;lt;audio controls preload=&quot;none&quot;&amp;gt;
  &amp;lt;source src={ audioSampleMP3 } type=&quot;audio/mp3&quot; /&amp;gt;

  &amp;lt;source src={a udioSampleWAV } type=&quot;audio/wav&quot; /&amp;gt;
&amp;lt;/audio&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is probably as good as we can do to use videos and images in Gatsby with performance in mind, aside from saving and compressing the files as best we can before serving them.&lt;/p&gt;&lt;h2 id=&quot;solving-iframe-headaches-in-gatsby&quot;&gt;Solving iFrame Headaches In Gatsby&lt;/h2&gt;&lt;p&gt;Speaking of video, what about ones embedded in an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; like we might do with a video from YouTube, Vimeo, or some other third party? Those can certainly lead to performance headaches, but it’s not as we have direct control over the video file and where it is served.&lt;/p&gt;&lt;p&gt;Not all is lost because the HTML &lt;code&gt;iframe&lt;/code&gt; element supports lazy loading the same way that images do.&lt;/p&gt;&lt;p&gt;Embedding a third-party video player via &lt;code&gt;iframe&lt;/code&gt; can possibly be an easier path than using the HTML &lt;code&gt;video&lt;/code&gt; element. &lt;code&gt;iframe&lt;/code&gt; elements are cross-platform compatible and could reduce hosting demands if you are working with heavy video files on your own server.&lt;/p&gt;&lt;p&gt;That said, an &lt;code&gt;iframe&lt;/code&gt; is essentially a &lt;strong&gt;sandbox serving a page from an outside source&lt;/strong&gt;. They’re not weightless, and we have no control over the code they contain. There are also GDPR considerations when it comes to services (such as YouTube) due to cookies, data privacy, and third-party ads.&lt;/p&gt;&lt;h2 id=&quot;solving-svg-headaches-in-gatsby&quot;&gt;Solving SVG Headaches In Gatsby&lt;/h2&gt;&lt;p&gt;SVGs contribute to improved page performance in several ways. Their vector nature results in a &lt;strong&gt;much smaller file size&lt;/strong&gt; compared to raster images, and they &lt;strong&gt;can be scaled up without compromising quality&lt;/strong&gt;. And SVGs can be compressed with GZIP, further reducing file sizes.&lt;/p&gt;&lt;p&gt;That said, there are several ways that we can use SVG files. Let’s tackle each one in the contact of Gatsby.&lt;/p&gt;&lt;h3 id=&quot;using-inline-svg&quot;&gt;Using Inline SVG&lt;/h3&gt;&lt;p&gt;SVGs are essentially lines of code that describe shapes and paths, making them lightweight and highly customizable. Due to their XML-based structure, SVG images can be directly embedded within the HTML &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;



const SVGInline = () =&amp;gt; {

  return (

    &amp;lt;svg viewBox=&quot;0 0 24 24&quot; fill=&quot;#000000&quot;&amp;gt;

      &amp;lt;!-- etc. --&amp;gt;

    &amp;lt;/svg&amp;gt;

  );

};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Just remember to change certain SVG attributes, such as &lt;code&gt;xmlns:xlink&lt;/code&gt; or &lt;code&gt;xlink:href&lt;/code&gt;, to JSX attribute spelling, like &lt;code&gt;xmlnsXlink&lt;/code&gt; and &lt;code&gt;xlinkHref&lt;/code&gt;, respectively.&lt;/p&gt;&lt;h3 id=&quot;using-svg-in-img-elements&quot;&gt;Using SVG In &lt;code&gt;img&lt;/code&gt; Elements&lt;/h3&gt;&lt;p&gt;An SVG file can be passed into an &lt;code&gt;img&lt;/code&gt; element’s &lt;code&gt;src&lt;/code&gt; attribute like any other image file.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import picture from &quot;./assets/svg/picture.svg&quot;;

const SVGinImg = () =&amp;gt; {
  return &amp;lt;img src={ picture } alt=&quot;Picture&quot; /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Loading SVGs inline or as HTML images are the &lt;em&gt;de facto&lt;/em&gt; approaches, but there are React and Gatsby plugins capable of simplifying the process, so let’s look at those next.&lt;/p&gt;&lt;h3 id=&quot;inlining-svg-with-the-react-svg-plugin&quot;&gt;Inlining SVG With The &lt;code&gt;react-svg&lt;/code&gt; Plugin&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/tanem/react-svg&quot;&gt;&lt;code&gt;react-svg&lt;/code&gt;&lt;/a&gt; provides an efficient way to render SVG images as React components by swapping a &lt;code&gt;ReactSVG&lt;/code&gt; component in the DOM with an inline SVG.&lt;/p&gt;&lt;p&gt;Once installing the plugin, import the &lt;code&gt;ReactSVG&lt;/code&gt; component and assign the SVG file to the component’s &lt;code&gt;src&lt;/code&gt; attribute:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import { ReactSVG } from &quot;react-svg&quot;;

import camera from &quot;./assets/svg/camera.svg&quot;;

const SVGReact = () =&amp;gt; {
  return &amp;lt;ReactSVG src={ camera } /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;using-the-gatsby-plugin-react-svg-plugin&quot;&gt;Using The &lt;code&gt;gatsby-plugin-react-svg&lt;/code&gt; Plugin&lt;/h3&gt;&lt;p&gt;The &lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-plugin-react-svg/&quot;&gt;&lt;code&gt;gatsby-plugin-react-svg&lt;/code&gt; plugin&lt;/a&gt; adds &lt;a href=&quot;https://github.com/jhamlet/svg-react-loader&quot;&gt;svg-react-loader&lt;/a&gt; to your Gatsby project’s webpack configuration. The plugin &lt;strong&gt;adds a loader to support using SVG files as React components while bundling them as inline SVG&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Once the plugin is installed, add it to the &lt;code&gt;gatsby-config.js&lt;/code&gt; file. From there, add a webpack rule inside the plugin configuration to only load SVG files ending with a certain filename, making it easy to split inline SVGs from other assets:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: &quot;gatsby-plugin-react-svg&quot;,

      options: {
        rule: {
          include: /\.inline\.svg$/,
        },
      },
    },
  ],
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can import SVG files like any other React component:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &quot;react&quot;;

import Book from &quot;./assets/svg/book.inline.svg&quot;;

const GatsbyPluginReactSVG = () =&amp;gt; {
  return &amp;lt;Book /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And just like that, we can use SVGs in our Gatsby pages in several different ways!&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Even though I personally love Gatsby, working with media files has given me more than a few headaches.&lt;/p&gt;&lt;p&gt;As a final tip, when needing common features such as images or querying from your local filesystem, go ahead and install the necessary plugins. But when you need a minor feature, try doing it yourself with the methods that are already available to you!&lt;/p&gt;&lt;p&gt;If you have experienced different headaches when working with media in Gatsby or have circumvented them with different approaches than what I’ve covered, please share them! This is a big space, and it’s always helpful to see how others approach similar challenges.&lt;/p&gt;&lt;p&gt;Again, this article is the first of a brief two-part series on curing headaches when working with media files in a Gatsby project. The &lt;a href=&quot;https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part2/&quot;&gt;following article&lt;/a&gt; will be about avoiding headaches when working with different media files, including Markdown, PDFs, and 3D models.&lt;/p&gt;&lt;h3 id=&quot;further-reading&quot;&gt;Further Reading&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;“&lt;a href=&quot;https://www.smashingmagazine.com/2022/06/demystifying-gatsby4-framework/&quot;&gt;Demystifying The New Gatsby Framework&lt;/a&gt;”&lt;/li&gt;&lt;li&gt;“&lt;a href=&quot;https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-1/&quot;&gt;Gatsby Headaches And How To Cure Them: i18n (Part 1)&lt;/a&gt;”&lt;/li&gt;&lt;li&gt;“&lt;a href=&quot;https://www.smashingmagazine.com/2023/06/gatsby-headaches-i18n-part-2/&quot;&gt;Gatsby Headaches And How To Cure Them: i18n (Part 2)&lt;/a&gt;”&lt;/li&gt;&lt;/ul&gt;</description><link>https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/</link><guid isPermaLink="false">https://www.smashingmagazine.com/2023/10/gatsby-headaches-working-media-part1/</guid><pubDate>Mon, 20 Jul 2026 16:00:00 GMT</pubDate><author>Juan Diego Rodríguez</author><category>React</category><category>Optimization</category><category>Performance</category></item><item><title>Meet Codux: The React Visual Editor That Improves Developer Experience</title><description>&lt;h1 id=&quot;main-heading&quot; class=&quot;article-header--title&quot;&gt;Meet Codux: The React Visual Editor That Improves Developer Experience&lt;/h1&gt;&lt;span id=&quot;article__start&quot; class=&quot;summary__heading&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;a href=&quot;https://codux.hopp.to/smashing&quot;&gt;Codux&lt;/a&gt; is a new visual IDE brought to you by the fine folks over at&lt;a href=&quot;https://www.wix.com/&quot;&gt; Wix&lt;/a&gt;. It’s got a lot of low-code features that level the playing field for app development but is really designed for React developers. How so, you ask? That’s what we’re going to explore in this article.&lt;ul class=&quot;meta-box meta-box--article&quot;&gt;&lt;li class=&quot;meta-box--item meta-box--published&quot;&gt;7 min read&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--tags&quot;&gt;&lt;a href=&quot;https://www.smashingmagazine.com/category/react&quot;&gt;React&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/tools&quot;&gt;Tools&lt;/a&gt;,
&lt;a href=&quot;https://www.smashingmagazine.com/category/workflow&quot;&gt;Workflow&lt;/a&gt;&lt;/li&gt;&lt;li class=&quot;meta-box--item meta-box--share&quot;&gt;Share on &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/intent/tweet?text=Meet%20Codux%3a%20The%20React%20Visual%20Editor%20That%20Improves%20Developer%20Experience&amp;amp;url=https%3A%2F%2Fwww.smashingmagazine.com%2f2023%2f06%2fcodux-react-visual-editor-improves-developer-experience%2f&amp;amp;via=smashingmag&quot;&gt;Twitter&lt;/a&gt;,&amp;nbsp;&lt;a rel=&quot;nofollow&quot; href=&quot;https://data.smashing.services/ball?uri=//www.linkedin.com/shareArticle?url=https://www.smashingmagazine.com%2f2023%2f06%2fcodux-react-visual-editor-improves-developer-experience%2f&amp;amp;title=Meet%20Codux%3a%20The%20React%20Visual%20Editor%20That%20Improves%20Developer%20Experience&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Personally, I get tired of the antics at the start of any new project. I’m a contractor, too, so there’s always some new dependency I need to adopt, config files to force me to write the way a certain team likes, and deployment process I need to plug into. It’s never a fire-up-and-go sort of thing, and it often takes the better part of a working day to get it all right.&lt;/p&gt;&lt;p&gt;There are a lot of moving pieces to a project, right? Everything — from integrating a framework and establishing a component library to collaboration and deployments — is a separate but equally important part of your IDE. If you’re like me, jumping between apps and systems is something you get used to. But honestly, it’s an act of Sisyphus rolling the stone up the mountain each time, only to do it again on the next project.&lt;/p&gt;&lt;p&gt;That’s the setup for what I think is a pretty darn good approach to streamline this convoluted process in a way that supports any common project structure and is capable of enhancing it with visual editing capabilities. &lt;a href=&quot;https://codux.hopp.to/smashing&quot;&gt;It’s called Codux&lt;/a&gt;, and if you stick with me for a moment, I think you’ll agree that Codux could be the one-stop shop for everything you need to build production-ready React apps.&lt;/p&gt;&lt;h2 id=&quot;codux-is-more-your-code-than-low-code&quot;&gt;Codux is More “Your-Code” Than “Low-Code”&lt;/h2&gt;&lt;p&gt;I know, I know. &lt;em&gt;“Yay, another visual editor!”&lt;/em&gt; says no one, ever. The planet is already full of those, and they’re really designed to give folks developer superpowers without actually doing any development.&lt;/p&gt;&lt;p&gt;That’s so not the case with Codux. There are indeed a lot of “low-code” affordances that could empower non-developers, but that’s not the headlining feature of Codux or really who or what it caters to. Instead, Codux is a fully-integrated IDE that provides the bones of your project while improving the developer experience instead of abstracting it away.&lt;/p&gt;&lt;p&gt;Do you use CodePen? What makes it so popular (and great to use) is that it “just” works. It combines frameworks, preprocessors, a live rendering environment, and modern build tools into a single interface that does all the work on “Save”. But I still get to write code in a single place, the way I like it.&lt;/p&gt;&lt;p&gt;I see Codux a lot like that. But bigger. Not bigger in the sense of more complicated, but bigger in that it is more integrated than frameworks and build tools. It &lt;em&gt;is&lt;/em&gt; your framework. It &lt;em&gt;is&lt;/em&gt; your component library. It &lt;em&gt;is&lt;/em&gt; your build process. And it just so happens to have incredibly powerful visual editing controls that are fully integrated with your code editor.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://codux.hopp.to/smashing&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;524&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png&quot; sizes=&quot;100vw&quot; alt=&quot;Creating a new project with Codux&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;There’s a growing collection of frameworks and starters right out of the box. (&lt;a href=&quot;https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/codux-new-project.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;That’s why it makes more sense to call Codux “your code” instead of the typical low-code or no-code visual editing tools. Those are designed for non-developers. Codux, on the other hand, is made &lt;em&gt;for developers&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;In fact, here’s a pretty fun thing to do. Open a component file from your project in VS Code and put the editor window next to the Codux window open to the same component. Make a small CSS change or something and watch both the preview rendering &lt;em&gt;and&lt;/em&gt; code update instantly in Codux.&lt;/p&gt;&lt;figure class=&quot;video-embed-container break-out&quot;&gt;&lt;div class=&quot;video-embed-container--wrapper&quot;&gt;&lt;iframe class=&quot;video-embed-container--wrapper-iframe&quot; src=&quot;https://player.vimeo.com/video/836090503&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; fullscreen; picture-in-picture&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;It’s &lt;em&gt;that&lt;/em&gt; tightly integrated.&lt;/p&gt;&lt;h2 id=&quot;it-s-your-visual-guide-for-react&quot;&gt;It’s Your Visual Guide For React&lt;/h2&gt;&lt;p&gt;That’s really the crux of Codux. It really &lt;em&gt;is&lt;/em&gt; a visual approach to working with React, at least from the development side of things. If I open a component file, Codux gives me all the context I need to edit the code confidently because it &lt;em&gt;shows&lt;/em&gt; me where and how the pieces are used throughout the app. Heck, it’s just as good (if not better!) at doing DevTools-y things than DevTools itself!&lt;/p&gt;&lt;figure class=&quot;video-embed-container break-out&quot;&gt;&lt;div class=&quot;video-embed-container--wrapper&quot;&gt;&lt;iframe class=&quot;video-embed-container--wrapper-iframe&quot; src=&quot;https://player.vimeo.com/video/836092154&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; fullscreen; picture-in-picture&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;Visual boards, component trees, Git integration, debugging… Codux has them all and more, making it even more of a one-stop shop for all things related to a project. I compared Codux to CodePen earlier, but really it’s more like having CodePen, Figma, GitHub, VS Code, and even a prototyping app all in one place without ever having to leave your code.&lt;/p&gt;&lt;h2 id=&quot;visually-explore-the-code&quot;&gt;Visually Explore The Code&lt;/h2&gt;&lt;p&gt;Here’s something you might not expect from a tool like Codux. There are plenty of times when I make a change to a component, preview it, and then spot an issue somewhere else. There’s no easy way to find where the code for that issue is other than clicking into DevTools to inspect a certain element. I find myself constantly guessing because it takes too darn long to find the information or dig through the component library files to investigate upfront.&lt;/p&gt;&lt;p&gt;As such, I often have no idea how or if the code I am writing is going to affect something else in the app. Will changing this piece in one component impact the same piece in another component? By updating the component’s state or behavior here, am I inadvertently triggering another state that shouldn’t be affected?&lt;/p&gt;&lt;p&gt;Codux lets you navigate code &lt;em&gt;visually&lt;/em&gt;. Your previews are right there in the app, and thanks to the tight integration between the visual and code editing capabilities, finding a piece of code is as simple as clicking on the element. Doing that will navigate you directly to the relevant code — both in the built-in code editor &lt;em&gt;and&lt;/em&gt; your code editor. There’s absolutely no need to leave the code to go troubleshoot an issue or look up a computed value.&lt;/p&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://codux.hopp.to/smashing&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;760&quot; height=&quot;589&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png&quot; sizes=&quot;100vw&quot; alt=&quot;Cloudcash screenshot&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;(&lt;a href=&quot;https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-screenshot.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;That’s just one of those affordances that really polish up the developer experience. Anyone else might overlook something like this, but as a developer, you know how much saved time can add up with something like this.&lt;/p&gt;&lt;h2 id=&quot;code-inspect-and-debug-together-at-last&quot;&gt;Code, Inspect And Debug Together At Last&lt;/h2&gt;&lt;p&gt;There are a few other affordances available when selecting an element on the interactive stage on Codux:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A &lt;strong&gt;style panel&lt;/strong&gt; for editing CSS and trying different layouts. And, again, changes are made in real-time, both in the rendered preview and in &lt;em&gt;your&lt;/em&gt; code, which is visible to you all the time — whether directly in Codux or in your IDE.&lt;/li&gt;&lt;li&gt;A &lt;strong&gt;property panel&lt;/strong&gt; that provides easy access to all the selected properties of a component with visual controllers to modify them (and see the changes reflected directly in the code)&lt;/li&gt;&lt;li&gt;An &lt;strong&gt;environment panel&lt;/strong&gt; that provides you with control over the rendering environment of the component, such as the screen or canvas size, as well as the styling for it.&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;break-out article__image&quot;&gt;&lt;a href=&quot;https://codux.hopp.to/smashing&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;low&quot; width=&quot;800&quot; height=&quot;458&quot; srcset=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png 2000w&quot; src=&quot;https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png&quot; sizes=&quot;100vw&quot; alt=&quot;Codux Elements Tree&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;figcaption class=&quot;op-vertical-bottom&quot;&gt;See the panel on the left? That is the Codux Elements Tree, which reveals the actual DOM and logic responsible for rendering components, letting you validate structure, whether the component is static or dynamic. (&lt;a href=&quot;https://files.smashing.media/articles/codux-react-visual-editor-improves-developer-experience/cloudcash-weekly-sumup.png&quot;&gt;Large preview&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;h2 id=&quot;maybe-give-codux-a-spin&quot;&gt;Maybe Give Codux A Spin&lt;/h2&gt;&lt;p&gt;It’s pretty rad that I can fire up a single app to access my component library, code, documentation, live previews, DOM inspector, and version control. If you would’ve tried explaining this to me &lt;em&gt;before&lt;/em&gt; seeing Codux, I would’ve said that’s too much for one app to handle; it’d be a messy UI that’s more aspiration than it is a liberating center of development productivity.&lt;/p&gt;&lt;p&gt;No lying. That’s exactly what I thought when the Wix team told me about it. I didn’t even think it was a good idea to pack all that in one place.&lt;/p&gt;&lt;p&gt;But they did, and I was dead wrong. Codux is pretty awesome. And apparently, it will be even more awesome because the&lt;a href=&quot;https://www.codux.com/faq&quot;&gt; FAQ talks about a bunch of new features&lt;/a&gt; in the works, things like supporting full frameworks. The big one is an online version that will completely remove the need to set up development environments every time someone joins the team, or a stakeholder wants access to a working version of the app. Again, this is all in the works, but it goes to show how Codux is all about improving the &lt;em&gt;developer experience&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;And it’s not like you’re building a Wix site with it. Codux is its own thing — something that Wix built to get rid of their own pain points in the development process. It just so happens that their frustrations are the same that many of us in the community share, which makes Codux a legit consideration for any developer or team.&lt;/p&gt;&lt;p&gt;Oh, and it’s free. You can &lt;a href=&quot;https://codux.hopp.to/smashing&quot;&gt;download it right now&lt;/a&gt;, and it supports Windows, Mac, and Linux. In other words, you can give it a spin without buying into anything.&lt;/p&gt;</description><link>https://www.smashingmagazine.com/2023/06/codux-react-visual-editor-improves-developer-experience/</link><guid isPermaLink="false">https://www.smashingmagazine.com/2023/06/codux-react-visual-editor-improves-developer-experience/</guid><pubDate>Mon, 20 Jul 2026 16:00:00 GMT</pubDate><author>Geoff Graham</author><category>React</category><category>Tools</category><category>Workflow</category></item></channel></rss>