<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Engineering on sbgrl.me</title><link>https://sbgrl.me/tags/engineering/</link><description>Recent content in Engineering on sbgrl.me</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>sylvain.bougerel@gmail.com (Sylvain Bougerel)</managingEditor><webMaster>sylvain.bougerel@gmail.com (Sylvain Bougerel)</webMaster><copyright>© 2026 Sylvain Bougerel</copyright><lastBuildDate>Sat, 16 May 2026 18:03:00 +0800</lastBuildDate><atom:link href="https://sbgrl.me/tags/engineering/index.xml" rel="self" type="application/rss+xml"/><item><title>The end of tech-debt?</title><link>https://sbgrl.me/posts/the-end-of-tech-debt/</link><pubDate>Sat, 16 May 2026 18:03:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/the-end-of-tech-debt/</guid><description>&lt;p&gt;After &lt;a href="https://claude.com/product/claude-code" target="_blank" rel="noreferrer"&gt;Claude Code&lt;/a&gt; performed a refactor across our codebase in less than 24 hours and mostly autonomously, I started to wonder if we could end our tech-debt, and put a price on it?&lt;/p&gt;

&lt;h2 class="relative group"&gt;Setting the stage
 &lt;div id="setting-the-stage" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#setting-the-stage" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The codebase I&amp;rsquo;m referring to is that of our main &lt;a href="https://nextjs.org/" target="_blank" rel="noreferrer"&gt;Next.js&lt;/a&gt; application. It&amp;rsquo;s not a small codebase by any measure: 200KLOC, 1500 files, Typescript, Terraform and other languages.&lt;/p&gt;
&lt;p&gt;Within this codebase, we use both &lt;a href="https://nextjs.org/docs/app/api-reference/file-conventions/route" target="_blank" rel="noreferrer"&gt;API route handlers&lt;/a&gt; and &lt;a href="https://react.dev/reference/rsc/server-functions" target="_blank" rel="noreferrer"&gt;server functions&lt;/a&gt; to fetch or mutate data. Each mechanism fits a specific purpose: an application should make its public API available via route handlers, while it should use server functions for internal mutations only.&lt;/p&gt;
&lt;p&gt;In our application, however, we had leftover server functions used only to fetch data — without mutating it. This often resulted in the server function returning stale information, because it was doing something it was not designed for. We needed to migrate them to route handlers.&lt;/p&gt;
&lt;p&gt;I had already fixed 4 of them, but I found 18 more that needed addressing.&lt;/p&gt;
&lt;p&gt;This is not hard work, but not a dumb task either. Each case requires a new API route, a new contract, properly bounded inputs, validated outputs, updating how the client-side sends requests and handles responses, all while making sure it lints correctly and passes the ~2700 unit tests of our application — a perfect task for an agent.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Ralph to the rescue!
 &lt;div id="ralph-to-the-rescue" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ralph-to-the-rescue" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;To make fixing the first 4 easier, I had already refined and tested a 150-line Claude skill. I knew it was time for a &lt;a href="https://ghuntley.com/ralph/" target="_blank" rel="noreferrer"&gt;Ralph loop&lt;/a&gt;. So I set up the skill as the spec file, provided additional instructions around merge request (MR) submission and handling, and got it going in auto mode.&lt;/p&gt;
&lt;p&gt;And it did. After 8 hours of work, it created 18 MRs for me to review, and continued to autonomously address the comments and fix any failures reported by the CI pipeline.&lt;/p&gt;
&lt;p&gt;At the point where all 18 MRs were submitted and ready to merge, this task had cost roughly USD$180. I estimate that Claude went roughly 5-10 times faster than I would.&lt;/p&gt;
&lt;p&gt;$180 is not a large amount of money for a specific, targeted refactor. We have dozens of those we can do to migrate and improve our codebase. Can we end tech-debt in our codebase?&lt;/p&gt;

&lt;h2 class="relative group"&gt;Then reality caught up
 &lt;div id="then-reality-caught-up" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#then-reality-caught-up" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;After MR 6/18 merged into the &lt;code&gt;main&lt;/code&gt; branch, our testing environment went down. It was not hard to notice: even a local test server would not run after rebasing. MR 6/18 contained a route that was not set up properly, and that could only be caught at runtime. It was an easy fix, and it reminded me that I had forgotten a step in my Ralph loop: I quickly instructed it to build comprehensive test cases for us to exhaustively verify the changes.&lt;/p&gt;
&lt;p&gt;Testing is going to need improvement, but we knew this; we had already started to experiment with end-to-end tests to tackle this tech-debt. Here again, with great help from Claude, we managed to automate some end-to-end testing. However, in contrast to the current migration, none of the results so far have been satisfactory. This time, it seems our issues have more to do with the design of our application — and the solution isn&amp;rsquo;t yet clear.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The trouble with the process
 &lt;div id="the-trouble-with-the-process" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-trouble-with-the-process" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;But the testing environment going down was not actually the biggest friction — it was fixed quickly. The problem was that it took Claude 4 to 5 hours &lt;em&gt;just&lt;/em&gt; to merge the remaining 12 MRs after MR 6. 8 hours of productive work, 4 hours of watching paint dry.&lt;/p&gt;
