<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ZIKORA CHUKWUKA</title>
    <description>The latest articles on DEV Community by ZIKORA CHUKWUKA (@zikthemaker).</description>
    <link>https://dev.to/zikthemaker</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3986431%2F469855c1-12e0-4b35-9525-8cf7ba39dd7d.jpeg</url>
      <title>DEV Community: ZIKORA CHUKWUKA</title>
      <link>https://dev.to/zikthemaker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zikthemaker"/>
    <language>en</language>
    <item>
      <title>Strategic Logging: The Debugging Skill Nobody Taught Me</title>
      <dc:creator>ZIKORA CHUKWUKA</dc:creator>
      <pubDate>Sun, 05 Jul 2026 02:14:25 +0000</pubDate>
      <link>https://dev.to/zikthemaker/strategic-logging-the-debugging-skill-nobody-taught-me-1de2</link>
      <guid>https://dev.to/zikthemaker/strategic-logging-the-debugging-skill-nobody-taught-me-1de2</guid>
      <description>&lt;p&gt;When something breaks in production, most developers do one of two things.&lt;/p&gt;

&lt;p&gt;They either guess- changing things randomly and hoping something works. Or they panic- opening every tab, reading every Stack Overflow answer, going in circles.&lt;/p&gt;

&lt;p&gt;Or more recently paste tracebacks into an AI.&lt;/p&gt;

&lt;p&gt;I have done all versions. &lt;/p&gt;

&lt;p&gt;I recently realized something from my work building production software.&lt;/p&gt;

&lt;p&gt;I realized I was missing a systematic way to see exactly what my code was doing at every step. &lt;/p&gt;

&lt;p&gt;Not just when it crashed, but before it crashed. During the journey from request to response, something was going wrong and I had no visibility into where.&lt;/p&gt;

&lt;p&gt;All of that changed after I learnt about strategic logging.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Strategic Logging?
&lt;/h2&gt;

&lt;p&gt;To understand strategic logging, we first have to understand logging.&lt;/p&gt;

&lt;p&gt;Logging is the practice of recording structured events from your code to capture the precise application state, execution path, and environmental context at a specific point in time.&lt;/p&gt;

&lt;p&gt;It is like leaving a trail of breadcrumbs in your code. Each breadcrumb tells you exactly where the program went, what data it was holding, and what the environment looked like at that exact moment.&lt;/p&gt;

&lt;p&gt;You have probably seen this before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;here&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is logging. But it is not strategic logging.&lt;/p&gt;

&lt;p&gt;Strategic logging is intentional. It tells you not just that your code reached a certain point, but what the state of your data was at that point, what action was being taken, and what the result was.&lt;/p&gt;

&lt;p&gt;The difference looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Not strategic&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;here&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Strategic&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login attempt started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Token received from API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tokenExists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cookie being set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;httpOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can you see the difference? The strategic version tells a story. The first version tells you nothing.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Production Bug
&lt;/h2&gt;

&lt;p&gt;I was building the frontend for an AI startup as the only frontend engineer on the team, consuming a FastAPI backend being built by another engineer.&lt;/p&gt;

&lt;p&gt;Auth was not working properly after deploying to &lt;a href="https://dev.to/igor_ag_aaa2341e64b1f4cb4/what-is-staging-in-software-development-3hfo"&gt;staging&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My first instinct was to assume the problem was on my end. I had recently implemented HttpOnly Cookies for session management and there were a lot of moving parts - server actions, middleware, cookie flags. Any one of them could have been the culprit.&lt;/p&gt;

