<?xml-stylesheet href="/pretty-feed-v2.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Trys Mudford's Blog</title>
    <link>https://www.trysmudford.com/tags/next.js/</link>
    <description>Posts, thoughts, links and photos from Trys</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 01 Oct 2024 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.trysmudford.com/blog/index.xml" rel="self" type="application/rss+xml"/>
    
    <item>
      <title>I wasted a day on CSS selector performance to make a website load 2ms faster</title>
      <link>https://www.trysmudford.com/blog/i-spent-a-day-making-the-website-go-2ms-faster/</link>
      <pubDate>Tue, 01 Oct 2024 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/i-spent-a-day-making-the-website-go-2ms-faster/</guid>
      <description><![CDATA[
<p>I&rsquo;ve been doing some performance tinkering at work. It&rsquo;s written in <a href="https://nextjs.org/">Next.js</a> and employs judicious use of <a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components">Server Components</a> to minimise client-side JS. I&rsquo;m really proud of it. It scores ~90 on Lighthouse mobile, which for Next.js isn&rsquo;t too bad. Sure, it pains me to see the tiny amount of client-side interaction resulting in such a large bundle, but it&rsquo;s the price we pay for developer convenience, apparently. But I digress.</p>
<p>I was showing some traces to a colleague, and we were looking intently at the considerable gap between the CSS being downloaded, and the website becoming visible.</p>
<p><img src="/images/blog/waterfall.png" alt="A trace from WebPageTest showing the browser main thread locking up before rendering"></p>
<p>My hunch was the bundle of <code>&lt;script&gt;</code>s than Next generates were being parsed at the point of CSS rendering, causing the main thread to choke. Incredibly, you <em>still</em> can&rsquo;t swap <code>async</code> to <code>defer</code> when using the the App Router (despite it being possible with their previous router), which would almost certainly close this gap. But I digress.</p>
<p>He found an option in the &ldquo;Performance&rdquo; tab in dev tools called &ldquo;Enable CSS selector stats (slow)&rdquo;, which analyses every CSS selector and pops them in a table.</p>
<p><img src="/images/blog/selectors.png" alt="The Selector Stats table in Chrome Dev Tools, showing the most &lsquo;expensive&rsquo; selectors"></p>
<p>In addition, the trace showed an alarming blob of purple where the main thread locked up under the label of &ldquo;Recalculate style&rdquo;. 270ms of delay just to calculate styles.</p>
<p>Goodness, I thought, that&rsquo;s some tasty low hanging fruit.</p>
<p>Now, I know in my heart that CSS selector performance isn&rsquo;t something to worry about. I&rsquo;ve heard it again and again that browsers are incredibly efficient at handling CSS selectors these days, and yet the graph didn&rsquo;t (appear to) lie; 270ms to recalculate styles on every DOM change and page load. Against my better judgement, on a day where I didn&rsquo;t have a tonne of other work to do, I felt I had to go deeper.</p>
<p>I was particularly drawn to the column &ldquo;Match attempts&rdquo; compared to &ldquo;Match count&rdquo;. Some selectors were attempting to match against <em>hundreds</em> of DOM nodes, but still end up not matching many/any elements. This fruit was looking tastier by the second.</p>
<p>The common culprits were any selector ending in <code>*, :last-child, :first-child, :nth-child</code>, which included the infamous <a href="https://alistapart.com/article/axiomatic-css-and-lobotomized-owls/">owl selector</a>, being used for <a href="https://piccalil.li/blog/my-favourite-3-lines-of-css/">flow spacing</a> amongst other things.</p>
<p>The penny suddenly dropped onto a piece of knowledge I&rsquo;d squirrelled away years ago. Selectors are read from right to left in CSS. This form of <a href="https://en.wikipedia.org/wiki/Bottom-up_parsing">bottom-up parsing</a> is more efficient for the browser when matching to DOM nodes. However, you can begin to see why a selector like <code>.parent &gt; * + *</code> is flagged as one of the more inefficient ones. Read backwards, the browser sees the selector as follows:</p>
<ol>
<li><code>*</code> - this matches <em>all</em> elements</li>
<li><code>* + *</code> - this narrows it to all elements that follow another (so almost all elements)</li>
<li><code>.parent &gt; * + *</code> - finally, this narrows it down to any direct children of <code>.parent</code> that follow another child</li>
</ol>
<p>So this selector <em>attempts</em> to match on <em>every</em> DOM node, but might only actually match five elements on the page.</p>
<p>Now, at this point, I should&rsquo;ve seen the column marked &lsquo;Elapsed (ms)&rsquo; with values such as <code>0.013</code> and thought, hmm, that doesn&rsquo;t seem very long, nor could all the selectors <em>really</em> add up to 270ms. But by now, the low hanging fruit looked far too tasty, and I had a hankering for some refactoring.</p>
<p>I took the table of selectors and calculated which selectors had the greatest difference between match attempts and elements found, and began working through those. A few were fed through our design system, but many came from my codebase.</p>
<p>Through much inversion of control and additional classes/data attributes, I updated almost all instances of <code>:last-child</code> and <code>*</code>. I made a release to our design system fixing the oh-so-inefficient selectors and marvelled at the improvement.</p>
<blockquote>
<p>Recalculate style /
Before: 270ms /
After: 40ms</p>
</blockquote>
<p>For a few minutes, I felt like Indiana Jones staring at the holy grail. 230ms of savings from some CSS selector changes. This felt <em>significant</em>. And yet, the niggling phrase of &ldquo;CSS selector efficiency is not something to worry about in 2024&rdquo; kept ringing through my head.</p>
<p>I ran the before/after through <a href="https://webpagetest.org/">WebPageTest</a> and to my surprise (and lack of surprise), nothing significant had changed.</p>
<p>It was at this point that the second penny dropped and I realised I&rsquo;d been duped by a graphing misunderstanding and my own enthusiasm for improving frontend performance. When clicking &ldquo;Enable CSS selector stats (slow)&rdquo;, I assumed it took longer to record the rendering process, but would provide the results as if they were not being profiled. I then realised that the graph was showing a big purple blob <em>because</em> I had the checkbox enabled. Suffice to say, I felt like a right doughnut.</p>
<p>I ran it all again without that option enabled and here are the fruits of my labour:</p>
<blockquote>
<p>Recalculate style /
Before: 11.95ms /
After: 10.23ms</p>
</blockquote>
<p>Whoop-de-doo</p>
<p>But if, somehow, you&rsquo;re browsing this site <em>with</em> CSS selector stats enabled, and find it to be a snappy experience, you&rsquo;re welcome.</p>
]]>
      </description>
    </item>
    
    <item>
      <title>Using &#39;use&#39; to stream deferred content</title>
      <link>https://www.trysmudford.com/blog/nextjs-use/</link>
      <pubDate>Fri, 30 Aug 2024 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/nextjs-use/</guid>
      <description><![CDATA[
<p>In the constant bid to improve page performance, I discovered <code>use</code>, an API recently shipped by React, and made available in Next.js. You&rsquo;d be forgiven for not immediately understanding its function, given the oh-so-descriptive name.</p>
<p><a href="https://react.dev/reference/react/use">use</a> lets you read the value of a promise (or context if you&rsquo;re that way inclined). It feels like a bit of syntactic sugar, to avoid writing <code>.then()</code> or <code>await</code> within your component, but I&rsquo;m sure there&rsquo;s more to it than that.</p>
<h2 id="when-is-use-useful">When is &lsquo;use&rsquo; useful?</h2>
<p><img src="/images/blog/use-carousel.jpg" alt=""></p>
<p>Take this carousel. Pretty standard stuff at the end of a product page. Definitely shouldn&rsquo;t be render-blocking, and a prime candidate for deferred loading. We could move the data request into a client-side handler and call <code>useEffect</code> to fetch the data on the client, but finding ways to avoid <code>useEffect</code> becomes a source of personal pride once you&rsquo;ve worked on a React project for any length of time. This approach has to wait for the whole page to hydrate before it can get to work loading the data.</p>
<p>Instead, we can still begin fetching the data on the server, but combine <code>&lt;Suspense&gt;</code> and <code>use</code> to handle the loading state before resolving it on the client.</p>
<h2 id="what-does-use-look-like">What does &lsquo;use&rsquo; look like?</h2>
<p>Before:</p>
<div class="highlight"><pre class="chroma"><code class="language-tsx" data-lang="tsx"><span class="kr">export</span> <span class="k">default</span> <span class="kr">async</span> <span class="kd">function</span> <span class="nx">Page() {</span>
    <span class="c1">// Wait for the data to load
</span><span class="c1"></span>    <span class="kr">const</span> <span class="nx">vehicles</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">getVehiclesFromDB</span><span class="p">();</span>

    <span class="k">return</span> <span class="p">(</span>
        <span class="p">&lt;</span><span class="nt">SimilarVehicles</span> <span class="na">vehicles</span><span class="o">=</span><span class="p">{</span><span class="nx">vehicles</span><span class="p">}</span> <span class="p">/&gt;</span>
    <span class="p">);</span>
<span class="p">}</span>

<span class="kr">export</span> <span class="kr">const</span> <span class="nx">SimilarVehicles</span>: <span class="kt">React.FC</span><span class="o">&lt;</span><span class="p">{</span>
    <span class="nx">vehicles</span>: <span class="kt">VehiclePreview</span><span class="p">[];</span>
<span class="p">}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">vehicles</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span>
        <span class="p">&lt;</span><span class="nt">section</span> <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="nx">cx</span><span class="p">(</span><span class="nx">styles</span><span class="p">.</span><span class="nx">section</span><span class="p">)}&gt;</span>
            <span class="p">{</span><span class="nx">vehicles</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">vehicle</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
                <span class="p">&lt;</span><span class="nt">VehicleCard</span>
                    <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">vehicle</span><span class="p">.</span><span class="nx">vehicleId</span><span class="p">}</span>
                    <span class="na">vehicle</span><span class="o">=</span><span class="p">{</span><span class="nx">vehicle</span><span class="p">}</span>
                <span class="p">/&gt;</span>
            <span class="p">))}</span>
        <span class="p">&lt;/</span><span class="nt">section</span><span class="p">&gt;</span>
    <span class="p">);</span>