&lt;p&gt;We use short-lived development branches against the &lt;code&gt;origin/main&lt;/code&gt; trunk, and maintain a semi-linear history on merges (trunk-based development). This means that after each merge, the next merge request needs to be rebased, and the CI pipeline must run again. A rebase + CI cycle takes around 8 to 10 minutes today; and if the pipeline fails or a conflict occurs during rebase, additional CI cycles may need to run.&lt;/p&gt;
&lt;p&gt;Every day, we race each other to merge our work. That day, we also raced the 18 MRs the agent had to merge. And our CI pipeline is not going to get faster — if anything it&amp;rsquo;s going to take &lt;em&gt;more&lt;/em&gt; time once we add end-to-end tests.&lt;/p&gt;
&lt;p&gt;So, can we end tech-debt?&lt;/p&gt;
&lt;p&gt;In short, not all tech-debt scales equally well for agents, especially when the issue may have more to do with application design or the solution is not yet clear — as in our case with end-to-end testing.&lt;/p&gt;
&lt;p&gt;And while we can certainly price some migration work, or even execute it concurrently with our feature work, it still needs to clear CI. Agents didn&amp;rsquo;t eliminate the implementation bottleneck — they moved it to the CI process.&lt;/p&gt;
&lt;p&gt;I now find myself thinking about &lt;a href="https://docs.gitlab.com/ci/pipelines/merge_trains/" target="_blank" rel="noreferrer"&gt;merge trains&lt;/a&gt; and other merging strategies to eliminate the CI bottleneck. I&amp;rsquo;m also wondering whether larger organisations are now experiencing the same pressure on their CI processes, and how this will shape the future of CI and trunk-based development.&lt;/p&gt;</description></item><item><title>Why I continue to review code</title><link>https://sbgrl.me/posts/why-i-continue-to-review-code/</link><pubDate>Fri, 01 May 2026 10:01:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/why-i-continue-to-review-code/</guid><description>&lt;p&gt;Given the increase in velocity from coding assistants such as Claude Code and Copilot, automated review had become a necessity. Since we rolled out &lt;a href="https://coderabbit.ai" target="_blank" rel="noreferrer"&gt;Coderabbit&lt;/a&gt; on our project, almost everybody stopped reviewing code. It didn&amp;rsquo;t happen immediately, but organically.&lt;/p&gt;
&lt;p&gt;As it turns out, Coderabbit is a formidable reviewer: with only a bit of context in the commit messages and a well-written &lt;code&gt;AGENTS.md&lt;/code&gt;, Coderabbit will often be more thorough in reviews than most of my colleagues. And if that wasn&amp;rsquo;t enough, Coderabbit is almost always available—&lt;a href="https://status.coderabbit.ai/" target="_blank" rel="noreferrer"&gt;98.3% of the time in the last 30 days&lt;/a&gt;, not good for typical SLA targets but much better than colleagues—and a fraction of their cost.&lt;/p&gt;
&lt;p&gt;Eventually everybody stopped reviewing code&amp;hellip; Save for me.&lt;/p&gt;
&lt;p&gt;For one thing, coding assistants can hallucinate blunders that neither humans—regrettably—nor Coderabbit will catch:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NON_EDITABLE_BOOKING_STATUSES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;cancelled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;canceled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;declined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;withdrawn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I took that snippet from a merge request submitted yesterday. We do not have any &lt;code&gt;declined&lt;/code&gt; or &lt;code&gt;canceled&lt;/code&gt; statuses in our entire domain model—they simply don&amp;rsquo;t exist. I chose this example because it&amp;rsquo;s one of the simplest. The day before, a merge request had a complete re-implementation of a 200+ lines utility we already had. This happens all the time.&lt;/p&gt;
&lt;p&gt;But most importantly, if the phone rings because of an issue on the system, &lt;strong&gt;I&lt;/strong&gt; need to get on the call and fix the issue. When my work has potential to impact my personal life and my weekends with family, there&amp;rsquo;s no AI that I will trust enough. And every alert, whether it fires on the job or not, still costs the whole team velocity when we scramble for unplanned work.&lt;/p&gt;
&lt;p&gt;Ultimately, our customers don&amp;rsquo;t care who wrote the code: &amp;ldquo;We&amp;rsquo;re sorry, Claude wrote that line and Coderabbit didn&amp;rsquo;t catch the problem&amp;rdquo; is not a good look. No matter how powerful our tools have become, responsibility is still mine. So I&amp;rsquo;ll keep on reviewing code.&lt;/p&gt;</description></item><item><title>AI exacerbated the divide between engineers</title><link>https://sbgrl.me/posts/ai-exacerbated-the-divide-between-engineers/</link><pubDate>Sat, 25 Apr 2026 22:12:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/ai-exacerbated-the-divide-between-engineers/</guid><description>&lt;p&gt;Any complex product requires deep context about the product&amp;rsquo;s domains, architecture, implementation. And if you want to make effective use of AI to solve a problem, you need to inject the right subset of that entire context into the prompt, or that smart auto-complete will spit out costly nonsense.&lt;/p&gt;
&lt;p&gt;Naturally, engineers with the best understanding of the context, the capacity to articulate the problem and the skills to implement it are even stronger with AI: they can provide better input to the model and quickly validate its output. The once derided 10x engineer is starting to become a reality for anyone who can use AI to implement a correct solution at a fraction of the time it would have taken them.&lt;/p&gt;
&lt;p&gt;On the other end of the spectrum, weaker engineers are now compelled to keep up with the pace set by their stronger peers, only they can neither prompt the LLM effectively nor validate what it produces. So they just let the garbage out—knowingly or not.&lt;/p&gt;
&lt;p&gt;If you feel like you&amp;rsquo;ve become a &amp;ldquo;man-in-the-middle&amp;rdquo;—a proxy between somebody else&amp;rsquo;s request and an LLM—you will be phased out. The great engineers of today learned the ropes at a time AI didn&amp;rsquo;t exist, when they could invest in their fundamentals. Give yourself the same gift: carve out time to use less AI, and start learning again so you can close the divide.&lt;/p&gt;</description></item><item><title>Should you use React Strict Mode in Next.js?</title><link>https://sbgrl.me/posts/should-you-use-react-strictmode/</link><pubDate>Tue, 03 Mar 2026 17:34:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/should-you-use-react-strictmode/</guid><description>&lt;p&gt;If you&amp;rsquo;re not familiar with &lt;a href="https://react.dev/reference/react/StrictMode" target="_blank" rel="noreferrer"&gt;React StrictMode&lt;/a&gt;, you&amp;rsquo;re probably not using a framework like Next.js that enables it by default. Strict Mode aims to help you find bugs during development. But it feels more like a band-aid that can reveal bugs or hide them, especially when mixing with server-side rendering (SSR).&lt;/p&gt;
&lt;p&gt;In this post, I will assume you&amp;rsquo;re using SSR and Strict Mode too as they are usually enabled by default with Next.js.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Enter the rabbit hole
 &lt;div id="enter-the-rabbit-hole" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#enter-the-rabbit-hole" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Now let&amp;rsquo;s suppose you write the following component, that renders a simple &lt;code&gt;&amp;lt;time /&amp;gt;&lt;/code&gt; markup for a given &lt;code&gt;date&lt;/code&gt;. Nothing fancy:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;: &lt;span class="kt"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isoString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt; &lt;span class="na"&gt;dateTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isoString&lt;/span&gt;&lt;span class="p"&gt;}&amp;gt;{&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Let&amp;rsquo;s say you also have a simple home page, in your typical React application:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LocalTime&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./local-time&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Home() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;It&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;LocalTime&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;02/03/2026, 10:00 AM&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If your application uses SSR, what you will see &lt;strong&gt;always&lt;/strong&gt; depends on your locale and on the time zone of the server; in this case, with an &lt;code&gt;en-US&lt;/code&gt; locale and a UTC timezone, the server will render the following page, even in development:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;It is 2/3/2026, 10:00:00 AM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;However, that&amp;rsquo;s usually &lt;strong&gt;not&lt;/strong&gt; what you want for your clients. Let&amp;rsquo;s say your client lives in Singapore, they should expect to see a time in a different timezone (GMT+8 in the case of Singapore). If you&amp;rsquo;re a bit experienced, you know your component must run on the client, and there&amp;rsquo;s a &lt;code&gt;&amp;quot;use client;&amp;quot;&lt;/code&gt; directive in React that ensures a component can be an entry point to client rendering. You update the &lt;code&gt;LocalTime&lt;/code&gt; component:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;use client&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// new directive
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;: &lt;span class="kt"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isoString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt; &lt;span class="na"&gt;dateTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isoString&lt;/span&gt;&lt;span class="p"&gt;}&amp;gt;{&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Your development server hot-reloads and this time you see:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;It is 2/3/2026, 6:00:00 PM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Success! However, when you refresh the page, you notice there&amp;rsquo;s a flicker: the UTC time shows first, then the local time appears. You pull up the console, and you see a pretty large error message:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Uncaught Error: Hydration failed because the server rendered text didn&amp;#39;t match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- A server/client branch `if (typeof window !== &amp;#39;undefined&amp;#39;)`.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Variable input such as `Date.now()` or `Math.random()` which changes each time it&amp;#39;s called.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Date formatting in a user&amp;#39;s locale which doesn&amp;#39;t match the server.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- External changing data without sending a snapshot of it along with the HTML.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Invalid HTML tag nesting.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We&amp;rsquo;ve hit a classic issue with React SSR/CSR: hydration mismatch due to a time zone difference between the server and the client. &lt;strong&gt;And more importantly&lt;/strong&gt;, you might not have noticed that error at all. The flicker might not have caught your attention, you might not have looked at the console.&lt;/p&gt;
&lt;p&gt;You might just have deployed it to production. And to your surprise, once in production the page would show the UTC time only. It seems the component did not run on the client. Not a great user experience:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;It is 2/3/2026, 10:00:00 AM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In reality, there was no reason for your component to re-render on the client again after hydration. This happened in development only due to React Strict Mode, so we&amp;rsquo;ve been led down the wrong path; thinking the application worked where it didn&amp;rsquo;t. You think this example is contrived? &lt;a href="http://canterly.com" target="_blank" rel="noreferrer"&gt;At Canterly&lt;/a&gt;, we have already faced 3 scenarios similar to this one; each time React Strict Mode was hiding an issue.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The reasoning behind Strict Mode
 &lt;div id="the-reasoning-behind-strict-mode" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-reasoning-behind-strict-mode" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;React brings a flavor of functional programming (FP) to UI. In FP, when the output of a function is defined only by its input, and it has no side effects, we call it a &lt;strong&gt;pure&lt;/strong&gt; function. The React equivalent means that the rendering of a component should be &lt;a href="https://react.dev/learn/keeping-components-pure#purity-components-as-formulas" target="_blank" rel="noreferrer"&gt;idempotent&lt;/a&gt;: it depends only on the component properties, its state and its context. These 3 sources of information are the rendering &lt;strong&gt;inputs&lt;/strong&gt; in React; you should not use any other inputs.&lt;/p&gt;
&lt;p&gt;That is not to say React does not handle side effects. However, you should confine side effects to events handlers, and in case you can&amp;rsquo;t find a suitable event handler for your side effect, React dedicates a whole phase of its update to side effects &lt;a href="https://react.dev/reference/react/hooks#effect-hooks" target="_blank" rel="noreferrer"&gt;which you declare in the &lt;code&gt;use*Effect()&lt;/code&gt; hooks&lt;/a&gt;. The steps below are an over-simplification of an update with React (&lt;a href="https://react.dev/learn/render-and-commit" target="_blank" rel="noreferrer"&gt;more about it here&lt;/a&gt;):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If there are no updates to component inputs, go to the last step;&lt;/li&gt;
&lt;li&gt;Render updated components;&lt;/li&gt;
&lt;li&gt;Run side effects;&lt;/li&gt;
&lt;li&gt;Return to the top;&lt;/li&gt;
&lt;li&gt;Commit changes to the DOM.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Idempotency allows React to track component changes by looking only at their inputs. However, it&amp;rsquo;s easy to break idempotency if you unknowingly use an external input, modify properties incorrectly or rely on a variable during rendering that is not provided through its properties, state or context.&lt;/p&gt;
&lt;p&gt;To help uncover these issues, React introduced Strict Mode. Wrapping your component in &lt;code&gt;&amp;lt;StrictMode&amp;gt;&lt;/code&gt; adds an extra render on mount and after the side effects run, to check that rendering was not impacted:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If there are no updates to component inputs, go to the last step;&lt;/li&gt;
&lt;li&gt;Render updated components;&lt;/li&gt;
&lt;li&gt;Run declared side effects;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strict mode: render all components once more and check there are no differences with the output in 2.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Return to the top;&lt;/li&gt;
&lt;li&gt;Commit changes to the DOM.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If your React component is pure, then that extra render should be idempotent and similar to the first.&lt;/p&gt;

&lt;h2 class="relative group"&gt;False positives with Strict Mode
 &lt;div id="false-positives-with-strict-mode" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#false-positives-with-strict-mode" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Returning to our example: the first server-side render produces an incorrect UTC time, which could have been caught if it was not immediately redrawn by the extra Strict Mode render that occurred when React mounted the component. Instead, the developer saw the local time.&lt;/p&gt;
&lt;p&gt;This little example illustrates that this approach is just as likely to catch a bug as to hide one, since Strict Mode can lead to false positives. Especially if you&amp;rsquo;re not careful with checking hydration issues.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s worth noting that a more careful developer could also catch the hydration mismatch. And they might be tempted to address the locale &amp;amp; time zone hydration mismatch by simply silencing it, since this component does not have any children:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;use client&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;: &lt;span class="kt"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isoString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt; &lt;span class="na"&gt;dateTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isoString&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="na"&gt;suppressHydrationWarning&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Perhaps unexpectedly, once the component uses the property &lt;code&gt;suppressHydrationWarning&lt;/code&gt;, the UTC time rendered server side re-appears and is not replaced by the local time in development. Even in the presence of Strict Mode, the component is not regenerated again. This could have revealed the error again; but requires employing a property that is usually discouraged.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Beyond Strict Mode
 &lt;div id="beyond-strict-mode" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#beyond-strict-mode" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Since React 18, the React Compiler &lt;a href="https://react.dev/blog/2024/10/21/react-compiler-beta-release#we-recommend-everyone-use-the-react-compiler-linter-today" target="_blank" rel="noreferrer"&gt;has grown a new linter&lt;/a&gt;. I highly recommend the React Compiler ESLint plugin. While it does not address all the objectives of Strict Mode, there&amp;rsquo;s a significant amount of overlap between the two tools. In my own usage, I have found that the linter helps me better with identifying violations of &lt;a href="https://react.dev/reference/rules" target="_blank" rel="noreferrer"&gt;the Rules of React&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, when it comes to our little example, the linter does not identify that &lt;code&gt;date.toLocaleString()&lt;/code&gt; requires a time zone and a locale that are not declared as input to the component. The linter is still a work in progress; I hope that these limitations will eventually be addressed.&lt;/p&gt;
&lt;p&gt;I will leave you with a version of the component that follows the Rules of React (note that this version will not scale well if you have a lot of components to render):&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;use client&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffectEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;react&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;: &lt;span class="kt"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;formattedDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFormattedDate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Fix a locale and a time zone for idempotency
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;en-US&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;timeZone&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;UTC&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isoString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useEffectEvent&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setFormattedDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt; &lt;span class="na"&gt;dateTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isoString&lt;/span&gt;&lt;span class="p"&gt;}&amp;gt;{&lt;/span&gt;&lt;span class="nx"&gt;formattedDate&lt;/span&gt;&lt;span class="p"&gt;}&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This may seem a little complicated for a component that just renders a date without any interactions; but remember that there&amp;rsquo;s a hidden side effect in the original rendering. And unfortunately, in this specific scenario, Strict Mode put us on the wrong path.&lt;/p&gt;
&lt;p&gt;One of the selling points of Strict Mode is that it will help reveal the issues with components that violate the Rules of React. But I would argue that if the issues can&amp;rsquo;t be found without Strict Mode, then you might as well just ignore them: your component is probably too simple to care about them. Otherwise, you should always pay attention to hydration issues and ensure your project uses the React Compiler ESLint plugin.&lt;/p&gt;</description></item><item><title>Introducing @sbougerel/eslint-plugin-next-use-client-boundary</title><link>https://sbgrl.me/posts/introducing-sbougerel-eslint-plugin-next-use-client-boundary/</link><pubDate>Sat, 14 Feb 2026 17:46:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/introducing-sbougerel-eslint-plugin-next-use-client-boundary/</guid><description>&lt;p&gt;In &lt;a href="https://sbgrl.me/posts/dont-abuse-use-client/" target="_blank" rel="noreferrer"&gt;my last post&lt;/a&gt; I spoke about releasing an ESLint plugin to lint the serialization of &lt;code&gt;'use client'&lt;/code&gt; module entries. I&amp;rsquo;ve spend a few hours with Claude code to &lt;a href="https://www.npmjs.com/package/@sbougerel/eslint-plugin-next-use-client-boundary" target="_blank" rel="noreferrer"&gt;get this done and published on npmjs.org&lt;/a&gt;. I&amp;rsquo;ll probably come back in another post on the experience there (Some gains, some losses). I&amp;rsquo;ve just started a branch on my product to use the plugin, looking forward to see if it&amp;rsquo;s useful to others.&lt;/p&gt;</description></item><item><title>Don't abuse 'use client'</title><link>https://sbgrl.me/posts/dont-abuse-use-client/</link><pubDate>Sat, 31 Jan 2026 17:56:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/dont-abuse-use-client/</guid><description>&lt;p&gt;I see &lt;code&gt;'use client'&lt;/code&gt; used too often in our own code base, and it seems to be &lt;a href="https://github.com/vercel/next.js/discussions/46795" target="_blank" rel="noreferrer"&gt;a matter of confusion elsewhere too&lt;/a&gt;: devs don&amp;rsquo;t understand &lt;code&gt;'use client'&lt;/code&gt; or don&amp;rsquo;t want to get into it. Let me give a quick explanation of why you should not use it everywhere and hopefully, by the end of this post, you&amp;rsquo;ll have at least understood what it is &lt;strong&gt;not&lt;/strong&gt; for.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;'use client'&lt;/code&gt; indicates that a client module can be called within server modules. No more no less. That means &lt;code&gt;'use client'&lt;/code&gt; modules have some restrictions, because they can sit at the server-client boundary:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;When a server evaluated module imports values from a &lt;code&gt;'use client'&lt;/code&gt; module, the values must either be a React component or supported serializable prop values to be passed to a Client Component. Any other use case will throw an exception.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;em&gt;(Source: &lt;a href="https://react.dev/reference/rsc/use-client#caveats" target="_blank" rel="noreferrer"&gt;React reference&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The thing is: most types are serializable, so you don&amp;rsquo;t generally see an issue with putting &lt;code&gt;'use client'&lt;/code&gt; everywhere. But one commonly used type is not: &lt;strong&gt;plain simple functions&lt;/strong&gt;. It should make intuitive sense: a reference to a function is meaningless at the server-client boundary (unless it is a server action).&lt;/p&gt;
&lt;p&gt;In practice: if your reactive client component exposes events to its parents, like &lt;code&gt;onSubmit&lt;/code&gt; and &lt;code&gt;onCancel&lt;/code&gt; in the component below, that means we expect the parents to be client component themselves.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LineItemForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onCancel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;account&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;: &lt;span class="kt"&gt;LineItemData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onCancel&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;How could it be otherwise? The function &lt;code&gt;onSubmit&lt;/code&gt; or &lt;code&gt;onCancel&lt;/code&gt; could never cross the client-server boundary. Thus, you can remember now:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;When an exported client component has properties that are not serializable, like functions, its module should not start with &lt;code&gt;'use client'&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Now, you may be thinking: &amp;ldquo;But adding &lt;code&gt;'use client'&lt;/code&gt; does not hurt anything, right?&amp;rdquo; Ever taken painkillers when you don&amp;rsquo;t need it? It&amp;rsquo;s the same reasoning: it does not harm, but at the critical moment where you need to feel pain, you&amp;rsquo;re suddenly numb to it.&lt;/p&gt;
&lt;p&gt;Without &lt;code&gt;'use client'&lt;/code&gt; at the top of that component, the compiler will not let you call that client component from a server component. But with an incorrect &lt;code&gt;'use client'&lt;/code&gt; the compiler will just obey and assume you&amp;rsquo;ve made sure the properties are serializable &lt;strong&gt;without checking&lt;/strong&gt;, and misusing that component in server components could result in runtime errors.&lt;/p&gt;
&lt;p&gt;Unfortunately, I cannot find any linter that lints this. There is however a dynamic warning provided by the &lt;code&gt;typescript-language-server&lt;/code&gt;: &lt;code&gt;Props must be serializable for components in the &amp;quot;use client&amp;quot; entry file&lt;/code&gt;. Adding a linter for this would be an interesting project.&lt;/p&gt;</description></item><item><title>3 rules for simple tenancy in DynamoDB</title><link>https://sbgrl.me/posts/3-rules-for-simple-tenancy-in-dynamodb/</link><pubDate>Mon, 12 Jan 2026 12:39:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/3-rules-for-simple-tenancy-in-dynamodb/</guid><description>
&lt;h2 class="relative group"&gt;Access control in DynamoDB
 &lt;div id="access-control-in-dynamodb" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#access-control-in-dynamodb" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://aws.amazon.com/dynamodb/" target="_blank" rel="noreferrer"&gt;DynamoDB&lt;/a&gt; does not come with its own account management &amp;amp; fine-grained access control. Instead, it integrates with IAM, just like other AWS services.&lt;/p&gt;