&lt;p&gt;So I added logs at every step of the auth flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Server action called with credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;emailProvided&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;passwordProvided&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Calling API login endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&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;API_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/login`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API response received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; 
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Response data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="na"&gt;tokenExists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; 
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What came back stopped me immediately.&lt;/p&gt;

&lt;p&gt;The API response status was 500. The token did not exist. The error field pointed to the backend.&lt;/p&gt;

&lt;p&gt;The problem was not in my frontend at all. The logs had traced the issue directly to the backend where an incomplete database migration was causing the API to crash silently on every login attempt.&lt;/p&gt;

&lt;p&gt;Without those logs, I could have spent days ripping apart my frontend code looking for a bug that was never there. &lt;/p&gt;

&lt;p&gt;Instead I had a clear answer in minutes and a productive conversation with the backend engineer to resolve it fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Good Logs Actually Look Like
&lt;/h2&gt;

&lt;p&gt;Through this experience and further, I have come to understand that good logs share a few things in common.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They are contextual.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A good log does not just say something happened. It says what happened, with what data, and what the outcome was.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Weak&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API call made&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Strong  &lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API call made&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;They follow the journey.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of your code as a journey from request to response. &lt;/p&gt;

&lt;p&gt;Log at the beginning of each significant step and at the end. That way when something breaks you can see exactly where the journey stopped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They are removed after debugging.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Very Important.&lt;/p&gt;

&lt;p&gt;Production logs that are too verbose can expose sensitive data and slow down your application. The logs I write for debugging are temporary. Once I understand the problem and fix it, I clean them out. What stays in production should be intentional, not accidental.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They never log sensitive data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is important. Never log passwords, tokens, full credit card numbers, or any personally identifiable information you do not need. A log file in the wrong hands becomes a security problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Never do this&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Do this instead&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User login attempt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;passwordProvided&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  A Simple Framework for Debugging with Logs
&lt;/h2&gt;

&lt;p&gt;When something breaks and I do not know why, I now follow a simple process.&lt;/p&gt;

&lt;p&gt;First I identify the flow. I trace the journey from where the user action starts to where the expected outcome should be. In my case it was: form submission, server action, API call, cookie set, redirect.&lt;/p&gt;

&lt;p&gt;Then I add a log at the start of each step. Not inside the step yet. Just at the entrance so I can see how far the request is getting before it fails.&lt;/p&gt;

&lt;p&gt;Then I run it and see where the logs stop. The last log that appears before silence or an error is your crime scene. That is where to look.&lt;/p&gt;

&lt;p&gt;Then I go deeper into that specific step. Add more logs inside it. Narrow the problem down until you can see exactly what is wrong.&lt;/p&gt;

&lt;p&gt;Then I fix it, verify with the logs, and remove the debugging logs once the problem is resolved.&lt;/p&gt;




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

&lt;p&gt;The most valuable thing about this experience was not the fix itself. It was realising that debugging is a skill, not a talent.&lt;/p&gt;

&lt;p&gt;Some developers seem to find bugs quickly and others seem to struggle forever. &lt;/p&gt;

&lt;p&gt;The difference is usually not intelligence. It is methodology. Strategic logging is one of the most important tools in that methodology.&lt;/p&gt;

&lt;p&gt;I also learned something about working on a team. &lt;/p&gt;

&lt;p&gt;Without those logs, I might have spent days convinced the problem was mine. The logs gave me evidence. Evidence made the conversation with the backend engineer fast and productive. Nobody was defensive. We just looked at the data together.&lt;/p&gt;

&lt;p&gt;That is what good debugging does. It removes the guesswork and replaces it with clarity.&lt;/p&gt;

&lt;p&gt;I am actively learning about logging and observability. &lt;/p&gt;

&lt;p&gt;This article reflects my current understanding of the subject from real experience. &lt;/p&gt;

&lt;p&gt;If you have feedback or see something I can improve, please leave a comment. I am here to learn.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>learning</category>
      <category>softwareengineering</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built a Voice Coding Agent Over the Weekend on a Laptop With No GPU.</title>
      <dc:creator>ZIKORA CHUKWUKA</dc:creator>
      <pubDate>Sun, 05 Jul 2026 00:52:30 +0000</pubDate>
      <link>https://dev.to/zikthemaker/i-built-a-voice-coding-agent-over-the-weekend-on-a-laptop-with-no-gpu-5cp8</link>
      <guid>https://dev.to/zikthemaker/i-built-a-voice-coding-agent-over-the-weekend-on-a-laptop-with-no-gpu-5cp8</guid>
      <description>&lt;p&gt;I hold a belief that software is heading toward a voice-first future. Not voice as a gimmick bolted onto an app, but voice as a genuine interface. The kind where talking to your tools feels as natural as talking to a colleague.&lt;/p&gt;

&lt;p&gt;The spark for this project came from something small. I was in a live voice session with Claude, the kind where you talk and it talks back in real time, no typing at all. Something about that experience stuck with me. It felt like a glimpse of where things are going.&lt;/p&gt;

&lt;p&gt;Around the same time, I had been wanting an excuse to properly play with local AI models. I have always been drawn to the idea of running models on your own machine, no API calls, no cloud dependency, just you and your hardware. I had read about it, dabbled a little, but never gone deep.&lt;/p&gt;

&lt;p&gt;So I combined the two curiosities into a weekend project. Build a voice coding agent. Run the speech parts locally. Let Claude be the brain.&lt;/p&gt;

&lt;p&gt;That project became Mabara.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Mabara Actually Does
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdc24ggk8t2533206ojf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdc24ggk8t2533206ojf7.png" alt="Screenshot of Mabara on windows" width="799" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/zikorachukwuka/mabara" rel="noopener noreferrer"&gt;Mabara&lt;/a&gt; lets you talk to your codebase the way you would talk to a coworker. You hold down a key, ask a question or give an instruction, and it responds out loud. It can explain how your project is structured, make changes to your files when you approve them by voice, and if it does something you do not like, you just say "revert that" and it undoes it.&lt;/p&gt;

&lt;p&gt;The clever part is where the thinking happens versus where the listening and speaking happen. Claude, running through the &lt;a href="https://github.com/anthropics/claude-agent-sdk-python" rel="noopener noreferrer"&gt;Claude Agent SDK&lt;/a&gt;, is the brain. It understands your code and decides what to do. But the ears and the mouth, the part that hears you and the part that talks back, run entirely on my own laptop. No cloud speech service. Just my CPU.&lt;/p&gt;

&lt;p&gt;And that laptop is not a powerful one. It is an i5-10210U with 8 GB of RAM and no GPU. Not the machine anyone benchmarks AI on. That constraint is actually the whole reason this story is interesting.&lt;/p&gt;

&lt;p&gt;This is really a story about measuring things, and about learning to trust data over assumptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; I am not an expert on speech models or real-time systems. I am sharing what I measured and what it taught me. If you see something I got wrong, please tell me. I am here to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson One: The Benchmark Is the Truth, Everything Else Is Marketing
&lt;/h2&gt;

&lt;p&gt;Here is a thing everyone "knows" in the AI world. Quantizing a model to int8 makes it faster on CPU. Smaller numbers, faster math. Every optimization guide says so.&lt;/p&gt;

&lt;p&gt;So when my text-to-speech model was too slow, I downloaded the int8 version, fully expecting a win.&lt;/p&gt;

&lt;p&gt;It ran 2.5 times SLOWER than the full precision model.&lt;/p&gt;

&lt;p&gt;Not slower by a little. The full precision model hit 1.6x real-time on my CPU. The int8 version managed 0.6x, which is slower than the speech it was supposed to be producing.&lt;/p&gt;

&lt;p&gt;Understanding why taught me more about hardware than any course I have taken. Int8 speedups depend on special CPU instructions, called VNNI on Intel chips, that handle quantized math natively. My Comet Lake CPU does not have them. Without those instructions, the CPU has to quantize, dequantize, and shuffle formats on every single operation. That is pure overhead with zero benefit.&lt;/p&gt;

&lt;p&gt;The pattern repeated soon after. Distil-Whisper, marketed as six times faster than Whisper, was exactly zero percent faster for my use case. Push-to-talk clips are short, and short clips spend almost all their time in the encoder, which distillation does not shrink. That famous speedup only shows up on hour-long recordings.&lt;/p&gt;

&lt;p&gt;A tuning parameter that was supposed to cut Whisper's processing window did nothing at all. I benchmarked three different values. Identical times, down to the decimal.&lt;/p&gt;

&lt;p&gt;By the end of day one I had a personal rule. Nothing ships in Mabara unless it wins a benchmark on this exact laptop. Not a benchmark from someone else's README. Mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson Two: I Traded a Beautiful Voice for 1.2 Seconds, and It Was the Easiest Decision of the build
&lt;/h2&gt;

&lt;p&gt;The voice journey humbled me.&lt;/p&gt;

&lt;p&gt;I started with &lt;a href="https://huggingface.co/hexgrad/Kokoro-82M" rel="noopener noreferrer"&gt;Kokoro&lt;/a&gt;, an 82 million parameter model that genuinely sounds human. On my CPU it synthesizes at roughly 1.0x real-time. That number sounds fine until you understand what it actually means. The system generates speech exactly as fast as it speaks it. There is zero margin for error. Every sentence boundary becomes a coin flip between smooth speech and an awkward gap.&lt;/p&gt;

&lt;p&gt;So I switched to Piper, a smaller and admittedly more robotic sounding model that runs at 7x real-time. The gaps disappeared completely. But the voice bothered me. Every reply reminded me I was talking to a machine, not a colleague.&lt;/p&gt;

&lt;p&gt;Then I found Supertonic, a 66 million parameter model that sounds nearly as good as Kokoro and runs at 2x real-time on my hardware. I spent a couple of hours integrating it properly. Picked a voice I liked. Tuned the pacing until it felt right.&lt;/p&gt;

&lt;p&gt;Then I actually used it. Within twenty minutes I switched back to Piper.&lt;/p&gt;

&lt;p&gt;Here is why. Supertonic needed about 1.6 seconds to produce the first word of every reply. Piper needed 0.4 seconds. That 1.2 second difference does not sound like much written down. But inside an actual conversation, it is the difference between talking to a colleague and waiting for a system to catch up.&lt;/p&gt;

&lt;p&gt;I had happily tolerated Piper's robotic edge for a full day of building. I could not tolerate 1.2 extra seconds of silence for twenty minutes of actually using it.&lt;/p&gt;

&lt;p&gt;That taught me something I now treat as a rule for conversational interfaces. Response latency beats voice beauty. Users say they want natural. Their patience says they want fast. Watch the patience, not the words.&lt;/p&gt;

&lt;p&gt;A small aside. I am Nigerian, and I genuinely wanted a Nigerian accented voice for this. There is a lovely project called &lt;a href="https://huggingface.co/saheedniyi/YarnGPT" rel="noopener noreferrer"&gt;YarnGPT&lt;/a&gt; that does exactly this, built by a young Nigerian engineer whose work I genuinely admire. But it uses an autoregressive language model architecture for text to speech, which my CPU simply cannot run in real time. It is on my someday list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson Three: A Voice Agent Is a Pipeline, and Every Stage Must Overlap
&lt;/h2&gt;

&lt;p&gt;The single biggest perceived speed win of the whole weekend had nothing to do with which model I chose.&lt;/p&gt;

&lt;p&gt;Early on, Mabara waited for Claude's complete response before speaking a single word. For a long answer that meant thirty seconds of silence, followed by an entire essay all at once. Unusable.&lt;/p&gt;

&lt;p&gt;The fix is treating the whole thing as a pipeline. Claude streams text token by token. The moment a full sentence exists, it gets sent to the speech synthesizer. The moment that sentence's audio exists, playback starts, while the second sentence is still being synthesized and the third sentence is still being generated by Claude. Three stages, all running simultaneously.&lt;/p&gt;

&lt;p&gt;Speech now starts after the first sentence instead of after the entire response. The total time to finish is the same. The felt experience is completely different.&lt;/p&gt;

&lt;p&gt;The same thinking applied everywhere else in the system. The microphone never fully closes. It keeps a rolling pre-roll buffer, because opening the device only after the key is pressed clips your first syllable and ruins transcription. Sentences get batched into single synthesis calls when the queue backs up, because every call carries fixed overhead regardless of length. And every piece of queued audio carries an epoch number, so when you interrupt Mabara mid-sentence, which you do by simply holding the talk key again, it shuts up within 0.2 seconds and any stale audio from the previous epoch gets silently discarded instead of playing awkwardly after you have already started talking.&lt;/p&gt;

&lt;p&gt;None of this is exotic engineering. It is simply accepting that in real-time systems, waiting for anything to finish completely before starting the next thing is almost always the wrong design. At least in my experience.&lt;/p&gt;

&lt;p&gt;Lesson Four: If the Interface Is Trust, Then Engineer the Trust&lt;/p&gt;

&lt;p&gt;A voice agent that edits your code has a unique problem. You cannot see what it is about to do before it does it. The entire interface is confidence itself. So the safety model became the part of Mabara I am proudest of.&lt;/p&gt;

&lt;p&gt;Reads are free, no approval needed. But every file edit and every shell command gets spoken out loud, something like "I would like to edit the file page.tsx, do you approve?", and requires a verbal yes from me. Saying "yes for the whole task" approves the rest of that task's edits, because approving fifteen individual edits one by one is misery. Shell commands always ask, every single time, because that is where the truly unrecoverable mistakes live.&lt;/p&gt;

&lt;p&gt;Underneath all of this, git does the real safety work. Mabara refuses to edit anything outside a git repository. The first approved edit of any task automatically triggers a checkpoint. Saying "revert that" restores everything the last task touched, including restoring my own uncommitted files from a backup rather than deleting them, which is an edge case that would have destroyed real work in the first week if I had not caught it. Saying "commit this" turns a good task into a proper git commit, containing only the files that task actually touched.&lt;/p&gt;

&lt;p&gt;I learned the trust lesson from the model side too, not just the tooling side. I tried running Mabara on a smaller, cheaper model to save on cost. Within an hour it confidently told me my backend was built on a PDF generation library. The real backend was FastAPI, plainly declared in requirements.txt, a file it apparently had not bothered to read. It answered from vibes and documentation prose instead of actual code.&lt;/p&gt;

&lt;p&gt;The fix was partly a prompt rule: code is the truth, documentation is intentions, verify before answering. But the deeper lesson stuck with me. A wrong answer delivered confidently in a warm human voice is the most expensive output a tool can produce. I moved back to the larger model as the default. Speed is negotiable. Trust is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Part
&lt;/h2&gt;

&lt;p&gt;Mabara was pair built with Claude Code. The AI wrote most of the actual lines of code. I want to be straightforward about that, because I think the genuinely interesting part of this story is the division of labor, not the line count.&lt;/p&gt;

&lt;p&gt;Every decision in this project was mine. Which benchmarks to run. Which numbers to actually believe. When to abandon two hours of integration work because twenty minutes of real use disagreed with it. The AI never once pushed back on the int8 hype or the distil-Whisper hype. The benchmarks did. I chose to run them and I chose to listen to what they said.&lt;/p&gt;

&lt;p&gt;I came away from this weekend thinking that this is what engineering looks like now. The typing is cheap. The judgment is the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;I built Mabara because I wanted to test a belief I hold, that voice is going to become a real interface layer for software, not a novelty. And I wanted to get my hands genuinely dirty with local models instead of just reading about them.&lt;/p&gt;

&lt;p&gt;Both things happened. I now understand the practical tradeoffs of local speech models in a way no article could have taught me. And I have a small, honest proof that voice-first tooling is buildable on modest hardware if you are willing to measure everything and trust nothing you have not tested yourself.&lt;/p&gt;

&lt;p&gt;Mabara is open source on&lt;a href="https://github.com/zikorachukwuka" rel="noopener noreferrer"&gt; my GitHub&lt;/a&gt;. One Python file, one process, five threads, no framework. Every default in it won a fight on my specific laptop, which means on your machine some of them might lose. So the losers are all still there behind flags, waiting for your benchmarks to decide.&lt;/p&gt;

&lt;p&gt;Have you ever benchmarked a "known" optimization and watched it lose? Or built something voice driven yourself? I would genuinely love to hear what surprised you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>An Auth Story...</title>
      <dc:creator>ZIKORA CHUKWUKA</dc:creator>
      <pubDate>Fri, 26 Jun 2026 01:35:07 +0000</pubDate>
      <link>https://dev.to/zikthemaker/an-auth-story-42hf</link>
      <guid>https://dev.to/zikthemaker/an-auth-story-42hf</guid>
      <description>&lt;p&gt;I love system design. I love geeking out about the tiny decisions that make software great — for me, for those I work with, and most of all for my users. &lt;/p&gt;

&lt;p&gt;My biggest obsession as a budding engineer is ensuring that the products I work on actually work as expected, every time, no matter the edge case. &lt;/p&gt;

&lt;p&gt;I am also pretty obsessed about security.&lt;/p&gt;

&lt;p&gt;I mean, what is the use of software if it is unsafe to use or loses money when it gets taken down? If I can do anything to secure the software I work on, I would.&lt;/p&gt;

&lt;p&gt;Security is something I want to get better at, and I believe talking about it and sharing my work and learnings publicly is one way to do just that. So that is the purpose of this article.&lt;/p&gt;

&lt;p&gt;I want to share a concept I learned while implementing Auth as the frontend engineer at an AI startup.&lt;/p&gt;

&lt;p&gt;This is my Auth story.&lt;/p&gt;




&lt;p&gt;I build my frontends with Next.js.&lt;/p&gt;

&lt;p&gt;It basically chose me. It felt like the best option while I was starting out.&lt;/p&gt;

&lt;p&gt;It is an elegant framework for a one-man team. I love the idea of having both my frontend and backend within the same codebase. I love a single deployment pipeline. I love shipping fast without reinventing the wheel.&lt;/p&gt;

&lt;p&gt;So when the opportunity to join a lean startup team came, choosing Next.js was a no-brainer. It meant I could build fast, deploy faster, and implement solid frontend architecture patterns and security as a one-man frontend team without getting overwhelmed.&lt;/p&gt;

&lt;p&gt;Working on this project pushed me to think more carefully about something I had always glossed over: how authentication tokens are actually stored and protected in the browser. The answer to that question is cookies, and specifically, HttpOnly cookies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwf5xi21pnvzf8dgrw78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwf5xi21pnvzf8dgrw78.png" alt="Black and white illustration of HTTPOnly Cookie" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's talk about Cookies. Specifically HttpOnly Cookies. What they are, why they are useful, when they are useful, and specifically why I used them in my project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; I am not an expert on this topic. I am only sharing from my personal experience building real projects. Please feel free to give me feedback. I am here to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what are Cookies?
&lt;/h2&gt;

&lt;p&gt;Cookies are technically key value pairs stored in a browser. For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sessionId = abc123xyz&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To use an analogy, imagine you drank coffee several times a day at a coffee shop close to your home or office, and every time you visit, the barista asks you the same questions: your name, your preferences, and so on.&lt;/p&gt;

&lt;p&gt;You are not just a casual visitor, remember? You are a regular, and several times a day at that. I am sure you would be frustrated every single time. I know I would. Can you not just write my name and preferences down or something!&lt;/p&gt;

&lt;p&gt;Well, that is the same thing that happens when a user visits your app in their browser. By their very nature, HTTP requests are stateless. This means they carry no context about who is making them or where the request is coming from. So in the eyes of your server, your best and most regular users are just another request.&lt;/p&gt;

&lt;p&gt;Your APIs do not know that a particular request is coming from the same user, so they treat every single request like what it is: just a request. Your users would have to manually authenticate every time they take an action on the frontend that sends a HTTP request to your server.&lt;/p&gt;

&lt;p&gt;Cookies help solve this problem by giving the browser a way to tell the server that the HTTP requests are coming from the same logged-in user. So the user authenticates once and can use your app without having to re-authenticate every time their action sends a request to the server.&lt;/p&gt;

&lt;p&gt;Cookies are not the only way to solve this problem. Most developers also use local storage:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;localStorage.setItem('token', 'abc123xyz')&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;However, local Storage has its security concerns. It is readable by JavaScript, which means it is exposed to XSS attacks as are regular cookies. &lt;/p&gt;

&lt;p&gt;When it comes to server calls and HTTP requests, cookies are actually the more suitable option, and as you will see, we can make them significantly more secure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem with Cookies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Regular cookies, as I established, are just key value pairs stored in a browser. And because they are stored in a browser they are exposed and can be read by anyone with access to it, including a malicious website.&lt;/p&gt;

&lt;p&gt;You can open your browser console right now and type:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;document.cookie&lt;/code&gt;  &lt;/p&gt;

&lt;p&gt;To see all your stored cookies.&lt;/p&gt;

&lt;p&gt;Cookies can store a number of things. They are basically key value pairs, so you can put whatever string you want in them. They are used to store authentication and session tokens, preferences like dark mode and language, and tracking or analytics data.&lt;/p&gt;

&lt;p&gt;The most common use case though is storing session tokens, and that is the whole reason for this article.&lt;/p&gt;

&lt;p&gt;You set it up once via:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Set-Cookie: sessionId=abc123xyz&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And from then on, the browser sends it back automatically with every request:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cookie: sessionId=abc123xyz&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This means that cookies, while convenient, create a risk surface that makes them a security concern.&lt;/p&gt;

&lt;p&gt;In reality, anyone who gets hold of your user's token can authenticate as them and perform actions on their behalf. This vulnerability is the foundation of a very common attack called cross-site scripting (XSS), where a malicious site can read and steal your user's token and do whatever they want on your app acting as that user. Pretty scary.&lt;/p&gt;

&lt;p&gt;HTTPOnly cookies solve this by adding a flag. A simple instruction that tells the browser one rule: this cookie is for HTTP requests only. JavaScript is not allowed to touch it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Set-Cookie: sessionId=abc123xyz; HttpOnly&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So when an attack runs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;document.cookie // ""  ← the HttpOnly cookie is completely invisible&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Nothing happens. The cookie is invisible but still gets sent with each HTTP request. Sweet!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A quick Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HTTPOnly Cookie is not a silver bullet. While it solves the XSS problem sufficiently, it still leaves two very common cookie vulnerabilities that a serious attacker can exploit: attacks over insecure connections (HTTP instead of HTTPS) and cross-site request forgery (CSRF).&lt;/p&gt;

&lt;p&gt;We can address both using two additional flags:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Set-Cookie: sessionId=abc123xyz; HttpOnly; Secure; SameSite=Lax&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The Secure flag ensures the cookie is only ever sent over HTTPS. The &lt;code&gt;SameSite=Lax&lt;/code&gt; flag helps block CSRF attacks by restricting when the browser sends the cookie across sites. If you want to be even stricter, &lt;code&gt;SameSite=Strict&lt;/code&gt; is an option, though it can affect user experience in some cases. &lt;code&gt;Lax&lt;/code&gt; is a sensible default for most apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it all work in Next.js
&lt;/h2&gt;

&lt;p&gt;This is where it all comes together, the juicy part if you will.&lt;/p&gt;

&lt;p&gt;Going forward I try to explain my implementation of these concepts in a recent project, the unique features in Next.js that made my decisions possible, and my specific constraints.&lt;/p&gt;

&lt;p&gt;For a bit of context: the frontend I am building is for an AI startup. We are building an AI agent for unified customer communication. Our stack of choice is roughly Next.js for the frontend and FastAPI for the backend. We are a small team trying to ship an MVP fast. I lead development on the frontend, consuming a robust FastAPI backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The constraint and opportunity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the many design decisions we made was to handle cookie setup on the frontend. This is in addition to largely limiting how much the client directly calls the FastAPI server.&lt;/p&gt;

&lt;p&gt;While I can't fully go into our system design here, Next.js offers features that are uniquely suited to these constraints. I will be going deep into my frontend implementation of the Auth flow specifically.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Next.js Primer
&lt;/h2&gt;

&lt;p&gt;To fully follow along there are a few things you need to understand about Next.js and its unique architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Next.js Server&lt;/strong&gt; Next.js (specifically the modern app router) is basically a running Node.js process that receives every request, does server-side work and sends back a response. It is a real backend just one that happens to also serve your react UI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Server Actions&lt;/strong&gt; A newer Next.js feature that lets you write a function on the Next.js server and call it directly from a form in your UI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Middleware (Proxy.ts)&lt;/strong&gt; Code that runs before every request hits your pages. If you are on Next.js 15 or earlier, this file was called &lt;code&gt;middleware.ts&lt;/code&gt;. The rename happened in Next.js 16 to better reflect what the file actually does: it sits at the network boundary and proxies requests before they reach your app. The logic is the same either way.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;4.&lt;strong&gt;Server components vs Client components&lt;/strong&gt; Server components are React components that never ship their code to the browser. They run on the server and can directly access databases and APIs. Client components run in the browser. Every component in Next.js is a server component by default.&lt;/p&gt;

&lt;p&gt;5.&lt;strong&gt;Route Handlers&lt;/strong&gt; Custom request handlers that allow you to build custom RESTful API endpoints in Next.js &lt;/p&gt;

&lt;p&gt;These five concepts unlock a lot of benefits and are what allow me to implement my Auth flow within the defined constraints.&lt;br&gt;
Here is the breakdown:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5nrvtbpvymyjw9pptqs3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5nrvtbpvymyjw9pptqs3.png" alt="Auth flow diagram in Next.js" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To elaborate: I am designing this flow to handle cookie setting on the frontend, hide the FastAPI base URL from the browser, and largely isolate my backend from external interaction. This creates a server-to-server setup instead of a client-to-server one. Next.js gives me all the pieces to make this possible.&lt;/p&gt;

&lt;p&gt;When a new user creates an account and logs in for the first time, the form's &lt;code&gt;action&lt;/code&gt; calls a &lt;code&gt;loginAction&lt;/code&gt; server action. This server action takes the user's credentials and forwards them to FastAPI directly (server to server), so the browser never talks to FastAPI at all. FastAPI validates the credentials and returns a token string to the server action, which then writes the token into a cookie marked &lt;code&gt;httpOnly: true&lt;/code&gt;. The browser receives and stores the cookie but can never read it with JavaScript.&lt;/p&gt;

&lt;p&gt;On every subsequent request, the proxy checks for the cookie before any page loads. If the cookie is missing, it redirects to &lt;code&gt;/login&lt;/code&gt;. If present, it lets the request through. I want to be clear that I am using the proxy for a lightweight presence check here, not as a full authentication solution. The real auth logic lives on the FastAPI side.&lt;/p&gt;

&lt;p&gt;Once through, the Next.js server handles the request in one of two ways depending on what is being loaded. For pages and data that can be fetched at render time, server components read the cookie directly and call FastAPI server to server. For data that needs to be fetched live on the client (in my case, a live inbox that polls every 30 seconds), client components call a Route Handler instead. The Route Handler runs on the Next.js server, reads the cookie, calls FastAPI, and returns the response to the client. The token never leaves the server either way.&lt;/p&gt;

&lt;p&gt;So there you go. My not so complicated implementation of HttpOnly Cookies with Next.js within my specific constraints.&lt;/p&gt;

&lt;p&gt;I acknowledge there are simpler ways to achieve this, but I want to say again that every case is unique and I implemented what made sense for mine.&lt;/p&gt;

&lt;p&gt;Have you handled Auth differently in your Next.js projects? I'd genuinely love to know what you did differently and why.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>webdev</category>
      <category>security</category>
      <category>career</category>
    </item>
  </channel>
</rss>