<span class="p">};</span>
</code></pre></div><p>After:</p>
<div class="highlight"><pre class="chroma"><code class="language-tsx" data-lang="tsx"><span class="kr">export</span> <span class="k">default</span> <span class="kr">async</span> <span class="kd">function</span> <span class="nx">Page() {</span>
    <span class="c1">// Crack on rendering the page
</span><span class="c1"></span>    <span class="kr">const</span> <span class="nx">vehiclesPromise</span> <span class="o">=</span> <span class="nx">getVehiclesFromDB</span><span class="p">();</span>

    <span class="k">return</span> <span class="p">(</span>
        <span class="p">&lt;</span><span class="nt">Suspense</span> <span class="na">fallback</span><span class="o">=</span><span class="p">{&lt;</span><span class="nt">LoadingState</span> <span class="p">/&gt;}&gt;</span>
            <span class="p">&lt;</span><span class="nt">SimilarVehicles</span> <span class="na">vehiclesPromise</span><span class="o">=</span><span class="p">{</span><span class="nx">vehiclesPromise</span><span class="p">}</span> <span class="p">/&gt;</span>
        <span class="p">&lt;/</span><span class="nt">Suspense</span><span class="p">&gt;</span>
    <span class="p">);</span>
<span class="p">}</span>

<span class="kr">export</span> <span class="kr">const</span> <span class="nx">SimilarVehicles</span>: <span class="kt">React.FC</span><span class="o">&lt;</span><span class="p">{</span>
    <span class="nx">vehiclesPromise</span>: <span class="kt">Promise</span><span class="p">&lt;</span><span class="nt">VehiclePreview</span><span class="err">[]</span><span class="p">&gt;;</span>
<span class="p">}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">vehiclesPromise</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="c1">// Resolve the promise
</span><span class="c1"></span>    <span class="kr">const</span> <span class="nx">vehicles</span> <span class="o">=</span> <span class="nx">use</span><span class="p">(</span><span class="nx">vehiclesPromise</span><span class="p">);</span>

    <span class="k">return</span> <span class="p">(</span>
        <span class="p">&lt;</span><span class="nt">section</span> <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="nx">cx</span><span class="p">(</span><span class="nx">styles</span><span class="p">.</span><span class="nx">section</span><span class="p">)}&gt;</span>
            <span class="p">{</span><span class="nx">vehicles</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">vehicle</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
                <span class="p">&lt;</span><span class="nt">VehicleCard</span>
                    <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">vehicle</span><span class="p">.</span><span class="nx">vehicleId</span><span class="p">}</span>
                    <span class="na">vehicle</span><span class="o">=</span><span class="p">{</span><span class="nx">vehicle</span><span class="p">}</span>
                <span class="p">/&gt;</span>
            <span class="p">))}</span>
        <span class="p">&lt;/</span><span class="nt">section</span><span class="p">&gt;</span>
    <span class="p">);</span>