&lt;p&gt;This is great when your users authenticate with IAM, but it&amp;rsquo;s not the case of the majority of serverless web applications that authenticate users via &lt;a href="https://oauth.net/2" target="_blank" rel="noreferrer"&gt;OAuth&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Serverless web applications on AWS are &lt;em&gt;usually&lt;/em&gt; structured this way: API Gateway receives a request, proxies this request to a Lambda, that constructs the response, optionally querying DynamoDB. In this architecture, the Lambda runs with a static IAM role. OAuth authentication is performed in the Lambda runtime using this static role; i.e. &lt;em&gt;no&lt;/em&gt; fine-grained IAM access control.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/3-rules-for-simple-tenancy-in-dynamodb-1.svg" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;This approach is usually preferred because it is simple, responsive, and yet scales. It leaves the responsibility of access control to the Lambda&amp;rsquo;s runtime, however&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re writing a SaaS application in particular, you need to make sure that your tenants are &lt;a href="https://www.saas-architecture.com/capabilities/tenant-isolation.html" target="_blank" rel="noreferrer"&gt;isolated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fortunately, by following 3 rules, DynamoDB can provide tenant-based access control.&lt;/p&gt;

&lt;h2 class="relative group"&gt;All partition keys have &lt;code&gt;tenant&lt;/code&gt;
 &lt;div id="all-partition-keys-have-tenant" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#all-partition-keys-have-tenant" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Make the &lt;code&gt;tenant&lt;/code&gt; identifier a constituent of all your items&amp;rsquo; partition key (a.k.a. hash key). For a &lt;code&gt;String&lt;/code&gt; partition key, with a &lt;a href="https://aws.amazon.com/blogs/database/amazon-dynamodb-single-table-design-using-dynamodbmapper-and-spring-boot/" target="_blank" rel="noreferrer"&gt;single-table design&lt;/a&gt;, the value of the partition key be:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;${tenant}#${entity_kind}#${item_partition_key}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;No &lt;code&gt;SCAN&lt;/code&gt;
 &lt;div id="no-scan" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#no-scan" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;All other &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html" target="_blank" rel="noreferrer"&gt;CRUD operations&lt;/a&gt; are allowed, however &lt;em&gt;you shall not use &lt;code&gt;scan&lt;/code&gt;&lt;/em&gt;. Scanning in DynamoDB reads any of your partitions &lt;em&gt;at random&lt;/em&gt;. Thus, it bypasses the first rule. Enforce this in your Lambda role permission: deny &lt;code&gt;dynamodb:Scan&lt;/code&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Get &lt;code&gt;tenant&lt;/code&gt; from the request only
 &lt;div id="get-tenant-from-the-request-only" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#get-tenant-from-the-request-only" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The final piece: the tenant identifier is always provided by the request. It shall not be a value you can retrieve from a query in the same table. If you do need to provide a list of tenants somewhere, this should be done separately, in a different table or preferably in a different cell.&lt;/p&gt;