<span class="p">};</span>
</code></pre></div><h2 id="how-does-use-work">How does &lsquo;use&rsquo; work?</h2>
<p>By continuing to call the vehicles database from the server, we&rsquo;ve kept our precious server-side secrets safe. But by omitting <code>await</code>, we can crack on with streaming the page to the user much quicker than before.</p>
<p>React pops our fallback component in place, and continues to stream the page in the background until the database call resolves. At that point, the page connection closes and the component inside <code>&lt;Suspense&gt;</code> is rendered. <code>use</code> converts the promise over to our intended data format, and the component renders as expected.</p>
<p>You can even use <code>ErrorBoundary</code> to handle promise failures. Pretty neat.</p>
<h2 id="does-use-work-without-js">Does &lsquo;use&rsquo; work without JS?</h2>
<p>Sadly not. React needs JS at runtime to swap out the loading content with the deferred content. Non-JS users will be left with the loading component for infinity. So this isn&rsquo;t appropriate for core user journey functionality, or spider-friendly content.</p>
]]>
      </description>
    </item>
    
    <item>
      <title>Fixing Next.js&#39;s CSS order using cascade layers</title>
      <link>https://www.trysmudford.com/blog/next-js-css-order/</link>
      <pubDate>Fri, 23 Aug 2024 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/next-js-css-order/</guid>
      <description><![CDATA[
<p>Somewhere in the patch/minor releases between <code>14.0.2</code> and <code>14.2.0</code>, <a href="https://nextjs.org/">Next.js</a> made a <em>fundamental</em> change to the way it orders CSS. More problematically, it also introduced a discrepancy between development and production builds, where the CSS chunks would be in entirely different orders between the environments. Incredibly, this is documented, and intentional:</p>
<blockquote class="longquote">CSS ordering can behave differently in development mode, always ensure to check the build (next build) to verify the final CSS order.
<cite><a href="https://nextjs.org/docs/app/building-your-application/styling/css">Next.js Documentation</a></cite>
  
</blockquote>

<p>Seems like recipe for disaster, right?</p>
<p>Theoretically, this is controllable by the developer:</p>
<blockquote class="longquote">The CSS order is determined by the order in which you import the stylesheets into your application code.
<cite><a href="https://nextjs.org/docs/app/building-your-application/styling/css">Next.js Documentation</a></cite>
  
</blockquote>

<p>But in the real world, it&rsquo;s not the case. In development mode, CSS files are named <code>layout.css</code> and <code>page.css</code> etc. and are written in the HTML in that order. However, in a production build, the CSS is outputted in the totally opposite order with a different naming convention.</p>
<p>The issue is, in circumstances where specificity is matched between selectors, the source order of the CSS takes precedence. Not a problem if it&rsquo;s consistent, but very much a problem if it changes depending on your environment.</p>
<p>Their &ldquo;solution&rdquo; also flies in the face of the web platform:</p>
<blockquote>
<p>Prefer CSS Modules over global styles</p>
</blockquote>
<p>No thanks.</p>
<p>How this is an acceptable &lsquo;feature&rsquo; is beyond me. To say that the &ldquo;order is determined by the order you import the stylesheets&rdquo;, only holds water if Next consistently renders page → layout, or layout → page. Anyway, Friday afternoon rant over.</p>
<h2 id="how-do-you-fix-this-mess">How do you fix this mess?</h2>
<p>I paired with my good friend and colleague <a href="https://szymonpajka.com/">Szymon Pajka</a> on this problem, and after much digging into the import order problem and trialling <code>:where</code>, he suggested we look at <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers">CSS Cascade Layers</a>.</p>
<p>Cascade Layers allow developers to create &amp; control multiple &lsquo;layers&rsquo; of cascade/specificity. In this example below, we can write our reset styles within a <code>@layer</code> block, and then choose the have them calculated before, or after the theme/components etc. If this CSS was then chunked and split up by a bulid process, it wouldn&rsquo;t matter which order the files were loaded in, they&rsquo;d always follow the format of <code>reset, theme, components</code>.</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">@</span><span class="k">layer</span> <span class="nt">reset</span><span class="o">,</span> <span class="nt">theme</span><span class="o">,</span> <span class="nt">components</span><span class="p">;</span>

<span class="p">@</span><span class="k">layer</span> <span class="nt">reset</span> <span class="p">{</span>
    <span class="c">/* Reset styles */</span>
<span class="p">}</span>

<span class="p">@</span><span class="k">layer</span> <span class="nt">theme</span> <span class="p">{</span>
    <span class="c">/* Theme styles */</span>
<span class="p">}</span>

<span class="p">@</span><span class="k">layer</span> <span class="nt">components</span> <span class="p">{</span>
    <span class="c">/* Component styles */</span>
<span class="p">}</span>
</code></pre></div><p>Any unlayered styles (existing styles without a wrapping layer block), will automatically &lsquo;beat&rsquo; any cascade layers you&rsquo;ve set up. So, rather than wrapping every component in: <code>@layer component {}</code>, we can leave them as unlayered, knowing they&rsquo;ll beat our reset, theming, design system CSS files.</p>
<p>Amazingly, this also works in SCSS (we did <em>not</em> think it would):</p>
<div class="highlight"><pre class="chroma"><code class="language-scss" data-lang="scss"><span class="k">@layer</span> <span class="nt">reset</span><span class="o">,</span> <span class="nt">designSystem</span><span class="o">,</span> <span class="nt">theme</span><span class="o">,</span> <span class="nt">utopia</span><span class="o">,</span> <span class="nt">utilities</span><span class="p">;</span>

<span class="k">@layer</span> <span class="nt">reset</span> <span class="p">{</span>
    <span class="k">@import</span> <span class="s1">&#39;./reset.scss&#39;</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@layer</span> <span class="nt">designSystem</span> <span class="p">{</span>
    <span class="k">@import</span> <span class="s1">&#39;@motorway-design-system/src/styles/reset.scss&#39;</span><span class="p">;</span>
    <span class="k">@import</span> <span class="s1">&#39;@motorway-design-system/src/styles/base.scss&#39;</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@layer</span> <span class="nt">theme</span> <span class="p">{</span>
    <span class="k">@import</span> <span class="s1">&#39;@motorway-design-system/src/styles/theme/light.scss&#39;</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@layer</span> <span class="nt">utopia</span> <span class="p">{</span>
    <span class="k">@import</span> <span class="s1">&#39;./utopia.scss&#39;</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@layer</span> <span class="nt">utilities</span> <span class="p">{</span>
    <span class="k">@import</span> <span class="s1">&#39;./utilities&#39;</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>Wrapping the imports keeps the control for these layers in one file, making adoption of the system more straightforward for new engineers, and most importantly, consistent across environments.</p>
<p>Thanks again to <a href="https://szymonpajka.com/">Szymon</a> for his help pairing on this. There&rsquo;s little better than writing CSS with someone else who adores and appreciates the language in the same way you do. 🫶</p>
]]>
      </description>
    </item>
    
    <item>
      <title>Next.js edge logging with Pino/Datadog</title>
      <link>https://www.trysmudford.com/blog/nextjs-edge-logging/</link>
      <pubDate>Tue, 30 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/nextjs-edge-logging/</guid>
      <description><![CDATA[
<p>I encountered an issue with the potent combo of: Next.js App Router, Edge runtime, Server Actions, Pino and Datadog. After much googling, I couldn&rsquo;t find a solution to this problem, hence this keyword-stuffed blog post for future me/others.</p>
<p>First up: logs were appearing in Datadog over multiple lines, which broke the <code>stdout</code> formatting rendering the logs unusable. Note, this only happened with the Edge runtime, not the standard Node variation. To solve this, I added a new section to the standard pino config:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">isServer</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nb">window</span> <span class="o">===</span> <span class="s1">&#39;undefined&#39;</span><span class="p">;</span>
<span class="kr">const</span> <span class="nx">isNextEdgeRuntime</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_RUNTIME</span> <span class="o">===</span> <span class="s1">&#39;edge&#39;</span><span class="p">;</span>

<span class="kr">export</span> <span class="kr">const</span> <span class="nx">config</span> <span class="o">=</span> <span class="p">{</span>	
    <span class="c1">// ...standard pino config
</span><span class="c1"></span>    <span class="p">...(</span><span class="nx">isServer</span> <span class="o">&amp;&amp;</span> <span class="nx">isNextEdgeRuntime</span> <span class="o">&amp;&amp;</span> <span class="p">{</span>
        <span class="nx">browser</span><span class="o">:</span> <span class="p">{</span>
            <span class="nx">write</span><span class="o">:</span> <span class="p">{</span>
                <span class="nx">critical</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">debug</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">error</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">fatal</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">info</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">trace</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">warn</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
            <span class="p">},</span>
        <span class="p">},</span>
    <span class="p">}),</span>
<span class="p">};</span>

<span class="kr">const</span> <span class="nx">loggerInstance</span> <span class="o">=</span> <span class="nx">pino</span><span class="p">(</span><span class="nx">config</span><span class="p">);</span>
</code></pre></div><p>However, this created a second problem. Despite using <code>console.error</code>, errors were still being reported to Datadog as <code>info</code>, which then failed to trigger alerts and monitors. Not great. The solution was to pass a <code>level</code> parameter into the object; pre-stringification, on the <code>warn</code> and <code>error</code> methods.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">isServer</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nb">window</span> <span class="o">===</span> <span class="s1">&#39;undefined&#39;</span><span class="p">;</span>
<span class="kr">const</span> <span class="nx">isNextEdgeRuntime</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_RUNTIME</span> <span class="o">===</span> <span class="s1">&#39;edge&#39;</span><span class="p">;</span>

<span class="kr">export</span> <span class="kr">const</span> <span class="nx">config</span> <span class="o">=</span> <span class="p">{</span>	
    <span class="c1">// ...standard pino config
</span><span class="c1"></span>    <span class="p">...(</span><span class="nx">isServer</span> <span class="o">&amp;&amp;</span> <span class="nx">isNextEdgeRuntime</span> <span class="o">&amp;&amp;</span> <span class="p">{</span>
        <span class="nx">browser</span><span class="o">:</span> <span class="p">{</span>
            <span class="nx">write</span><span class="o">:</span> <span class="p">{</span>
                <span class="nx">critical</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">debug</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">error</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="p">...</span><span class="nx">o</span><span class="p">,</span> <span class="nx">level</span><span class="o">:</span> <span class="s1">&#39;error&#39;</span> <span class="p">})),</span>
                <span class="nx">fatal</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">info</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">trace</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">o</span><span class="p">)),</span>
                <span class="nx">warn</span><span class="o">:</span> <span class="p">(</span><span class="nx">o</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="p">...</span><span class="nx">o</span><span class="p">,</span> <span class="nx">level</span><span class="o">:</span> <span class="s1">&#39;warn&#39;</span> <span class="p">})),</span>
            <span class="p">},</span>
        <span class="p">},</span>
    <span class="p">}),</span>
<span class="p">};</span>

<span class="kr">const</span> <span class="nx">loggerInstance</span> <span class="o">=</span> <span class="nx">pino</span><span class="p">(</span><span class="nx">config</span><span class="p">);</span>
</code></pre></div><p>This tells Datadog to treat the offending logs as errors/warnings, which can then be used to trigger alerts etc.</p>
]]>
      </description>
    </item>
    
    <item>
      <title>Next.js Server Actions</title>
      <link>https://www.trysmudford.com/blog/next-server-actions/</link>
      <pubDate>Tue, 20 Feb 2024 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/next-server-actions/</guid>
      <description><![CDATA[
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 1 - Slide 1.jpg"
		alt="Title slide of the talk: Lessons from using Next.js Server Actions: The good, the bad, and the plain undocumented"
		loading="lazy"
	/>
	<figcaption>
		<p>This is a (rough) transcript of a talk I gave at work at the end of last year, entitled: "Lessons from using Next.js Server Actions: The good, the bad, and the plain undocumented".</p>
		<p>We'd just launched a greenfield product that used Server Actions and this talk was an opportunity to share the approach, top tips, and gotchas that I'd learned along the way.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 1 - Slide 3.jpg"
		alt="What are server actions: Stable in Next 14"
		loading="lazy"
	/>
	<figcaption>
		<p>Server Actions were recently released to stable in Next.js 14.0.0, although they've been developed behind an experimental feature flag for a number of months.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 1 - Slide 4.jpg"
		alt="Built into React Canary"
		loading="lazy"
	/>
	<figcaption>
		<p>Although released as part of Next.js, they are built into React Canary and utilise several 'core' hooks. Although, as you might've read recently, the gap between React Canary and Vercel is hazy at best.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 1 - Slide 6.jpg"
		alt="Server actions only ever run on the server"
		loading="lazy"
	/>
	<figcaption>
		<p>But what <em>exactly</em> are they? They are asynchronous functions that <strong>only</strong> ever run on the server. Whereas you may currently write what <em>looks</em> like a server function to get some initial page state, you'll find they're regularly called by the client-side as the SPA mode of Next.js takes over.</p>
		<p>Server Actions will <strong>only</strong> run on the server, which brings a tonne of security, performance and accessibility benefits. Next.js provides the tooling to make your application continue to <em>feel</em> like it's making client-side requests, but under the hood, there's a load more native, web-standard plumbing going on.</p>
		<p>Think of them as the perfect backend-for-frontend starting point.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 1 - Slide 7.jpg"
		alt="Typically receive a FormData class"
		loading="lazy"
	/>
	<figcaption>
		<p>They typically receive a <code>FormData</code> class as an input parameter&hellip;</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 1 - Slide 8.jpg"
		alt="Return object/redirects"
		loading="lazy"
	/>
	<figcaption>
		<p>&hellip;and they usually return serializable objects or redirects.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 1 - Slide 9.jpg"
		alt="The three methods to invoke server actions:"
		loading="lazy"
	/>
	<figcaption>
		<p>They can be invoked in three ways:</p>
		<ul>
			<li>An <code>action</code> attribute on a <code>form</code></li>
			<li>A <code>formAction</code> attribute on a <code>button</code></li>
			<li>Within a <code>startTransition</code> method, returned by the new <code>useTransition</code> hook</li>
		</ul>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 1 - Slide 10.jpg"
		alt="Server Components + Server Actions"
		loading="lazy"
	/>
	<figcaption>
		<p>They play really nicely with Server Components. When understood and used correctly, they beautifully separate the concerns of your HTML rendering path, client-side interactivity, and API requests. They encourage the mindset of responsible server-side rendering.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 2.jpg"
		alt="Example no. 1 - the cookie bar"
		loading="lazy"
	/>
	<figcaption>
		<p>Let's use an example of a common pattern to explore how Server Actions work. Everyone's favourite feature of the web, the humble cookie bar.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 3.jpg"
		alt="A code snippet of a client-side component that shows/hides a cookie bar"
		loading="lazy"
	/>
	<figcaption>
		<p>Here's a pseudo-code snippet of a pretty normal implementation of a Cookie bar. Let's walk through it. We pull in <code>useState</code> and a third-party client-side cookie library because it's 2023 and we still need a decent interface for reading/setting cookies. We set up some state for whether the cookie has been set or not by reading in the cookie. Now we have two sources of truth&hellip;</p>
		<p>Then we create a method to update our state <strong>and</strong> update the cookie. If the bar has been accepted, we return nothing and if not, we return some markup that renders the bar and calls our method.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 4.jpg"
		alt="A flow diagram of the client-side cookie bar implementation"
		loading="lazy"
	/>
	<figcaption>
		<p>Let's plot this rendering timeline onto a flow-chart.</p>
		<p>The thing about React components is it's easy to forget where they start; rendered on a server. So the request comes into the server and because we're using a client-side cookie library, we don't know whether the cookie has been set or not, so we return nothing. The response gets to the browser and we hit <abbr title="Time to first byte">(TTFB)</abbr>.</p>
		<p>As the page loads, we also get our <abbr title="Largest contentful paint">(LCP)</abbr> before the JS is parsed, hydrated and some time later, is interactive. This marks <abbr title="Time to interactive">(TTI)</abbr>. Only now we can re-check whether the cookie is set, which may or may not show the cookie bar. If it does, we're in danger of triggering a <abbr title="Cumulative layout shift">(CLS)</abbr>. Not good.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 5.jpg"
		alt="Another flow diagram of the client-side cookie bar implementation"
		loading="lazy"
	/>
	<figcaption>
		<p>When the "Accept cookies" button is clicked, everything now happens on the client side. We set our cookie and state, and React re-renders the component, showing/hiding accordingly.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 6.jpg"
		alt="Code snippet of a server action implementation of a cookie bar"
		loading="lazy"
	/>
	<figcaption>
		<p>Here's the equivalent using Server Actions. We pull in the <code>cookies</code> helper from within Next.js, and import a server action that we'll look at in a second. Next, we read directly from the helper, returning <code>null</code> if the cookie is set. Now we return a <code>form</code> with the <code>action</code> attribute set to our imported action. Traditionally, you'd put a string in an <code>action</code> attribute, but under the hood, Next resolves and routes this action so it works without JS. Rather than <code>type="button"</code>, we can use <code>type="submit"</code>. Note, we've not written any client-side code, nor a single <code>event.preventDefault()</code> despite the form.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 7.jpg"
		alt="Code snippet of the cookie bar server action"
		loading="lazy"
	/>
	<figcaption>
		<p>And here's the first Server Action of the day. It's in a file with a <code>'use&nbsp;server'</code> directive. This is crucial, and one of the hallmarks of a Server Action. It tells Next to <strong>never</strong> send this file to the client-side or bundle it into a script so we're safe to work with API secrets and import weightier libraries without penalising our users.</p>
		<p>We import the very same <code>cookies</code> helper as we did in our component and do one thing in the action: set the cookie. Nice and simple.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 8.jpg"
		alt="Rendering flow chart of the server action cookie example"
		loading="lazy"
	/>
	<figcaption>
		<p>The rendering flow chart for this is a dream. The request comes in, with all the lovely headers including any cookies currently set. We either return the markup for the cookie bar, or we don't, but this is all happening on the server. When the response is sent to the client, there's no risk of <abbr title="Cumulative layout shift">(CLS)</abbr>.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 9.jpg"
		alt="Interaction flow chart of the server action cookie example"
		loading="lazy"
	/>
	<figcaption>
		<p>This time, when the button is clicked, a request is sent to the server (via an internal <code>fetch</code> call that Next handles for us). The cookie is checked on the server and the new cookie state is determined. Next then re-renders the component showing/hiding the bar accordingly. From a user's perspective, it's the same outcome, but we've moved the work up to the server. This is a key theme of Server Actions.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 10.jpg"
		alt="Example no. 1.1 - Cookie bar withough Javascript. What happens when JS fails?"
		loading="lazy"
	/>
	<figcaption>
		<p>Now you may be thinking, why does this matter? Well, let's explore the same component with JS disabled for the inevitable moment when JS fails on your site.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 11.jpg"
		alt="Flow chart of the client-side cookie bar with no JS"
		loading="lazy"
	/>
	<figcaption>
		<p>Here's the client-side version again, let's click the button&hellip;</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 12.jpg"
		alt="Everything is broken after the button is clicked."
		loading="lazy"
	/>
	<figcaption>
		<p>&hellip;sad times. It ded.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 13.jpg"
		alt="Flow chart for the Server Action version"
		loading="lazy"
	/>
	<figcaption>
		<p>Now, let's compare it to the Server Action version. You click "accept" and perform a traditional <code>POST</code> request to the server. The cookie is set and the new markup is shown without the cookie bar!</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 14.jpg"
		alt="Benefit no. 1 - No client-side JS required"
		loading="lazy"
	/>
	<figcaption>
		<p>You might not <em>think</em> you care about user's without JS, but <q>All your users are non-JS while they're downloading your JS</q> - <cite>Jake Archibald</cite>.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 15.jpg"
		alt="Benefit no. 2 - Interactive before and during hydration"
		loading="lazy"
	/>
	<figcaption>
		<p>We've all been on pages where the page <em>looks</em> interactive but you're still waiting for the behemoth bundle to parse and hydrate. If your users interact with a Server Action <em>before</em> JS is ready, they'll just follow the non-JS path, and be able to get on with their task quicker than ever.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 16.jpg"
		alt="Gotcha no. 1 - Cookies can only be set in Server Actions & Route handlers"
		loading="lazy"
	/>
	<figcaption>
		<p>Gotcha #1 - as you're interacting with actual request headers, cookies can only be set within Server Actions and route handlers. Once the HTML is sent to the browser, the setting stops.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 17.jpg"
		alt="Gotcha no. 2 - setting cookies in middleware requires cloning headers"
		loading="lazy"
	/>
	<figcaption>
		<p>Gotcha #2 - you can also <em>technically</em> set cookies in Next.js middleware but it requires you to copy headers from the response back to the request or you'll end up in a state where your cookies are a page behind you. This is one of those undocumented features that is only found at the end of a long GitHub issue.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 2 - Slide 18.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Tip #1 - <code>&lt;noscript&gt;</code> and <code>type="hidden"</code> inputs are surprisingly useful with Server Actions.</p>
		<p>Only non-JS users will send the <code>no-js</code> parameter, allowing us handle the two user groups independently.<p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 1.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Onto the next example, a login form. I'll use a simpler "magic link" form to keep things simple.<p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 2.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>This is your common or garden React form. Gosh it's something to behold. Let's work through it. After importing various components, it's time to create some reactive state to hold our email address. Then we create a method to handle the form submission. We send the email to an API and if it's successful, route the user on the client side. We'll ignore error handling for now, but that's another load of complexity to add in.<p>
		<p>Then, <em>on every key press</em>, we set the entered text into state and re-render the whole component to pass the value back to the input you just typed in. This is the two-way data binding we've all come to accept, but it's actually bonkers when you stop and think about it.</p>
		<p>This component covers rendering, API requests and user interactions in the space of 80 lines.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 3.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Let's look at a Server Action equivalent form. The difference is staggering.</p>
		<p>To begin with, it's so focused. It's only concern is providing the markup to render the form. There's no state management per field, no key press events, and no inline functions. Just HTML (well, JSX, but close enough in this case). It uses web standards that have been around for decades: the <code>name</code> attribute, the form <code>action</code>. These things are battle-tested and <em>just work</em> without a tonne of extra complexity.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 4.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>The Server Action doesn't need to be complicated either. Next passes in the <code>FormData</code> instance, so we can easily extract the email using the same <code>name</code> attribute, and pass that to our handler. When that's resolved, we redirect the user to the success page.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 5.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Here's the flow for the Server Action with JS enabled. The form submits, Next sends the data to the Server Action via a POST request with <code>fetch</code>, and on success, it redirects the users through the client-side router.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 6.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Turn off JS and the result is <em>exactly</em> the same. Instead of an AJAX call, a classic POST request happens in the browser, the Server Action handles the login flow, and redirects the user. I would bet money that users would be none the wiser if JS was removed from this page.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 8.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>And for posterity's sake, here's the client-side version with JS turned off. The cookie bar example may have been contrived, but a login form is a primary action for many sites. Through progressive enhancement, Server Actions provide a way for <em>all</em> users to complete primary tasks on a site in all circumstances.</p>
		<p>And moreover, these components are simpler to write and smaller to serve than their client-side counterparts.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 9.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>The reason this all works so neatly, is because these API's follow the <a href="https://frankchimero.com/blog/2015/the-webs-grain/">grain of the web</a>. <code>FormData</code>, for example, is a wonderful web native interface that handles forms incredibly elegantly. It doesn't cost the user anything to import, it's well documented and most importantly, it isn't yet another NPM library negatively effecting your project's maintainability.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 10.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Another weird gotcha - Redirects and <code>try/catch</code> don't work well together in Server Actions&hellip;</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 11.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>If you redirect within the <code>try</code> block, it will throw an error. Use <code>try/catch</code> around asynchronous or volatile calls, then redirect afterwards.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 3 - Slide 12.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Server Actions make it trivial to use API keys/secrets in a responsible manner. Next already requires environment variables you intent to ship to the client-side to be prefixed with <code>NEXT_PUBLIC_</code>, which means any other variable is <em>only</em> usable in a Server Action file.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 1.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Let's carry on from the previous login form example and add some error handling.</p>
		<p>It's important to sanitize our inputs and validate data on the server, not just on the client. With all the data automatically send to the server, this becomes the default pattern using Server Actions.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 2.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Up until now, all our Server Action forms have been server components, and haven't required any client-side state or hooks. That's going to change in this example. Although it's possible to handle error checking with server components, we're going to use the new <code>useFormState</code> hook imported not from Next, but from <code>react-dom</code>. You need to mark the file with a <code>'use&nbsp;client'</code> directive.</p>
		<p>This hook takes in the Server Action import, and some default state in the shape of an object. It returns an array with a the reactive state, and a new embellished Server Action. These can then be applied to the form and used to render error messages.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 3.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Looking at the updated Server Action, you'll see we now return a JSON object if there's an error generated in the <code>try/catch</code> block. This object will be returned to the client and the form updated to show the error message.</p>
		<p>The <code>try/catch</code> pattern works really nicely here, allowing us to liberally throw errors in validation libraries, or within the login flow, and handle them in a consistent way.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 4.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Here's the flow chart of what's going on, showing the early JSON response if the error is present, and the redirect on success&hellip;</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 5.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>&hellip;and without JS, it works very similarly&hellip;</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 6.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>&hellip;but there is a gotcha here. You'll probably remember this classic error message. It appears if you try and refresh a page that has been rendered directly from a POST request. Depending on the task, it could be pretty bad news if a user can resubmit the same form multiple times.</p>
		<p>Non-js users can get into this state when we return an error response message. That's not a big deal, but could be if we used <code>useFormState</code> to handle a success state, rather than redirecting the user onto another page.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 7.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>We can keep things idempotent with the POST → Redirect → GET pattern. This approach ensures that redirects are <a href="https://www.w3.org/TR/design-principles/#safe-to-browse">safe</a> and without side-effects.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 8.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Time for another gotcha. The Edge runtime might sound like a U2 effects pedal, but it's the name given to the flavour of limited JS used on CDN servers. At build-time, Next breaks down an application into a bunch of smaller functions that can be distributed to 'Edge' servers and run from there, rather than on a single, central server. This is all very clever and helps with performance, but it's also required to opt-into for Server Actions, specifically if you want to support non-JS requests.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 9.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>These two, barely documented exports need to be set within the page importing the server action. You won't notice an issue when running the application in development mode, but without <code>export const runtime = 'edge';</code>, non-JS POST requests will just hang indefinently.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 10.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>A popular JS validation library is Zod. It's very good. It's also recommended on the Next.js Server Actions documentation. It has a <code>FormData</code> plugin designed to take in a POST request and validate accordingly. Unfortunately, that plugin doesn't work with the Edge runtime and thus can't be used with Server Actions if you want to support non-JS users. I'm not sure if anyone at Vercel has noticed.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 11.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>We use DataDog for logging, monitoring and alerting. But again, there's a gotchat here. I was finding our nice, tidy logs were being desecrated by nasty multi-line logs, but only on some requests. You guessed it, it's our friend the Edge runtime again.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 4 - Slide 12.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Within our Pino logger, I had to take charge of the logging, but only for requests currently using the Edge runtime in production.</p>
		<p>This has formatted logs to a single line, but some events continue to appear as <code>info</code>, despite being classified as <code>error</code>.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 5 - Slide 1.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>The final update to the login form example involves adding a loading state.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 5 - Slide 2.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p><code>useFormStatus</code> from <code>react-dom</code> is the hook needed for this feature. It returns an object with a <code>pending</code> property, which updates when the form is being submitted. We can use this to show a loading/disabled state on the submit button.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 5 - Slide 3.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>Big gotcha for this - <code>useFormStatus</code> must be used in a child component to the form. It <strong>cannot</strong> be used directly in the parent component.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 6 - Slide 1.jpg"
		alt=""
		loading="lazy"
	/>
	<figcaption>
		<p>To finish, I'll run through a few quickfire tips.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 6 - Slide 2.jpg"
		alt="useOptimistic can update the UI before the server action completes - perfect for event driven architectures"
		loading="lazy"
	/>
	<figcaption>
		<p><code>useOptimistic</code> is a hook provided to update the UI before the Server Action has completed. The classic example for this is an instant messaging application. <a href="https://lawsofux.com/jakobs-law/">Jakob's law</a> suggests that you expect to be able to hit enter on a message and it will appear in the message list instantly, rather than waiting for the server to respond positively that the message has been sent. This hook provides some plumbing to make this pattern simpler to implement.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 6 - Slide 3.jpg"
		alt="Server Actions can GET data on page load in a server component"
		loading="lazy"
	/>
	<figcaption>
		<p>Server Actions can be used to fetch data on page/layout load. This is a instance where an action won't receive a FormData object, and can be supplied with whatever parameters you'd like. You're also likely to return an object, rather than a redirect in these instances.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 6 - Slide 4.jpg"
		alt="Server actions only every POST data from forms"
		loading="lazy"
	/>
	<figcaption>
		<p>However, you cannot run a Server Action from a form as a GET request, only a POST. In fact, Next will just steamroll your <code>method</code> parameter and force a POST on there.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 6 - Slide 5.jpg"
		alt="revalidatePath - bust the cache on a route before redirecting"
		loading="lazy"
	/>
	<figcaption>
		<p><abbr title="Create. Read. Update. Delete.">CRUD</abbr> updates are a pretty common usecase for Server Actions. So when you create/update/delete your todo/comment/post etc, you're probably going to want to refresh the list of todos/comments/posts to reflect the change.</p>
		<p><code>revalidatePath</code> accepts a relative URL string where it will flush the cache for any data persisted on that route. When you next visit the route, it will refetch the latest copy of the data, rather than respond with an earlier cached copy.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 6 - Slide 6.jpg"
		alt="revalidateTag - mark up your fetch calls with tags and call this in a server action to bust the cache"
		loading="lazy"
	/>
	<figcaption>
		<p>If you're using the monkey-patched <code>fetch</code> implementation within Next, you're going to want to swat up on <a href="https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating">caching</a> within the framework. Next now caches <em>everything</em>. If you're dealing with user-specific data, you're going to want to tag up your <code>fetch</code> calls to avoid the risk of cross-pollinating another user's data. I like to include a user ID within the tag to ensure they're unique per user.</p>
		<p><code>revalidateTag</code> accepts one of those tags and busts the cache for it.</p>
	</figcaption>
</figure>
<figure>
	<img
		width="1152 "
		height="648"
		src="/images/blog/next-server-actions/Section 7 - Slide 1.jpg"
		alt="Thanks for listening"
		loading="lazy"
	/>
	<figcaption>
		<p>Thanks for <del>listening</del> reading!</p>
	</figcaption>
</figure>
]]>
      </description>
    </item>
    
    <item>
      <title>Declarative Tracking</title>
      <link>https://www.trysmudford.com/blog/declarative-tracking/</link>
      <pubDate>Fri, 08 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://www.trysmudford.com/blog/declarative-tracking/</guid>
      <description><![CDATA[
<p>On &lsquo;reactive&rsquo; projects, it&rsquo;s pretty common to attach event tracking to buttons/forms etc using inline <code>onClick</code>/<code>onSubmit</code> handlers. It usually starts with the harmless &ldquo;can we track how many times this button is pressed&rdquo; request, and without thinking, you attach a handler that looks something like this: <code>onClick={() =&gt; ga('event'))</code>. This continues for many months &amp; years until your codebase is littered with these inline tracking functions.</p>
<p>I&rsquo;ve had the opportunity to work on a greenfield project recently and was able to trial an alternative approach. A more declarative approach. I&rsquo;m certainly not the first person to think of this, but I wanted to share my experience. It looks a bit like this:</p>
<div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">button</span>
    <span class="na">type</span><span class="o">=</span><span class="s">&#34;button&#34;</span>
    <span class="na">data-track</span><span class="o">=</span><span class="s">&#34;cta&#34;</span>
    <span class="na">data-track-category</span><span class="o">=</span><span class="s">&#34;homepage&#34;</span>
    <span class="na">data-track-name</span><span class="o">=</span><span class="s">&#34;heroCta&#34;</span>
<span class="p">&gt;</span>

<span class="p">&lt;</span><span class="nt">form</span> <span class="na">data-track</span><span class="o">=</span><span class="s">&#34;submit&#34;</span> <span class="na">data-track-category</span><span class="o">=</span><span class="s">&#34;createAccount&#34;</span><span class="p">&gt;</span>

<span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;...&#34;</span> <span class="na">data-track</span><span class="o">=</span><span class="s">&#34;click&#34;</span><span class="p">&gt;</span>
</code></pre></div><p>Any anchor, form, or button can be marked up with <code>data-</code> attributes that describe the event. Rather than attach a handler to each event, we register one global <code>click</code> and <code>submit</code> event handler on the <code>document</code>. Thanks to event bubbling and <code>event.target.closest</code>, we can easily filter out elements that don&rsquo;t have the <code>data-track</code> attribute.</p>
<h2 id="benefits">Benefits</h2>
<ul>
<li>This is a substantially more efficient pattern to attaching events to each element, particularly in a re-render heavy framework like React</li>
<li>Abstracting this functionality allows us to declare less per element. For example, we can read <code>event.target.textContent</code>, rather than re-supply the label on every call-to-action button. We can automatically pull in the <code>href</code> on an anchor once, rather than duplicate the URL in an inline function</li>
<li>There&rsquo;s less code to write per component</li>
<li>We can add new tracking providers without touching every component</li>
<li>Common values can be set globally, rather than per component. For example, we may wish to pass the current page URL for all events</li>
<li>This all started in a Next.js project. I&rsquo;d painstakingly constructed my server/client components to minimise the number of client components. And that would&rsquo;ve been entirely obliterated with inline tracking functions, that have to exist in a <code>'use client'</code> file. This approach allowed me to keep as many components as pure server components</li>
</ul>
<h2 id="drawbacks">Drawbacks</h2>
<ul>
<li>It&rsquo;s extra HTML to send over the wire - sending markup for something that is entirely client-side isn&rsquo;t ideal</li>
<li>Everything that&rsquo;s tracked is easier to see in dev-tools - it&rsquo;s not as if inline functions are hidden, but they&rsquo;re certainly less obvious. This could be a positive; if you&rsquo;re not comfortable with someone seeing what&rsquo;s being tracked, then maybe it shouldn&rsquo;t be tracked? The tracking sunlight test</li>
<li>Non-primitives can&rsquo;t easily be tracked without stringifying JSON</li>
</ul>
<h2 id="abstracting-the-tracking">Abstracting the tracking</h2>
<p>Rather than calling, say, Google Analytics directly in the global handler, I&rsquo;ve taken to dispatching a <code>CustomEvent</code> in a consistent format that can be listened to by any number of handlers. For the few times you may need to track something outside of the global handler, you can dispatch a <code>CustomEvent</code> with the same format and it&rsquo;ll be fed to all the handlers.</p>
<h2 id="implementation">Implementation</h2>
<p>This is a rough implementation of the concept - I can&rsquo;t guarantee perfection but it&rsquo;s broadly what I&rsquo;ve been using:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kr">const</span> <span class="nx">TRACKING_EVENT</span> <span class="o">=</span> <span class="s1">&#39;namespaced:tracking&#39;</span><span class="p">;</span>

<span class="c1">// Fire events to all tracking providers
</span><span class="c1"></span><span class="kr">const</span> <span class="nx">trackEvent</span> <span class="o">=</span> <span class="p">(</span><span class="nx">eventAction</span><span class="p">,</span> <span class="nx">trackingDetail</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">dispatchEvent</span><span class="p">(</span>
        <span class="k">new</span> <span class="nx">CustomEvent</span><span class="p">(</span><span class="nx">TRACKING_EVENT</span><span class="p">,</span> <span class="p">{</span>
            <span class="nx">detail</span><span class="o">:</span> <span class="p">{</span>
                <span class="nx">eventAction</span><span class="p">,</span>
                <span class="nx">trackingDetail</span><span class="p">,</span>
            <span class="p">},</span>
        <span class="p">}),</span>
    <span class="p">);</span>
<span class="p">};</span>

<span class="kr">const</span> <span class="nx">globalTrackingHandler</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">event</span><span class="o">:</span> <span class="nx">Event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="c1">// Detect if we&#39;re on a trackable element
</span><span class="c1"></span>    <span class="kr">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span> <span class="nx">as</span> <span class="nx">HTMLElement</span><span class="p">).</span><span class="nx">closest</span><span class="p">(</span><span class="s1">&#39;[data-track]&#39;</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">target</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>

    <span class="c1">// Prevent click events from firing on trackable forms
</span><span class="c1"></span>    <span class="kr">const</span> <span class="nx">action</span> <span class="o">=</span> <span class="nx">target</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">track</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">action</span> <span class="o">===</span> <span class="s1">&#39;submit&#39;</span> <span class="o">&amp;&amp;</span> <span class="nx">event</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="s1">&#39;click&#39;</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>

    <span class="c1">// Extract data from the trackable element
</span><span class="c1"></span>    <span class="kr">const</span> <span class="nx">category</span> <span class="o">=</span> <span class="nx">target</span><span class="p">.</span><span class="nx">dataset</span><span class="p">[</span><span class="s1">&#39;track-category&#39;</span><span class="p">];</span>
    <span class="kr">const</span> <span class="nx">label</span> <span class="o">=</span> <span class="nx">target</span><span class="p">.</span><span class="nx">dataset</span><span class="p">[</span><span class="s1">&#39;track-label&#39;</span><span class="p">];</span>
    <span class="kr">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">target</span><span class="p">.</span><span class="nx">dataset</span><span class="p">[</span><span class="s1">&#39;track-name&#39;</span><span class="p">];</span>
    <span class="kr">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="nx">target</span><span class="p">.</span><span class="nx">dataset</span><span class="p">[</span><span class="s1">&#39;track-name&#39;</span><span class="p">]</span> <span class="o">??</span> <span class="nx">target</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="s1">&#39;href&#39;</span><span class="p">)</span> <span class="o">??</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">;</span>

    <span class="c1">// Pass events to the global tracking helper
</span><span class="c1"></span>    <span class="k">switch</span> <span class="p">(</span><span class="nx">action</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">case</span> <span class="s1">&#39;click&#39;</span><span class="o">:</span> <span class="p">{</span>
            <span class="nx">trackEvent</span><span class="p">(</span><span class="nx">action</span><span class="p">,</span> <span class="p">{</span>
                <span class="nx">category</span><span class="p">,</span>
                <span class="nx">label</span><span class="p">,</span>
                <span class="nx">name</span><span class="p">,</span>
                <span class="nx">url</span><span class="p">,</span>
            <span class="p">});</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">case</span> <span class="s1">&#39;submit&#39;</span><span class="o">:</span> <span class="p">{</span>
            <span class="kr">const</span> <span class="nx">formLabel</span> <span class="o">=</span> <span class="nx">target</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;[type=&#34;submit&#34;]&#39;</span><span class="p">)</span><span class="o">?</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">??</span> <span class="nx">label</span><span class="p">;</span>
            <span class="kr">const</span> <span class="nx">formUrl</span> <span class="o">=</span> <span class="nx">target</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="s1">&#39;action&#39;</span><span class="p">)</span> <span class="o">??</span> <span class="nx">url</span><span class="p">;</span>
            <span class="nx">trackEvent</span><span class="p">(</span><span class="s1">&#39;cta&#39;</span><span class="p">,</span> <span class="p">{</span>
                <span class="nx">category</span><span class="p">,</span>
                <span class="nx">label</span><span class="o">:</span> <span class="nx">formLabel</span><span class="p">,</span>
                <span class="nx">name</span><span class="p">,</span>
                <span class="nx">url</span><span class="o">:</span> <span class="nx">formUrl</span><span class="p">,</span>
            <span class="p">});</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">default</span><span class="o">:</span>
            <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span> <span class="nx">globalTrackingHandler</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;submit&#39;</span><span class="p">,</span> <span class="nx">globalTrackingHandler</span><span class="p">);</span>

<span class="kr">const</span> <span class="nx">specificTrackingHandler</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="kr">const</span> <span class="p">{</span> <span class="nx">detail</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">event</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">detail</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
    <span class="kr">const</span> <span class="p">{</span> <span class="nx">eventAction</span><span class="p">,</span> <span class="nx">trackingDetail</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">detail</span><span class="p">;</span>

    <span class="k">switch</span> <span class="p">(</span><span class="nx">eventAction</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">case</span> <span class="s1">&#39;click&#39;</span><span class="o">:</span>
            <span class="nx">trackClick</span><span class="p">(</span><span class="nx">trackingDetail</span><span class="p">);</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="s1">&#39;cta&#39;</span><span class="o">:</span>
            <span class="nx">trackCta</span><span class="p">(</span><span class="nx">trackingDetail</span><span class="p">);</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">default</span><span class="o">:</span>
            <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="nx">TRACKING_EVENT</span><span class="p">,</span> <span class="nx">specificTrackingHandler</span><span class="p">);</span>
</code></pre></div>]]>
      </description>
    </item>
    
  </channel>
</rss>