&lt;h2 class="relative group"&gt;How does it work?
 &lt;div id="how-does-it-work" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-does-it-work" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;All CRUD operations in DynamoDB require a partition key. All except &lt;code&gt;scan&lt;/code&gt; which is why we disable it in rule 2.&lt;/p&gt;
&lt;p&gt;Rule 3 ensures that a client&amp;rsquo;s access to a tenant partition is handled separately; the &lt;code&gt;tenant&lt;/code&gt; identifier acts as a message passed between this step and the current requests; isolating each request to a single tenant. As a corollary, you should treat any request that require access to multiple tenant with higher restrictions, ideally running on a separate Lambda with the least privileges.&lt;/p&gt;
&lt;p&gt;Finally, rule 1 leverages rule 2 and 3 to ensure that your CRUD operations can only retrieve information from that &lt;code&gt;tenant&lt;/code&gt;&amp;rsquo;s partition, since it becomes impossible to access other tenants&amp;rsquo; data without their identifiers.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Limitations
 &lt;div id="limitations" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#limitations" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;While this works well to isolate requests to a single tenant, this remains a &amp;ldquo;coarse-grained&amp;rdquo; approach; you do not have access to the attribute based access control provided by IAM and will need to implement the equivalent permissions in your application.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html" target="_blank" rel="noreferrer"&gt;API Gateway&amp;rsquo;s Lambda authorizers&lt;/a&gt; allow you to provide context variables that can be used in dynamic fine-grained access control roles. This approach is more complex and not common for Next.js applications, in particular.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Pino log instrumentation with Next.js 16</title><link>https://sbgrl.me/posts/pino-log-instrumentation-in-next-js-16/</link><pubDate>Sun, 04 Jan 2026 11:39:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/pino-log-instrumentation-in-next-js-16/</guid><description>&lt;p&gt;Recently at &lt;a href="https://canterly.com" target="_blank" rel="noreferrer"&gt;Canterly&lt;/a&gt;, we migrated from Next.js 14 to Next.js 16. In that process, we also switched from &lt;a href="https://webpack.js.org/" target="_blank" rel="noreferrer"&gt;webpack&lt;/a&gt; to &lt;a href="https://nextjs.org/docs/app/api-reference/turbopack" target="_blank" rel="noreferrer"&gt;turbopack&lt;/a&gt;. This was a great speed improvement that came with some surprises. In particular, log instrumentation via &lt;a href="https://github.com/sainsburys-tech/next-logger" target="_blank" rel="noreferrer"&gt;next-logger&lt;/a&gt; now triggered a failure during compilation. I had been wanting to replace it anyway, since all it does is to patch &lt;code&gt;console&lt;/code&gt; to use &lt;a href="https://getpino.io" target="_blank" rel="noreferrer"&gt;Pino&lt;/a&gt; instead (It also patches log functions in Next.js &lt;code&gt;next/dist/build/output/log&lt;/code&gt;, which is redundant since these functions use &lt;code&gt;console&lt;/code&gt; too). So instead, I patched &lt;code&gt;console&lt;/code&gt; on my own. This is simpler and allows us to keep control over the instrumentation, rather than obfuscating the process with an additional dependency.&lt;/p&gt;
&lt;p&gt;This is what we have now in &lt;code&gt;instrumentation.ts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;register() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_RUNTIME&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;nodejs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instrumentation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./instrumentation-nodejs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;instrumentation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We&amp;rsquo;ve replaced &lt;code&gt;next-logger.config.cjs&lt;/code&gt; with our own &lt;code&gt;logger.ts&lt;/code&gt; which is a straight forward port:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LoggerOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pino&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pino&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pinoOptions&lt;/span&gt;: &lt;span class="kt"&gt;LoggerOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;warn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;production&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pinoOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;info&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pino-pretty&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pino&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pinoOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And in &lt;code&gt;instrumentation-nodejs.ts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LogFn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pino&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;instrumentLogging() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// NextJS also patches `console` functions during development for better
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// debugging; bypass instrumentation unless the environment
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// variable CANTERLY_INSTRUMENTATION is &amp;#34;true&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;development&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CANTERLY_INSTRUMENTATION&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loggerModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./logger&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;: &lt;span class="kt"&gt;LogFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;: &lt;span class="kt"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Converts usual Console usage to Pino&amp;#39;s meta patterns.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;string&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;string&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;consoleArgs&lt;/span&gt;: &lt;span class="kt"&gt;args.slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;consoleArgs&lt;/span&gt;: &lt;span class="kt"&gt;args&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;consoleLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;loggerModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;console&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;warn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;debug&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;info&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;log&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;trace&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;log&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;wrapLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consoleLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consoleLogger&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;wrapLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consoleLogger&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consoleLogger&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;register() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;instrumentLogging&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And that&amp;rsquo;s it. This instrumentation does not just replace next-logger, it improves on it. With next-logger, the following &lt;code&gt;console&lt;/code&gt; call&amp;hellip;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;console.log(&amp;#34;Log without interpolation&amp;#34;, valueA, valueB, valueC)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&amp;hellip;would be patched to the Pino call&amp;hellip;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;logger.log(&amp;#34;Log without interpolation&amp;#34;, valueA, valueB, valueC)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&amp;hellip;but that would lose logging data: when the first argument is a &lt;code&gt;string&lt;/code&gt; message, Pino will only keep other arguments &lt;a href="https://getpino.io/#/docs/api?id=interpolationvalues-any" target="_blank" rel="noreferrer"&gt;when they get interpolated&lt;/a&gt;. This new instrumentation preserves the additional arguments, by treating them as a &lt;a href="https://getpino.io/#/docs/api?id=mergingobject-object" target="_blank" rel="noreferrer"&gt;mergingObject&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, when our application starts, we can immediately see the additional arguments in the logs, as soon as instrumentation kicks in (We use &lt;code&gt;pino-pretty&lt;/code&gt; as the default transport during development):&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm run dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; env TZ=UTC next dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;▲ Next.js 16.1.1 (Turbopack)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Local: http://localhost:3000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Network: http://...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Environments: .env.local
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✓ Starting...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[03:23:14.265] INFO (console/28071): ✓ Ready in 1439ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[03:23:14.447] WARN (console/28071): [auth][warn][debug-enabled]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; consoleArgs: [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;Read more: https://warnings.authjs.dev&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</description></item><item><title>NextJS revalidateTag won't work if it's not called in an awaited promise</title><link>https://sbgrl.me/posts/nextjs-revalidate-tag-wont-work-unless-awaited/</link><pubDate>Thu, 18 Dec 2025 18:37:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/nextjs-revalidate-tag-wont-work-unless-awaited/</guid><description>&lt;p&gt;Using &lt;code&gt;unstable_cache&lt;/code&gt; extensions or &lt;code&gt;&amp;quot;use cache&amp;quot;&lt;/code&gt; if you&amp;rsquo;re on NextJS16, you can leverage &lt;a href="https://nextjs.org/docs/app/api-reference/functions/revalidateTag" target="_blank" rel="noreferrer"&gt;&lt;code&gt;revalidateTag&lt;/code&gt;&lt;/a&gt; to invalidate cache across cached calls. However, I noticed today that these won&amp;rsquo;t work in a route handler that is not &lt;code&gt;async&lt;/code&gt;. And I&amp;rsquo;m not yet sure why.&lt;/p&gt;
&lt;p&gt;This was my original (simplified) route handler:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;: &lt;span class="kt"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;someTask&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Internal error`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;status&lt;/span&gt;: &lt;span class="kt"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that &lt;code&gt;someTask&lt;/code&gt; returns a &lt;code&gt;Promise&lt;/code&gt; which I didn&amp;rsquo;t &lt;code&gt;await&lt;/code&gt; initially: the caller only needed to know that &lt;code&gt;someTask&lt;/code&gt; was called, not that it succeeded. However, it became evident &lt;code&gt;someTask&lt;/code&gt; didn&amp;rsquo;t behave properly.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;someTask&lt;/code&gt; gets a list of subscriptions and updates them progressively. After each update, it re-validates cache entries associated with the &lt;code&gt;Subscription&lt;/code&gt; tag:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cachedFindOutdatedSubscriptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unstable_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;findOutdatedSubscriptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Unused (For additional keys)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Subscription&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;someTask() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cachedFindOutdatedSubscriptions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;updateSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Subscription&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The function &lt;code&gt;cachedFindOutdatedSubscriptions&lt;/code&gt; returns subscriptions from a cache entry also tagged with &lt;code&gt;Subscription&lt;/code&gt;. You would therefore expect that across repeated calls to this route, the &lt;code&gt;subscriptions&lt;/code&gt; array becomes empty when no new subscriptions are created.&lt;/p&gt;
&lt;p&gt;This is not what happened; the same &lt;code&gt;subscriptions&lt;/code&gt; got reprocessed every single time, served from the cache. As if &lt;code&gt;revalidateTag&lt;/code&gt; was not called.&lt;/p&gt;
&lt;p&gt;Interestingly, the fix is simply to await the call to &lt;code&gt;someTask&lt;/code&gt; in the route handler:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; export async function POST(req: Request) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; try {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- void someTask();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ await someTask();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return NextResponse.json({ done: true });
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; } catch (err) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return new NextResponse(`Internal error`, {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; status: 500,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After reading the code for &lt;code&gt;unstable_cache&lt;/code&gt;, I got the intuition for the fix. I&amp;rsquo;m not sure of what is exactly happening under the hood yet, but I think it has to do with the lifetime of the request&amp;rsquo;s context. I&amp;rsquo;ll return to this once I understand it properly.&lt;/p&gt;</description></item></channel></rss>