<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Jameel Ahmad</title>
      <link>https://zola-paper.pages.dev</link>
      <description>Full-stack engineer (Next.js). AI &amp; medical imaging research, open source, and LucidHire—portfolio and blog by Jameel Ahmad.</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://zola-paper.pages.dev/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Wed, 22 Apr 2026 02:22:00 +0500</lastBuildDate>
      <item>
          <title>Record any Privacy Protected Screen like Snapchat, Netflix and other Social &amp; OTT Platforms</title>
          <pubDate>Wed, 22 Apr 2026 02:22:00 +0500</pubDate>
          <author>Jameel Ahmad</author>
          <link>https://zola-paper.pages.dev/posts/record-privacy-protected-screen-fedora-boxes-obs/</link>
          <guid>https://zola-paper.pages.dev/posts/record-privacy-protected-screen-fedora-boxes-obs/</guid>
          <description xml:base="https://zola-paper.pages.dev/posts/record-privacy-protected-screen-fedora-boxes-obs/">&lt;p&gt;I was casually scrolling Snapchat one night when a friend sent me a private video marked “Not allowed to share.” The app promised it would notify the sender if I tried to screen-record it. I got curious — does this protection actually hold up?&lt;&#x2F;p&gt;
&lt;p&gt;Being a Fedora guy, I decided to test it the Linux way. What I discovered was surprisingly simple and works on Snapchat, Netflix, Disney+, Prime Video, and pretty much any social or OTT platform that tries to block recording. No root, no cracked apps, no shady tools — just a virtual machine and OBS.&lt;&#x2F;p&gt;
&lt;p&gt;Let me walk you through the whole story and turn it into a complete tutorial so you can try it yourself (for testing and educational purposes only, of course!).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;drm-protection-disaster.png&quot; alt=&quot;Retro style pixel art of a Linux virtual machine recording Snapchat&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-this-trick-even-works-the-simple-explanation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-this-trick-even-works-the-simple-explanation&quot; aria-label=&quot;Anchor link for: why-this-trick-even-works-the-simple-explanation&quot;&gt;Why This Trick Even Works (The Simple Explanation)&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Most DRM and “anti-recording” protections (like Snapchat’s notification system or Netflix’s screen-block) only look inside the operating system where the app is running. They check for screen-recording APIs, overlays, or running processes in that same environment.&lt;&#x2F;p&gt;
&lt;p&gt;When you run the app inside a virtual machine (the “guest” OS), it has zero knowledge of what’s happening on your real computer (the “host” or “master” OS). The guest thinks it’s on a normal device. Your recording tool runs completely outside the guest, so the DRM never sees it. It’s like watching someone through a one-way mirror — they have no idea you’re there.&lt;&#x2F;p&gt;
&lt;p&gt;That’s the whole magic. Now let’s do it step by step.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-you-ll-need&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-you-ll-need&quot; aria-label=&quot;Anchor link for: what-you-ll-need&quot;&gt;What You’ll Need&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;A Fedora Linux machine (I used Fedora 43 Workstation)&lt;&#x2F;li&gt;
&lt;li&gt;GNOME Boxes (a virtualization tool)&lt;&#x2F;li&gt;
&lt;li&gt;OBS Studio (free and excellent screen recorder)&lt;&#x2F;li&gt;
&lt;li&gt;About 15 minutes of your time&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;step-by-step-tutorial-record-any-protected-screen&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#step-by-step-tutorial-record-any-protected-screen&quot; aria-label=&quot;Anchor link for: step-by-step-tutorial-record-any-protected-screen&quot;&gt;Step-by-Step Tutorial: Record Any Protected Screen&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;1-prepare-your-host-fedora-system&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-prepare-your-host-fedora-system&quot; aria-label=&quot;Anchor link for: 1-prepare-your-host-fedora-system&quot;&gt;1. Prepare Your Host Fedora System&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Open a terminal and install the virtualization tools and OBS:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; dnf install gnome-boxes qemu-kvm libvirt virt-manager obs-studio&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; systemctl enable --now libvirtd&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;2-create-an-isolated-virtual-machine&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#2-create-an-isolated-virtual-machine&quot; aria-label=&quot;Anchor link for: 2-create-an-isolated-virtual-machine&quot;&gt;2. Create an Isolated Virtual Machine&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;GNOME Boxes&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Click &lt;strong&gt;Create a new virtual machine&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Choose the latest Fedora Workstation ISO (or let Boxes download it for you).&lt;&#x2F;li&gt;
&lt;li&gt;Give the VM 4 GB RAM and 4 CPU cores (plenty for Snapchat or Netflix).&lt;&#x2F;li&gt;
&lt;li&gt;Click &lt;strong&gt;Create&lt;&#x2F;strong&gt; and let it install a fresh Fedora inside the window.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Once the VM boots up, you now have a completely separate Linux desktop running inside your main computer.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-install-the-app-inside-the-virtual-machine&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#3-install-the-app-inside-the-virtual-machine&quot; aria-label=&quot;Anchor link for: 3-install-the-app-inside-the-virtual-machine&quot;&gt;3. Install the App Inside the Virtual Machine&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Inside the guest VM, install the app you want to record. For Snapchat (official Flatpak):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; dnf install flatpak&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;flatpak&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; remote-add --if-not-exists flathub https:&#x2F;&#x2F;flathub.org&#x2F;repo&#x2F;flathub.flatpakrepo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;flatpak&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; install flathub org.snapchat.Snapchat&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For Netflix or any other platform, just open Firefox inside the VM and go to the website.&lt;&#x2F;p&gt;
&lt;p&gt;Log in and open the private&#x2F;protected video or content you want to capture.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-record-from-the-host-os-using-obs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#4-record-from-the-host-os-using-obs&quot; aria-label=&quot;Anchor link for: 4-record-from-the-host-os-using-obs&quot;&gt;4. Record from the Host OS Using OBS&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Now switch back to your real (host) Fedora desktop:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;OBS Studio&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Create a new Scene.&lt;&#x2F;li&gt;
&lt;li&gt;Add a new source → &lt;strong&gt;Window Capture&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Select the GNOME Boxes window (your virtual machine).&lt;&#x2F;li&gt;
&lt;li&gt;(Optional) Turn on “Capture Cursor” if you want the mouse pointer.&lt;&#x2F;li&gt;
&lt;li&gt;Set your recording quality (I use MP4, 1080p or 1440p, 8000–15000 kbps bitrate).&lt;&#x2F;li&gt;
&lt;li&gt;Hit &lt;strong&gt;Start Recording&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Go back to the VM, play the private Snapchat video &#x2F; Netflix show &#x2F; whatever protected content.&lt;&#x2F;p&gt;
&lt;p&gt;Watch what happens:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The video plays in full quality inside the VM.&lt;&#x2F;li&gt;
&lt;li&gt;OBS captures everything perfectly on your host.&lt;&#x2F;li&gt;
&lt;li&gt;The sender or the platform gets &lt;strong&gt;zero&lt;&#x2F;strong&gt; notification or warning.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I tried it with a Snapchat private snap — my friend never got the “screen recorded” message. Same thing with Netflix: no black screen, no DRM block.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-save-and-stop&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#5-save-and-stop&quot; aria-label=&quot;Anchor link for: 5-save-and-stop&quot;&gt;5. Save and Stop&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;When you’re done, stop the recording in OBS. Your captured video is saved on the host machine, completely outside the VM’s world.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;this-works-on-almost-any-platform&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#this-works-on-almost-any-platform&quot; aria-label=&quot;Anchor link for: this-works-on-almost-any-platform&quot;&gt;This Works on Almost Any Platform&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I’ve personally tested it on:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Snapchat private snaps and stories&lt;&#x2F;li&gt;
&lt;li&gt;Facebook &amp;amp; Instagram&lt;&#x2F;li&gt;
&lt;li&gt;Netflix (web version)&lt;&#x2F;li&gt;
&lt;li&gt;Disney+&lt;&#x2F;li&gt;
&lt;li&gt;Amazon Prime Video&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;As long as the protection lives inside the guest OS, it stays blind to the host recorder.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>How I Fixed Testsprite Tests Getting Blocked by Clerk Auth in Next.js</title>
          <pubDate>Sat, 18 Apr 2026 01:43:00 +0500</pubDate>
          <author>Jameel Ahmad</author>
          <link>https://zola-paper.pages.dev/posts/testsprite-nextjs-clerk-auth/</link>
          <guid>https://zola-paper.pages.dev/posts/testsprite-nextjs-clerk-auth/</guid>
          <description xml:base="https://zola-paper.pages.dev/posts/testsprite-nextjs-clerk-auth/">&lt;p&gt;I was working on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.lucidhire.io&quot;&gt;LucidHire&lt;&#x2F;a&gt; and decided to run a full test sweep using Testsprite — both backend API tests and frontend E2E tests. Nearly everything came back blocked. The backend tests were hitting &lt;strong&gt;401 Unauthorized&lt;&#x2F;strong&gt; on every API route. The frontend tests were failing even earlier: Clerk’s sign-in form was rejecting the test credentials entirely, so the runner couldn’t even log in to reach the pages it needed to test.&lt;&#x2F;p&gt;
&lt;p&gt;Two separate problems, two separate fixes. This post covers both.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#table-of-contents&quot; aria-label=&quot;Anchor link for: table-of-contents&quot;&gt;Table of contents&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;the-problem-everything-is-blocked&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-problem-everything-is-blocked&quot; aria-label=&quot;Anchor link for: the-problem-everything-is-blocked&quot;&gt;The Problem: Everything Is Blocked&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The two failure modes look different but share the same root cause — Clerk has no idea these requests are coming from a test runner and not a real user.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Backend tests&lt;&#x2F;strong&gt; hit your API routes directly. Your Next.js middleware runs &lt;code&gt;clerkMiddleware()&lt;&#x2F;code&gt;, which checks for a valid session token. Since Testsprite makes raw HTTP calls with no browser session and no Bearer token, Clerk blocks the request before it ever reaches your route handler.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GET &#x2F;api&#x2F;jobs → 401 Unauthorized&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;POST &#x2F;api&#x2F;candidates → 401 Unauthorized&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;PATCH &#x2F;api&#x2F;interviews&#x2F;:id → 401 Unauthorized&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Frontend tests&lt;&#x2F;strong&gt; have a different problem. Testsprite launches a browser, navigates to your sign-in page, and tries to log in with test credentials. But if those credentials are a made-up email and password, Clerk will either reject them or — if email verification is enabled — get stuck waiting for a verification code that never arrives. The test runner sits at the login screen forever and never reaches the actual UI it needs to test.&lt;&#x2F;p&gt;
&lt;p&gt;Here is what the Testsprite dashboard looked like before any fix — almost every frontend test case showing &lt;strong&gt;Blocked&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;testsprite-before.png&quot; alt=&quot;Testsprite test results before the fix — nearly all test cases showing Blocked status&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-not-just-bypass-auth&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-not-just-bypass-auth&quot; aria-label=&quot;Anchor link for: why-not-just-bypass-auth&quot;&gt;Why Not Just Bypass Auth?&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The quick fix is to add a secret header check in your middleware and skip Clerk when it matches. That works for backend tests, but it has real downsides:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Your route handlers call &lt;code&gt;auth()&lt;&#x2F;code&gt; to get &lt;code&gt;userId&lt;&#x2F;code&gt;, &lt;code&gt;orgId&lt;&#x2F;code&gt;, etc. With a bypass, those calls return &lt;code&gt;null&lt;&#x2F;code&gt; or break entirely&lt;&#x2F;li&gt;
&lt;li&gt;You have to mock &lt;code&gt;auth()&lt;&#x2F;code&gt; separately, which adds more complexity&lt;&#x2F;li&gt;
&lt;li&gt;Your tests are no longer exercising the same code path real users hit&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;With a real token, none of that is a problem. The token passes through Clerk’s normal verification, &lt;code&gt;auth()&lt;&#x2F;code&gt; returns a real session object, and your route handlers run exactly as they would in production. That is the goal.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;part-1-fixing-backend-tests-with-the-clerk-backend-sdk&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#part-1-fixing-backend-tests-with-the-clerk-backend-sdk&quot; aria-label=&quot;Anchor link for: part-1-fixing-backend-tests-with-the-clerk-backend-sdk&quot;&gt;Part 1: Fixing Backend Tests with the Clerk Backend SDK&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Clerk’s Backend SDK lets you create sessions and generate signed JWTs for any user in your Clerk application — no browser, no OAuth dance, no cookie required.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-1-install-the-sdk&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#step-1-install-the-sdk&quot; aria-label=&quot;Anchor link for: step-1-install-the-sdk&quot;&gt;Step 1: Install the SDK&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;npm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; install @clerk&#x2F;backend&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;step-2-create-a-dedicated-test-user&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#step-2-create-a-dedicated-test-user&quot; aria-label=&quot;Anchor link for: step-2-create-a-dedicated-test-user&quot;&gt;Step 2: Create a Dedicated Test User&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Go to your &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;dashboard.clerk.com&quot;&gt;Clerk Dashboard&lt;&#x2F;a&gt;, open your application, and create a dedicated test user — something like &lt;code&gt;testsprite@yourdomain.com&lt;&#x2F;code&gt;. Copy the user’s ID from the dashboard (it looks like &lt;code&gt;user_2xyz...&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;Never use a real user’s ID for testing. A dedicated test user keeps things isolated and makes it easy to spot test-generated data in your database.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-3-add-environment-variables&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#step-3-add-environment-variables&quot; aria-label=&quot;Anchor link for: step-3-add-environment-variables&quot;&gt;Step 3: Add Environment Variables&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Add these to your &lt;code&gt;.env.local&lt;&#x2F;code&gt; and your CI environment:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CLERK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CLERK_TEST_USER_ID=user_2xxxxxxxxxxxxxxxxxxx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;step-4-write-the-global-setup-file&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#step-4-write-the-global-setup-file&quot; aria-label=&quot;Anchor link for: step-4-write-the-global-setup-file&quot;&gt;Step 4: Write the Global Setup File&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Create a global setup file that runs once before all your tests, generates a real JWT, and stores it in &lt;code&gt;process.env&lt;&#x2F;code&gt; so every test can access it.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; testsprite.setup.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; createClerkClient&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;@clerk&#x2F;backend&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; clerk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; createClerkClient&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  secretKey&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;CLERK_SECRET_KEY&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export async function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; setup&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Create a real Clerk session for the test user&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; clerk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sessions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;createSession&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    userId&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;CLERK_TEST_USER_ID&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Get a signed JWT from that session&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; jwt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; clerk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sessions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;getToken&lt;&#x2F;span&gt;&lt;span&gt;(session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;session_token&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Make it available to all test files&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;TEST_AUTH_TOKEN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; jwt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;✅ Clerk test token generated, expires:&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; Date&lt;&#x2F;span&gt;&lt;span&gt;(session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;expireAt))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;step-5-configure-testsprite-to-use-the-setup&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#step-5-configure-testsprite-to-use-the-setup&quot; aria-label=&quot;Anchor link for: step-5-configure-testsprite-to-use-the-setup&quot;&gt;Step 5: Configure Testsprite to Use the Setup&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Point Testsprite at your setup file in its config:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; testsprite.config.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; defineConfig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;testsprite&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; defineConfig&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  globalSetup&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;.&#x2F;testsprite.setup.ts&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  baseURL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;http:&#x2F;&#x2F;localhost:3000&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;step-6-use-the-token-in-your-test-requests&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#step-6-use-the-token-in-your-test-requests&quot; aria-label=&quot;Anchor link for: step-6-use-the-token-in-your-test-requests&quot;&gt;Step 6: Use the Token in Your Test Requests&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Now every test request just grabs the token from &lt;code&gt;process.env&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; tests&#x2F;api&#x2F;jobs.test.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; res&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;api&#x2F;jobs&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  method&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;GET&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  headers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Authorization&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; `Bearer &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;TEST_AUTH_TOKEN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    &amp;quot;Content-Type&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;application&#x2F;json&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(res&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;status)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;toBe&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;No bypasses, no mocks, no workarounds. Clerk sees a real Bearer token, verifies it, and your route handler gets a fully populated &lt;code&gt;auth()&lt;&#x2F;code&gt; context.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-this-looks-like-end-to-end&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-this-looks-like-end-to-end&quot; aria-label=&quot;Anchor link for: what-this-looks-like-end-to-end&quot;&gt;What This Looks Like End-to-End&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Global setup runs&lt;&#x2F;strong&gt; → SDK creates a Clerk session for your test user, fetches a signed JWT&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Token stored&lt;&#x2F;strong&gt; → &lt;code&gt;process.env.TEST_AUTH_TOKEN&lt;&#x2F;code&gt; is set for the entire test process&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Test runs&lt;&#x2F;strong&gt; → each request includes &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Clerk middleware&lt;&#x2F;strong&gt; → verifies the token, sets the session context, passes the request through&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Route handler&lt;&#x2F;strong&gt; → &lt;code&gt;auth()&lt;&#x2F;code&gt; returns &lt;code&gt;{ userId: &quot;user_2xyz...&quot;, ... }&lt;&#x2F;code&gt; just like in production&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Test assertion&lt;&#x2F;strong&gt; → checks the actual response from your real business logic&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;handling-token-expiry-in-long-test-runs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#handling-token-expiry-in-long-test-runs&quot; aria-label=&quot;Anchor link for: handling-token-expiry-in-long-test-runs&quot;&gt;Handling Token Expiry in Long Test Runs&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Clerk tokens expire after about an hour by default. For most test suites this is fine, but if you have very long-running pipelines you may want to refresh the token per file using a &lt;code&gt;beforeAll&lt;&#x2F;code&gt; block:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; tests&#x2F;api&#x2F;jobs.test.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; createClerkClient&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;@clerk&#x2F;backend&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; clerk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; createClerkClient&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; secretKey&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;CLERK_SECRET_KEY&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;beforeAll&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;async&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; clerk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sessions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;createSession&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    userId&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;CLERK_TEST_USER_ID&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; jwt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; clerk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sessions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;getToken&lt;&#x2F;span&gt;&lt;span&gt;(session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;session_token&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;TEST_AUTH_TOKEN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; jwt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;one-thing-to-watch-out-for&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#one-thing-to-watch-out-for&quot; aria-label=&quot;Anchor link for: one-thing-to-watch-out-for&quot;&gt;One Thing to Watch Out For&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;clerk.sessions.createSession()&lt;&#x2F;code&gt; requires a &lt;strong&gt;Clerk Pro plan or above&lt;&#x2F;strong&gt;. If you call it on a free plan, you will get a &lt;code&gt;402 Payment Required&lt;&#x2F;code&gt; error. Also make sure &lt;code&gt;CLERK_SECRET_KEY&lt;&#x2F;code&gt; starts with &lt;code&gt;sk_test_...&lt;&#x2F;code&gt; — the publishable key (&lt;code&gt;pk_test_...&lt;&#x2F;code&gt;) is frontend-only and will not work here.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;part-2-fixing-frontend-tests-with-clerk-test-credentials&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#part-2-fixing-frontend-tests-with-clerk-test-credentials&quot; aria-label=&quot;Anchor link for: part-2-fixing-frontend-tests-with-clerk-test-credentials&quot;&gt;Part 2: Fixing Frontend Tests with Clerk Test Credentials&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Frontend tests are a different challenge. Testsprite spins up a real browser and navigates through your actual UI. That means it has to go through your sign-in page and successfully authenticate before it can test anything behind the auth wall.&lt;&#x2F;p&gt;
&lt;p&gt;The problem is that Clerk’s email verification flow — OTP codes, magic links — is designed to be interactive. A test runner can’t check a real inbox.&lt;&#x2F;p&gt;
&lt;p&gt;Clerk solves this with &lt;strong&gt;special test email addresses&lt;&#x2F;strong&gt; that bypass real email delivery entirely and accept a fixed, known verification code.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-clerk-test-credentials-work&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-clerk-test-credentials-work&quot; aria-label=&quot;Anchor link for: how-clerk-test-credentials-work&quot;&gt;How Clerk Test Credentials Work&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Any email address in the format:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[name]+clerk_test@example.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…is recognized by Clerk as a test address. When this address triggers an email verification, Clerk skips sending a real email and instead accepts &lt;strong&gt;&lt;code&gt;424242&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; as the OTP code every single time.&lt;&#x2F;p&gt;
&lt;p&gt;This works in your development and staging environments where you are using a Clerk test API key (&lt;code&gt;sk_test_...&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;pk_test_...&lt;&#x2F;code&gt;). It does &lt;strong&gt;not&lt;&#x2F;strong&gt; work in production.&lt;&#x2F;p&gt;
&lt;p&gt;So your test credentials look like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Email:             testsprite+clerk_test@example.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Password:          YourStrongPassword123!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Verification code: 424242&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The name part (&lt;code&gt;testsprite&lt;&#x2F;code&gt;) can be anything valid. The &lt;code&gt;+clerk_test@example.com&lt;&#x2F;code&gt; suffix is what activates the special behavior.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-1-register-the-test-user-in-your-app&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#step-1-register-the-test-user-in-your-app&quot; aria-label=&quot;Anchor link for: step-1-register-the-test-user-in-your-app&quot;&gt;Step 1: Register the Test User in Your App&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Before your test suite runs, you need this user to exist in Clerk. You can either create them manually through your app’s sign-up flow once, or do it programmatically in your global setup:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; testsprite.setup.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; createClerkClient&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;@clerk&#x2F;backend&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; clerk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; createClerkClient&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  secretKey&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;CLERK_SECRET_KEY&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export async function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; setup&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; testEmail&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;testsprite+clerk_test@example.com&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; testPassword&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;CLERK_TEST_PASSWORD&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Check if the test user already exists&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; data&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; existing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; clerk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;users&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;getUserList&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    emailAddress&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [testEmail]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (existing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;length&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span&gt; clerk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;users&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;createUser&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      emailAddress&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [testEmail]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      password&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; testPassword&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      firstName&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;Test&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      lastName&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;User&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;✅ Test user created&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;✅ Test user already exists&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add the password to your &lt;code&gt;.env.local&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CLERK_TEST_PASSWORD=YourStrongPassword123!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;step-2-configure-testsprite-with-the-test-credentials&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#step-2-configure-testsprite-with-the-test-credentials&quot; aria-label=&quot;Anchor link for: step-2-configure-testsprite-with-the-test-credentials&quot;&gt;Step 2: Configure Testsprite with the Test Credentials&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Pass the test credentials to Testsprite so it knows what to type into the sign-in form:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; testsprite.config.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; defineConfig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;testsprite&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; defineConfig&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  globalSetup&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;.&#x2F;testsprite.setup.ts&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  baseURL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;http:&#x2F;&#x2F;localhost:3000&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  auth&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    email&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;testsprite+clerk_test@example.com&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    password&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;CLERK_TEST_PASSWORD&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    otpCode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;424242&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;step-3-what-happens-during-sign-in&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#step-3-what-happens-during-sign-in&quot; aria-label=&quot;Anchor link for: step-3-what-happens-during-sign-in&quot;&gt;Step 3: What Happens During Sign-In&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;With these credentials in place, here is what the frontend test flow looks like:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Testsprite opens your app in a browser and navigates to the sign-in page&lt;&#x2F;li&gt;
&lt;li&gt;It fills in &lt;code&gt;testsprite+clerk_test@example.com&lt;&#x2F;code&gt; and your test password&lt;&#x2F;li&gt;
&lt;li&gt;Clerk prompts for the verification code&lt;&#x2F;li&gt;
&lt;li&gt;Testsprite enters &lt;code&gt;424242&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Clerk accepts it and creates a real authenticated session&lt;&#x2F;li&gt;
&lt;li&gt;The browser is now logged in and Testsprite can navigate to any protected page&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;From here, all your frontend tests run against a fully authenticated session — the same way a real user would experience your app.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-424242-always-works&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-424242-always-works&quot; aria-label=&quot;Anchor link for: why-424242-always-works&quot;&gt;Why &lt;code&gt;424242&lt;&#x2F;code&gt; Always Works&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Clerk’s test email format is a convention baked into their SDK. When Clerk sees a &lt;code&gt;+clerk_test@example.com&lt;&#x2F;code&gt; address during a test-mode session, it short-circuits the email delivery system and registers &lt;code&gt;424242&lt;&#x2F;code&gt; as the valid OTP for that request. No email is sent. No inbox to check. The code is always the same and always valid.&lt;&#x2F;p&gt;
&lt;p&gt;This is analogous to how Stripe uses &lt;code&gt;4242 4242 4242 4242&lt;&#x2F;code&gt; as a test card number — a fixed, well-known value that the system recognizes as being in test mode and treats accordingly.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-full-picture&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-full-picture&quot; aria-label=&quot;Anchor link for: the-full-picture&quot;&gt;The Full Picture&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Once both fixes are in place, your test suite looks like this:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Test type&lt;&#x2F;th&gt;&lt;th&gt;Auth mechanism&lt;&#x2F;th&gt;&lt;th&gt;What Clerk sees&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Backend API tests&lt;&#x2F;td&gt;&lt;td&gt;Bearer JWT from Backend SDK&lt;&#x2F;td&gt;&lt;td&gt;Real session token, full &lt;code&gt;auth()&lt;&#x2F;code&gt; context&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Frontend E2E tests&lt;&#x2F;td&gt;&lt;td&gt;Sign-in via &lt;code&gt;+clerk_test&lt;&#x2F;code&gt; email + &lt;code&gt;424242&lt;&#x2F;code&gt; OTP&lt;&#x2F;td&gt;&lt;td&gt;Real browser session, full UI context&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Neither approach involves bypassing auth, mocking Clerk, or special-casing your middleware. Both test the real code path.&lt;&#x2F;p&gt;
&lt;p&gt;After setting this up across LucidHire’s test suite, every test that was previously blocked by auth started running cleanly. The backend tests hit real route handlers with real session data. The frontend tests navigated through the actual sign-in UI and landed on authenticated pages. Here is the same dashboard after the fix — the wall of &lt;strong&gt;Blocked&lt;&#x2F;strong&gt; results replaced almost entirely by green &lt;strong&gt;Pass&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;testsprite-after.png&quot; alt=&quot;Testsprite test results after the fix — nearly all test cases now showing Pass status&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That is exactly what a good test suite should do.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Your &quot;Server Component&quot; Is Running on the Client (And You Have No Idea)</title>
          <pubDate>Tue, 14 Apr 2026 01:00:00 +0500</pubDate>
          <author>Jameel Ahmad</author>
          <link>https://zola-paper.pages.dev/posts/server-component-runs-on-client/</link>
          <guid>https://zola-paper.pages.dev/posts/server-component-runs-on-client/</guid>
          <description xml:base="https://zola-paper.pages.dev/posts/server-component-runs-on-client/">&lt;p&gt;You write a component. No &lt;code&gt;&quot;use client&quot;&lt;&#x2F;code&gt; directive. You fetch data directly, maybe even do a &lt;code&gt;console.log&lt;&#x2F;code&gt; to confirm it’s running server-side. The log shows up — in your browser console.&lt;&#x2F;p&gt;
&lt;p&gt;That’s the moment. The “wait, what?” moment.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;server-component-runs-on-client.jpg&quot; alt=&quot;Scooby-Doo mask reveal meme: “Server Component” is actually “Runs on Client Side”&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Next.js App Router made Server Components the default, which is great. But that default comes with a catch: it’s surprisingly easy to write code you &lt;em&gt;think&lt;&#x2F;em&gt; is server-only but is actually being executed on the client. Here are the real cases where this happens.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#table-of-contents&quot; aria-label=&quot;Anchor link for: table-of-contents&quot;&gt;Table of contents&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;case-1-no-use-client-doesn-t-mean-server-component&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#case-1-no-use-client-doesn-t-mean-server-component&quot; aria-label=&quot;Anchor link for: case-1-no-use-client-doesn-t-mean-server-component&quot;&gt;Case 1: No “use client” Doesn’t Mean Server Component&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;The misconception:&lt;&#x2F;strong&gt; A component will render on the server simply because you didn’t add &lt;code&gt;&quot;use client&quot;&lt;&#x2F;code&gt; to the top of the file.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The reality:&lt;&#x2F;strong&gt; If you import and nest that component inside a parent that &lt;em&gt;is&lt;&#x2F;em&gt; a Client Component, it automatically inherits client behavior and runs in the browser.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; UserProfile.tsx — no &amp;quot;use client&amp;quot;, so it must be a Server Component, right?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; UserProfile&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;* sensitive render logic *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; Sidebar.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;use client&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; UserProfile&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;.&#x2F;UserProfile&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;&#x2F; ← now a Client Component&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; Sidebar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; setOpen&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;UserProfile&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;* running in the browser *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;UserProfile&lt;&#x2F;code&gt; had no directive, but because it’s &lt;em&gt;imported&lt;&#x2F;em&gt; inside a Client Component, it gets bundled into the client and runs there. Any server-only logic inside it — DB calls, secret env vars — silently breaks or gets exposed.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;&#x2F;strong&gt; Pass the Server Component as &lt;code&gt;children&lt;&#x2F;code&gt; instead of importing it directly.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; page.tsx (Server Component)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; Sidebar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;.&#x2F;Sidebar&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; UserProfile&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;.&#x2F;UserProfile&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; Page&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;Sidebar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;UserProfile&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;* still a Server Component *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;Sidebar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; Sidebar.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;use client&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; Sidebar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;({&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; setOpen&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Components passed as &lt;code&gt;children&lt;&#x2F;code&gt; or props are not pulled into the client boundary. They stay on the server.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;case-2-placing-use-client-too-high-in-the-tree&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#case-2-placing-use-client-too-high-in-the-tree&quot; aria-label=&quot;Anchor link for: case-2-placing-use-client-too-high-in-the-tree&quot;&gt;Case 2: Placing “use client” Too High in the Tree&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;The misconception:&lt;&#x2F;strong&gt; You put &lt;code&gt;&quot;use client&quot;&lt;&#x2F;code&gt; high up in your layout or page, assuming child components beneath it stay as Server Components since that’s the Next.js default.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The reality:&lt;&#x2F;strong&gt; The directive at the top of the tree creates a boundary that forces the &lt;em&gt;entire subtree beneath it&lt;&#x2F;em&gt; to be bundled and executed on the client, completely wiping out Server Component benefits for every child.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; app&#x2F;layout.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;use client&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;&#x2F; ← placed here to use a single useState&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; RootLayout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;({&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;theme&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; setTheme&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;light&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;* Every single component rendered here is now a Client Component *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        {&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Every page, every data-fetching component, every child that should be running on the server — is now running in the browser. You’ve opted your entire app out of Server Components with one misplaced directive.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;&#x2F;strong&gt; Push &lt;code&gt;&quot;use client&quot;&lt;&#x2F;code&gt; as deep as possible. If you only need interactivity for a theme toggle button, extract &lt;em&gt;just that button&lt;&#x2F;em&gt; into its own file and mark that file as &lt;code&gt;&quot;use client&quot;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; components&#x2F;ThemeToggle.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;use client&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; ThemeToggle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;theme&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; setTheme&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;light&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;button&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; onClick&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; setTheme&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;light&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;dark&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;light&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      Toggle&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;button&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; app&#x2F;layout.tsx — no &amp;quot;use client&amp;quot; needed here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; ThemeToggle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;@&#x2F;components&#x2F;ThemeToggle&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; RootLayout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;({&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;        &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;ThemeToggle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;* only this is a Client Component *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        {&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}      {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;* everything else stays on the server *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;case-3-not-separating-client-and-server-utils&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#case-3-not-separating-client-and-server-utils&quot; aria-label=&quot;Anchor link for: case-3-not-separating-client-and-server-utils&quot;&gt;Case 3: Not Separating Client and Server Utils&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;The misconception:&lt;&#x2F;strong&gt; You write utility functions for backend tasks — database queries, secret lookups — and assume they’ll safely stay on the server.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The reality:&lt;&#x2F;strong&gt; If you accidentally import one of those server utilities into a client component, Next.js bundles that sensitive logic and ships it directly to the user’s browser.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; lib&#x2F;utils.ts — one file for everything&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; formatDate&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;date&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Date&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;* safe, UI logic *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export async function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; getUserFromDb&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;* db query, secret logic *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; components&#x2F;ProfileCard.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;use client&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; formatDate&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; getUserFromDb&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;@&#x2F;lib&#x2F;utils&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; ↑ getUserFromDb is now in the browser bundle&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You only meant to import &lt;code&gt;formatDate&lt;&#x2F;code&gt;. But because both functions live in the same file, the entire module gets bundled — including your database logic.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;&#x2F;strong&gt; Separate your utilities by environment. Keep server-only logic in dedicated files and protect them with the &lt;code&gt;server-only&lt;&#x2F;code&gt; package.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; lib&#x2F;server&#x2F;db.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;server-only&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;&#x2F; ← hard build error if this gets imported client-side&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export async function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; getUserFromDb&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;* safe *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; lib&#x2F;utils.ts — only safe, UI-level helpers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; formatDate&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;date&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Date&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;* fine anywhere *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now if &lt;code&gt;lib&#x2F;server&#x2F;db.ts&lt;&#x2F;code&gt; ever ends up in a client bundle, Next.js throws a build error instead of silently shipping your DB logic to users.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;case-4-leaking-sensitive-data-through-props&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#case-4-leaking-sensitive-data-through-props&quot; aria-label=&quot;Anchor link for: case-4-leaking-sensitive-data-through-props&quot;&gt;Case 4: Leaking Sensitive Data Through Props&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;The misconception:&lt;&#x2F;strong&gt; You securely fetch data inside a Server Component and assume it stays secure because the fetch happened on the server.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The reality:&lt;&#x2F;strong&gt; If you pass that sensitive data — tokens, passwords, hidden IDs, internal pricing — down as a prop to a Client Component, it gets serialized and sent to the browser. The user can read it in full.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; app&#x2F;dashboard&#x2F;page.tsx (Server Component)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default async function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; DashboardPage&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; db&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;query&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;`SELECT * FROM users WHERE id = ?`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [userId])&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; user contains: { id, name, email, passwordHash, stripeSecretKey, internalScore }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;UserCard&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;&#x2F; ← passing the entire object down&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; components&#x2F;UserCard.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;use client&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; UserCard&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;({&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; user.passwordHash and user.stripeSecretKey are now in the browser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You’re only &lt;em&gt;displaying&lt;&#x2F;em&gt; &lt;code&gt;user.name&lt;&#x2F;code&gt;, but the entire object was serialized into the HTML payload and shipped to the client. Anyone can inspect the network response and read the rest.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;&#x2F;strong&gt; Pass only what the Client Component actually needs to render.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; app&#x2F;dashboard&#x2F;page.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default async function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; DashboardPage&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; db&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;query&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;`SELECT * FROM users WHERE id = ?`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [userId])&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;UserCard&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; email&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;email&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;&#x2F; ← only safe fields&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Never pass a raw database object to a Client Component. Treat the prop boundary the same way you’d treat an API response — intentionally shape what leaves the server.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;edge-case-server-actions-aren-t-private-functions&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#edge-case-server-actions-aren-t-private-functions&quot; aria-label=&quot;Anchor link for: edge-case-server-actions-aren-t-private-functions&quot;&gt;Edge Case: Server Actions Aren’t Private Functions&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Server Actions do execute on the server, so this section is a bit different — your code &lt;em&gt;does&lt;&#x2F;em&gt; run server-side. But there’s a fundamental misunderstanding about what that means for security.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;adding-use-server-doesn-t-make-a-function-private&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#adding-use-server-doesn-t-make-a-function-private&quot; aria-label=&quot;Anchor link for: adding-use-server-doesn-t-make-a-function-private&quot;&gt;Adding “use server” Doesn’t Make a Function Private&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;strong&gt;The misconception:&lt;&#x2F;strong&gt; You add &lt;code&gt;&quot;use server&quot;&lt;&#x2F;code&gt; to a function thinking it’s a strict rule to “run this private utility on the server” — an internal function the outside world can’t touch.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The reality:&lt;&#x2F;strong&gt; &lt;code&gt;&quot;use server&quot;&lt;&#x2F;code&gt; creates a &lt;strong&gt;public-facing POST endpoint&lt;&#x2F;strong&gt;. The client can invoke it at will. If you don’t validate and authorize inside the action itself, you’ve left a door wide open.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; app&#x2F;actions.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;use server&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export async function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; deleteUser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;userId&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; No auth check. No ownership check.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  await&lt;&#x2F;span&gt;&lt;span&gt; db&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;delete&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;users&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;span&gt; id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; userId&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;&#x2F; ← anyone can call this&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This action is reachable via a POST request from any client — your app, someone else’s browser tab, a curl command. The fact that it runs on your server doesn’t make it safe; it makes it a publicly accessible API that does destructive work.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;&#x2F;strong&gt; Treat every Server Action like a public API route. Verify authentication and authorization inside the action on every single call.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;use server&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; auth&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;@&#x2F;lib&#x2F;auth&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export async function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; deleteUser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;userId&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; auth&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span&gt;session)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; Error&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Unauthorized&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;role&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; !==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;admin&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; Error&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Forbidden&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  await&lt;&#x2F;span&gt;&lt;span&gt; db&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;delete&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;users&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;span&gt; id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; userId&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Never assume a Server Action is protected because it lives in your codebase. Assume every action is reachable by anyone and validate accordingly.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-mental-model-that-actually-helps&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-mental-model-that-actually-helps&quot; aria-label=&quot;Anchor link for: the-mental-model-that-actually-helps&quot;&gt;The Mental Model That Actually Helps&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Stop thinking of Server vs. Client as a property of individual files. Think of it as &lt;strong&gt;two separate execution environments&lt;&#x2F;strong&gt;, and your code lives in whichever one its import chain puts it in.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&quot;use client&quot;&lt;&#x2F;code&gt; doesn’t mark a component as client-only. It marks a &lt;strong&gt;boundary&lt;&#x2F;strong&gt; — the point where the server tree ends and the client bundle begins.&lt;&#x2F;li&gt;
&lt;li&gt;Everything &lt;em&gt;imported inside&lt;&#x2F;em&gt; a Client Component becomes client code, regardless of its own directives.&lt;&#x2F;li&gt;
&lt;li&gt;Everything &lt;em&gt;passed as props or children&lt;&#x2F;em&gt; keeps its original context.&lt;&#x2F;li&gt;
&lt;li&gt;Server Actions are public endpoints, not private functions.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Once that clicks, the mask reveal makes sense. Your “Server Component” was always the client. You just hadn’t pulled it off yet.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>How TestSprite Helped Me Find a Silent Polar Checkout Bug (and How I Fixed It)</title>
          <pubDate>Sun, 12 Apr 2026 14:00:00 +0500</pubDate>
          <author>Jameel Ahmad</author>
          <link>https://zola-paper.pages.dev/posts/testsprite-polar-checkout-bug/</link>
          <guid>https://zola-paper.pages.dev/posts/testsprite-polar-checkout-bug/</guid>
          <description xml:base="https://zola-paper.pages.dev/posts/testsprite-polar-checkout-bug/">&lt;p&gt;I have been building &lt;strong&gt;Lucid Hire&lt;&#x2F;strong&gt;, a Next.js recruiter dashboard with &lt;strong&gt;Clerk&lt;&#x2F;strong&gt; auth, &lt;strong&gt;Neon&lt;&#x2F;strong&gt; Postgres, and &lt;strong&gt;Polar&lt;&#x2F;strong&gt; for subscriptions. The billing page lets recruiters click &lt;strong&gt;Upgrade to Pro&lt;&#x2F;strong&gt; and navigate to Polar-hosted checkout. In manual testing I had tunneling and env quirks, so I leaned on &lt;strong&gt;TestSprite&lt;&#x2F;strong&gt;—an MCP-connected E2E runner—to exercise the full app through a real browser session.&lt;&#x2F;p&gt;
&lt;p&gt;One case kept failing: &lt;strong&gt;TC013&lt;&#x2F;strong&gt;, “Billing page remains usable after returning from external checkout.” The report was blunt: hitting &lt;code&gt;&#x2F;api&#x2F;billing&#x2F;checkout?plan=pro&lt;&#x2F;code&gt; returned &lt;strong&gt;HTTP 500&lt;&#x2F;strong&gt; with a generic browser error page—no JSON, no stack trace in the UI.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;testsprite-polar-checkout-bug.jpg&quot; alt=&quot;Optional hero image for the post&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This post is about how that failure was actually a gift: TestSprite reproduced a path I had not fully validated, and chasing it led to a concrete bug in how we integrated Polar—not “Polar is down,” but &lt;strong&gt;our route crashing before the SDK could even run&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#table-of-contents&quot; aria-label=&quot;Anchor link for: table-of-contents&quot;&gt;Table of contents&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;what-testsprite-reported&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-testsprite-reported&quot; aria-label=&quot;Anchor link for: what-testsprite-reported&quot;&gt;What TestSprite reported&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;TestSprite generates a frontend test plan, runs Playwright-style flows against my local app (via a tunnel), and writes a report under &lt;code&gt;testsprite_tests&#x2F;tmp&#x2F;raw_report.md&lt;&#x2F;code&gt;. For &lt;strong&gt;TC013&lt;&#x2F;strong&gt;, the relevant excerpt looked like this in spirit:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The automation navigated to &lt;strong&gt;&lt;code&gt;&#x2F;api&#x2F;billing&#x2F;checkout?plan=pro&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; (same as our real &lt;strong&gt;Upgrade&lt;&#x2F;strong&gt; button).&lt;&#x2F;li&gt;
&lt;li&gt;The browser showed &lt;strong&gt;“This page isn’t working”&lt;&#x2F;strong&gt; &#x2F; &lt;strong&gt;HTTP ERROR 500&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;No redirect to Polar, so the test correctly marked a &lt;strong&gt;failure&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That told me the bug was &lt;strong&gt;server-side&lt;&#x2F;strong&gt; in our checkout API route, not a flaky click in the billing UI.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;following-the-trail-to-src-app-api-billing-checkout-route-ts&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#following-the-trail-to-src-app-api-billing-checkout-route-ts&quot; aria-label=&quot;Anchor link for: following-the-trail-to-src-app-api-billing-checkout-route-ts&quot;&gt;Following the trail to &lt;code&gt;src&#x2F;app&#x2F;api&#x2F;billing&#x2F;checkout&#x2F;route.ts&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Our billing client triggers checkout with a full-page navigation—simple and intentional:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; startUpgrade&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;plan&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;pro&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;max&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  setLoadingPlan&lt;&#x2F;span&gt;&lt;span&gt;(plan)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  window&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;location&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;href&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; `&#x2F;api&#x2F;billing&#x2F;checkout?plan=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;plan&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So every upgrade goes through &lt;strong&gt;&lt;code&gt;GET &#x2F;api&#x2F;billing&#x2F;checkout&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;. I opened the route handler next.&lt;&#x2F;p&gt;
&lt;p&gt;Originally, the route used &lt;strong&gt;&lt;code&gt;Checkout&lt;&#x2F;code&gt; from &lt;code&gt;@polar-sh&#x2F;nextjs&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;, which wraps &lt;strong&gt;&lt;code&gt;@polar-sh&#x2F;sdk&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; and builds a checkout session from query parameters. On the surface that is the “official” integration. The problem was what happened &lt;strong&gt;before&lt;&#x2F;strong&gt; Polar’s API was called.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-mystery-of-the-blank-500&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-mystery-of-the-blank-500&quot; aria-label=&quot;Anchor link for: the-mystery-of-the-blank-500&quot;&gt;The mystery of the blank 500&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Inside &lt;code&gt;@polar-sh&#x2F;nextjs&lt;&#x2F;code&gt;, the checkout handler does something like this (simplified from the published package):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; success&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; successUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; URL&lt;&#x2F;span&gt;&lt;span&gt;(successUrl)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; : void&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; (success&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt; includeCheckoutId)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  success&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;searchParams&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;set&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;checkoutId&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;{CHECKOUT_ID}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; result&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; polar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;checkouts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;* ... *&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; NextResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;redirect&lt;&#x2F;span&gt;&lt;span&gt;(redirectUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;toString&lt;&#x2F;span&gt;&lt;span&gt;())&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; catch&lt;&#x2F;span&gt;&lt;span&gt; (error)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span&gt;(error)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; NextResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two important details:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;new URL(successUrl)&lt;&#x2F;code&gt; runs outside the &lt;code&gt;try&lt;&#x2F;code&gt; block.&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
In JavaScript, &lt;code&gt;new URL(&quot;&#x2F;dashboard&#x2F;settings&#x2F;billing&quot;)&lt;&#x2F;code&gt; &lt;strong&gt;throws&lt;&#x2F;strong&gt;—relative URLs are invalid without a base. So if &lt;code&gt;POLAR_SUCCESS_URL&lt;&#x2F;code&gt; was unset in a way that still led to bad input, or was documented as a “path only” value, the handler could &lt;strong&gt;throw before &lt;code&gt;try&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; → Next.js responds with an &lt;strong&gt;unhelpful 500&lt;&#x2F;strong&gt; and no JSON body.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;When the Polar API failed, the helper returned &lt;code&gt;NextResponse.error()&lt;&#x2F;code&gt;.&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
That is also an &lt;strong&gt;opaque 500&lt;&#x2F;strong&gt; from the browser’s point of view. TestSprite (and users) only see “something broke.”&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Separately, our README listed &lt;strong&gt;&lt;code&gt;POLAR_ACCESS_TOKEN&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; and product IDs but never &lt;strong&gt;&lt;code&gt;POLAR_SUCCESS_URL&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;. Locally I had been “fine” until the exact combination of tunnel + env + navigation reproduced the crash in CI-style E2E.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-testsprite-helped-beyond-checkout-is-broken&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-testsprite-helped-beyond-checkout-is-broken&quot; aria-label=&quot;Anchor link for: how-testsprite-helped-beyond-checkout-is-broken&quot;&gt;How TestSprite helped beyond “checkout is broken”&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Without TestSprite, I might have assumed:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;“Polar credentials are wrong,” or&lt;&#x2F;li&gt;
&lt;li&gt;“Test user is not allowed to checkout,” or&lt;&#x2F;li&gt;
&lt;li&gt;“Clerk session is missing.”&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The report narrowed it: &lt;strong&gt;authenticated flows passed&lt;&#x2F;strong&gt; (billing page, plan copy, other cases), but the &lt;strong&gt;API route itself&lt;&#x2F;strong&gt; returned 500 when used like a real user (&lt;strong&gt;full navigation&lt;&#x2F;strong&gt; to the API URL). That pushed me to read the &lt;strong&gt;route + Polar adapter&lt;&#x2F;strong&gt;, not redo Clerk for the tenth time.&lt;&#x2F;p&gt;
&lt;p&gt;So the value was not only automation—it was &lt;strong&gt;a reproducible, user-shaped repro&lt;&#x2F;strong&gt; attached to logs and video links in the TestSprite dashboard.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-fix-own-the-checkout-flow-and-make-urls-safe&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-fix-own-the-checkout-flow-and-make-urls-safe&quot; aria-label=&quot;Anchor link for: the-fix-own-the-checkout-flow-and-make-urls-safe&quot;&gt;The fix: own the checkout flow and make URLs safe&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I stopped delegating to &lt;code&gt;@polar-sh&#x2F;nextjs&lt;&#x2F;code&gt;’s &lt;code&gt;Checkout()&lt;&#x2F;code&gt; for this route and called &lt;strong&gt;&lt;code&gt;polar.checkouts.create()&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; from &lt;strong&gt;&lt;code&gt;@polar-sh&#x2F;sdk&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; directly, with:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A resolver that always produces a valid absolute success URL&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A &lt;code&gt;try&#x2F;catch&lt;&#x2F;code&gt; that returns JSON with a real error message&lt;&#x2F;strong&gt; (HTTP 502) when Polar rejects the request&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;1-resolve-polar-success-url-safely&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-resolve-polar-success-url-safely&quot; aria-label=&quot;Anchor link for: 1-resolve-polar-success-url-safely&quot;&gt;1. Resolve &lt;code&gt;POLAR_SUCCESS_URL&lt;&#x2F;code&gt; safely&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Polar needs an &lt;strong&gt;absolute&lt;&#x2F;strong&gt; success URL. If the env var is missing, we default to returning the user to billing. If it is a relative path, we resolve it against &lt;code&gt;NEXT_PUBLIC_APP_URL&lt;&#x2F;code&gt;, &lt;code&gt;VERCEL_URL&lt;&#x2F;code&gt;, or the incoming request origin:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; resolveCheckoutSuccessUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; NextRequest&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; URL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;POLAR_SUCCESS_URL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;?.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;trim&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; origin&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;NEXT_PUBLIC_APP_URL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;?.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;replace&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;&#x2F;\&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ||&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;VERCEL_URL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; `https:&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;VERCEL_URL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; null&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ||&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;nextUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;origin&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span&gt;raw)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; URL&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&#x2F;dashboard&#x2F;settings&#x2F;billing&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; origin)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; URL&lt;&#x2F;span&gt;&lt;span&gt;(raw)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; catch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span&gt; path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;startsWith&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;span&gt; raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; `&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; URL&lt;&#x2F;span&gt;&lt;span&gt;(path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; origin)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then we append Polar’s &lt;strong&gt;&lt;code&gt;checkoutId={CHECKOUT_ID}&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; placeholder the same way the official helper does:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; successUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; resolveCheckoutSuccessUrl&lt;&#x2F;span&gt;&lt;span&gt;(request)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;successUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;searchParams&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;set&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;checkoutId&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;{CHECKOUT_ID}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;2-create-the-session-and-redirect-or-return-a-clear-error&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#2-create-the-session-and-redirect-or-return-a-clear-error&quot; aria-label=&quot;Anchor link for: 2-create-the-session-and-redirect-or-return-a-clear-error&quot;&gt;2. Create the session and redirect—or return a clear error&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; polar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; Polar&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  accessToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  server&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; polarServerFromEnv&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; result&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; polar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;checkouts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    products&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [productId]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    successUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; decodeURI&lt;&#x2F;span&gt;&lt;span&gt;(successUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;toString&lt;&#x2F;span&gt;&lt;span&gt;())&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    externalCustomerId&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; organization&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    customerEmail&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; appUser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;email&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    customerName&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; appUser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ??&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; undefined&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    metadata&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; orgId&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; organization&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; NextResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;redirect&lt;&#x2F;span&gt;&lt;span&gt;(result&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;url)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; catch&lt;&#x2F;span&gt;&lt;span&gt; (error)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;[billing&#x2F;checkout] Polar API error:&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; error)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; NextResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;json&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    {&lt;&#x2F;span&gt;&lt;span&gt; error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; checkoutErrorMessage&lt;&#x2F;span&gt;&lt;span&gt;(error)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    {&lt;&#x2F;span&gt;&lt;span&gt; status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 502&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After this change:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Misconfiguration&lt;&#x2F;strong&gt; (wrong token, wrong sandbox vs production product id) still fails—but the response is &lt;strong&gt;JSON with a message&lt;&#x2F;strong&gt;, and the server log has the Polar error. TestSprite (or curl) can surface that instead of a blank chrome error page.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Missing or relative &lt;code&gt;POLAR_SUCCESS_URL&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; no longer crashes the process on &lt;code&gt;new URL()&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;what-i-learned&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-learned&quot; aria-label=&quot;Anchor link for: what-i-learned&quot;&gt;What I learned&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;E2E tools like TestSprite&lt;&#x2F;strong&gt; are not only for “click happy paths.” They excel at &lt;strong&gt;boring but exact repros&lt;&#x2F;strong&gt;: same URL, same query string, same navigation semantics as production.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Third-party adapters&lt;&#x2F;strong&gt; (&lt;code&gt;@polar-sh&#x2F;nextjs&lt;&#x2F;code&gt;) save time until they &lt;strong&gt;hide thrown errors&lt;&#x2F;strong&gt; or &lt;strong&gt;parse env in ways that assume perfect configuration&lt;&#x2F;strong&gt;. Sometimes owning the ten lines of SDK calls buys you &lt;strong&gt;observability&lt;&#x2F;strong&gt; and &lt;strong&gt;control&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;POLAR_SUCCESS_URL&lt;&#x2F;code&gt; should be documented&lt;&#x2F;strong&gt; next to tokens and product IDs: absolute URL preferred; if you use a path, we now resolve it—but you should still set &lt;strong&gt;&lt;code&gt;NEXT_PUBLIC_APP_URL&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; (or rely on &lt;code&gt;request.nextUrl.origin&lt;&#x2F;code&gt; in dev) so the base is correct behind proxies and tunnels.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
</description>
      </item>
      <item>
          <title>Fixing &quot;No Wi-Fi Adapter Found&quot; on Fedora with Intel AX201</title>
          <pubDate>Wed, 25 Feb 2026 20:10:00 +0500</pubDate>
          <author>Jameel Ahmad</author>
          <link>https://zola-paper.pages.dev/posts/fixing-wifi-intel-ax201-fedora/</link>
          <guid>https://zola-paper.pages.dev/posts/fixing-wifi-intel-ax201-fedora/</guid>
          <description xml:base="https://zola-paper.pages.dev/posts/fixing-wifi-intel-ax201-fedora/">&lt;p&gt;I was working on my Lenovo ThinkPad X13 Yoga Gen 2 running Fedora Linux 43 when suddenly the Wi-Fi disappeared. The settings showed a dreaded message: &lt;strong&gt;“No Wi-Fi Adapter Found”&lt;&#x2F;strong&gt;. What made it even more frustrating was that the hardware was clearly there—I had an Intel Wi-Fi 6 AX201 card installed.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;fixing-wifi-intel-ax201-fedora.png&quot; alt=&quot;Retro style pixel art of a Linux terminal fixing Wi-Fi&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;After some investigation, I discovered this was a common issue with Intel AX201 wireless cards on Linux, particularly affecting Fedora users. The driver would work sometimes but fail to initialize properly on boot.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#table-of-contents&quot; aria-label=&quot;Anchor link for: table-of-contents&quot;&gt;Table of contents&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;diagnosing-the-problem&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#diagnosing-the-problem&quot; aria-label=&quot;Anchor link for: diagnosing-the-problem&quot;&gt;Diagnosing the Problem&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;First, I needed to understand what was actually happening. I ran some diagnostic commands to check if the hardware was detected:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;lspci&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -nnk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; grep&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -iA3 network&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Output showed my card was detected:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;00:14.3 Network controller [0280]: Intel Corporation Wi-Fi 6 AX201 [8086:a0f0] (rev 20)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Subsystem: Intel Corporation Device [8086:0070]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Kernel modules: iwlwifi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The hardware was there, and the &lt;code&gt;iwlwifi&lt;&#x2F;code&gt; driver module was available. So what was the issue?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-real-culprit-driver-initialization-timeout&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-real-culprit-driver-initialization-timeout&quot; aria-label=&quot;Anchor link for: the-real-culprit-driver-initialization-timeout&quot;&gt;The Real Culprit: Driver Initialization Timeout&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I checked the kernel messages to see what was happening during boot:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; dmesg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; grep&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -i iwl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Buried in the output was the critical error:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;iwlwifi 0000:00:14.3: probe with driver iwlwifi failed with error -110&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Error -110&lt;&#x2F;strong&gt; is a timeout error. The driver was trying to communicate with the Wi-Fi card but couldn’t establish a connection within the expected time frame. This is a well-known issue with Intel AX201 cards on Linux systems.&lt;&#x2F;p&gt;
&lt;p&gt;I also verified that the driver module was loaded:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;lsmod&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; grep&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -i iwl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Output:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;iwlwifi&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;               589824  0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cfg80211&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;             1544192  1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; iwlwifi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The module was loaded, but it just couldn’t talk to the hardware properly.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-temporary-solution-that-revealed-the-root-cause&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-temporary-solution-that-revealed-the-root-cause&quot; aria-label=&quot;Anchor link for: the-temporary-solution-that-revealed-the-root-cause&quot;&gt;The Temporary Solution That Revealed the Root Cause&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;At first, I tried reinstalling the firmware:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; dnf reinstall iwl&lt;&#x2F;span&gt;&lt;span&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;-firmware&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; reboot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This worked! After the reboot, Wi-Fi appeared and connected successfully. But when I rebooted again, the problem came back. The Wi-Fi would disappear on every other boot.&lt;&#x2F;p&gt;
&lt;p&gt;This intermittent behavior told me something important: the firmware and driver &lt;em&gt;could&lt;&#x2F;em&gt; work, but they weren’t being initialized properly during the boot process.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-permanent-fix-a-multi-layered-approach&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-permanent-fix-a-multi-layered-approach&quot; aria-label=&quot;Anchor link for: the-permanent-fix-a-multi-layered-approach&quot;&gt;The Permanent Fix: A Multi-Layered Approach&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;After researching and testing, I found that a combination of solutions was needed to make the Wi-Fi work reliably across all reboots.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-reinstall-the-firmware&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-reinstall-the-firmware&quot; aria-label=&quot;Anchor link for: 1-reinstall-the-firmware&quot;&gt;1. Reinstall the Firmware&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;First, ensure the Intel wireless firmware is properly installed:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; dnf reinstall iwl&lt;&#x2F;span&gt;&lt;span&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;-firmware&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;2-add-the-driver-to-initramfs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#2-add-the-driver-to-initramfs&quot; aria-label=&quot;Anchor link for: 2-add-the-driver-to-initramfs&quot;&gt;2. Add the Driver to Initramfs&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The initramfs (initial RAM filesystem) is loaded early in the boot process. By adding the iwlwifi driver to it, we ensure it’s available when the system needs it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;add_drivers+=&amp;quot; iwlwifi &amp;quot;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; tee &#x2F;etc&#x2F;dracut.conf.d&#x2F;iwlwifi.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; dracut -f --regenerate-all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;3-disable-power-management&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#3-disable-power-management&quot; aria-label=&quot;Anchor link for: 3-disable-power-management&quot;&gt;3. Disable Power Management&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The timeout error often occurs because aggressive power management prevents the driver from initializing properly. Disabling power saving for the Wi-Fi module fixes this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;options iwlwifi power_save=0&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; tee &#x2F;etc&#x2F;modprobe.d&#x2F;iwlwifi.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;4-rebuild-initramfs-again&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#4-rebuild-initramfs-again&quot; aria-label=&quot;Anchor link for: 4-rebuild-initramfs-again&quot;&gt;4. Rebuild Initramfs Again&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;After adding the power management configuration, rebuild the initramfs to include these settings:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; dracut -f --regenerate-all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;5-create-a-systemd-service-as-a-backup&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#5-create-a-systemd-service-as-a-backup&quot; aria-label=&quot;Anchor link for: 5-create-a-systemd-service-as-a-backup&quot;&gt;5. Create a Systemd Service as a Backup&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;As an extra layer of reliability, I created a systemd service that forces the driver to reload on every boot:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; tee &#x2F;etc&#x2F;systemd&#x2F;system&#x2F;iwlwifi-reload.service&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;EOF&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;[Unit]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;Description=Reload iwlwifi module&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;After=network-pre.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;Before=network.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;[Service]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;Type=oneshot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;ExecStart=&#x2F;usr&#x2F;sbin&#x2F;modprobe -r iwlwifi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;ExecStart=&#x2F;usr&#x2F;sbin&#x2F;modprobe iwlwifi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;RemainAfterExit=yes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;[Install]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;WantedBy=multi-user.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;EOF&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; systemctl enable iwlwifi-reload.service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;6-final-reboot&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#6-final-reboot&quot; aria-label=&quot;Anchor link for: 6-final-reboot&quot;&gt;6. Final Reboot&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; reboot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;why-this-combination-works&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-this-combination-works&quot; aria-label=&quot;Anchor link for: why-this-combination-works&quot;&gt;Why This Combination Works&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;This multi-layered approach ensures Wi-Fi reliability through:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Firmware reinstallation&lt;&#x2F;strong&gt;: Ensures the latest Intel AX201 firmware is available&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Initramfs configuration&lt;&#x2F;strong&gt;: Makes the driver available early in the boot process&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Power management disabled&lt;&#x2F;strong&gt;: Prevents timeout errors during initialization&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Systemd service&lt;&#x2F;strong&gt;: Provides a safety net by forcing module reload if other methods fail&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The combination handles edge cases across kernel updates, firmware updates, and various boot scenarios.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;verification&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#verification&quot; aria-label=&quot;Anchor link for: verification&quot;&gt;Verification&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;After rebooting, you can verify everything is working:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Check if the service is active&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;systemctl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; status iwlwifi-reload.service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Check if power_save is disabled&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &#x2F;etc&#x2F;modprobe.d&#x2F;iwlwifi.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Verify Wi-Fi adapter is detected&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;nmcli&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; device status&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;Conclusion&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The Intel Wi-Fi 6 AX201 is an excellent wireless card, but its Linux drivers can be finicky on Fedora. The error -110 timeout issue is frustrating because it’s intermittent—sometimes working, sometimes not.&lt;&#x2F;p&gt;
&lt;p&gt;By combining proper initramfs configuration, power management tweaks, and a systemd service fallback, I’ve achieved reliable Wi-Fi functionality across all reboots. This solution should persist across system updates and kernel changes.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re experiencing similar issues with Intel wireless cards on Fedora or other Linux distributions, this comprehensive approach should help you achieve stable Wi-Fi connectivity.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Overcoming YouTube Authentication Hurdles with yt-dlp</title>
          <pubDate>Fri, 30 Jan 2026 03:02:00 +0500</pubDate>
          <author>Jameel Ahmad</author>
          <link>https://zola-paper.pages.dev/posts/ytdlp-cookie-auth-guide/</link>
          <guid>https://zola-paper.pages.dev/posts/ytdlp-cookie-auth-guide/</guid>
          <description xml:base="https://zola-paper.pages.dev/posts/ytdlp-cookie-auth-guide/">&lt;p&gt;For anyone who loves managing their own media library, &lt;strong&gt;yt-dlp&lt;&#x2F;strong&gt; is the gold standard. It is an incredibly powerful command-line tool that can download video and audio from thousands of sites with surgical precision. Whether you want a specific resolution or just a high-quality MP3, yt-dlp handles it with ease.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;ytdlp-cookie-auth-guide.png&quot; alt=&quot;The Get cookies.txt LOCALLY extension in the Chrome Web Store&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;However, as platforms like YouTube tighten their security to prevent botting, we often run into “Sign in to confirm you’re not a bot” errors or age-restriction blocks. While yt-dlp has a built-in feature to grab cookies directly from your browser, modern security updates have made this process surprisingly frustrating.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#table-of-contents&quot; aria-label=&quot;Anchor link for: table-of-contents&quot;&gt;Table of contents&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;the-database-locked-headache&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-database-locked-headache&quot; aria-label=&quot;Anchor link for: the-database-locked-headache&quot;&gt;The “Database Locked” Headache&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Usually, you would try to use your browser’s session by adding &lt;code&gt;--cookies-from-browser edge&lt;&#x2F;code&gt; to your command. But more often than not, you’ll be greeted by this annoying error:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Extracting cookies &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; edge&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ERROR: Could not copy Chrome cookie database. See &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;https&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;github.com&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;yt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;dlp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;yt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;dlp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;issues&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;7271&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;](&lt;&#x2F;span&gt;&lt;span&gt;https:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;github.com&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;yt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;dlp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;yt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;dlp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;issues&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;7271&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; more info&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This happens because Chromium-based browsers (like Edge, Chrome, and Brave) lock their cookie database file while the browser is open. Even if you close the window, background processes often keep the file “in use,” preventing yt-dlp from reading your login session.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-solution-manual-cookie-extraction&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-solution-manual-cookie-extraction&quot; aria-label=&quot;Anchor link for: the-solution-manual-cookie-extraction&quot;&gt;The Solution: Manual Cookie Extraction&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The most reliable way to bypass this without constantly closing your browser is to use a cookie export extension. This gives yt-dlp a static “snapshot” of your login session that it can read freely.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-install-the-extension&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-install-the-extension&quot; aria-label=&quot;Anchor link for: 1-install-the-extension&quot;&gt;1. Install the Extension&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I use an extension called &lt;strong&gt;“Get cookies.txt LOCALLY”&lt;&#x2F;strong&gt;. It’s lightweight and doesn’t send your data to any external servers.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Install it from the Chrome&#x2F;Edge Web Store.&lt;&#x2F;li&gt;
&lt;li&gt;Open YouTube and make sure you are logged in.&lt;&#x2F;li&gt;
&lt;li&gt;Click the extension icon and export your cookies as a &lt;code&gt;.txt&lt;&#x2F;code&gt; file.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;2-prepare-your-folder&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#2-prepare-your-folder&quot; aria-label=&quot;Anchor link for: 2-prepare-your-folder&quot;&gt;2. Prepare Your Folder&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Save the file (e.g., &lt;code&gt;cookies.txt&lt;&#x2F;code&gt;) directly into the folder where you run your commands. In my case, that is &lt;code&gt;C:\Users\jameel\music&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-run-the-correct-command&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#3-run-the-correct-command&quot; aria-label=&quot;Anchor link for: 3-run-the-correct-command&quot;&gt;3. Run the Correct Command&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Now, instead of telling yt-dlp to look into the browser, we point it directly to our text file using the &lt;code&gt;--cookies&lt;&#x2F;code&gt; flag:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;yt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;dlp &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;--&lt;&#x2F;span&gt;&lt;span&gt;js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;runtimes node &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;--&lt;&#x2F;span&gt;&lt;span&gt;remote&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;components ejs:github &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;--&lt;&#x2F;span&gt;&lt;span&gt;cookies cookies.txt &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;x &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;--&lt;&#x2F;span&gt;&lt;span&gt;audio&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;format mp3 &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;--&lt;&#x2F;span&gt;&lt;span&gt;audio&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;quality &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;media_link_to_be_download&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;why-this-works-better&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-this-works-better&quot; aria-label=&quot;Anchor link for: why-this-works-better&quot;&gt;Why This Works Better&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No Conflicts:&lt;&#x2F;strong&gt; It doesn’t matter if Edge is open, closed, or running 50 tabs; the text file is always accessible.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Consistency:&lt;&#x2F;strong&gt; This method works even when browser updates change how internal databases are encrypted.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Bypass Captchas:&lt;&#x2F;strong&gt; Since the file contains your actual “logged-in” session, YouTube treats the request as coming from a verified user.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;a-note-on-security&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-note-on-security&quot; aria-label=&quot;Anchor link for: a-note-on-security&quot;&gt;A Note on Security&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Your &lt;code&gt;cookies.txt&lt;&#x2F;code&gt; file is essentially a temporary key to your account. Never share this file with anyone else! Once you are done with your downloads, it is a good habit to delete the text file or move it to a secure location.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>How I Fixed Missing H.264 Codecs on a Fresh Fedora Install</title>
          <pubDate>Fri, 09 Jan 2026 01:43:00 +0500</pubDate>
          <author>Jameel Ahmad</author>
          <link>https://zola-paper.pages.dev/posts/fixing-h264-codec-fedora/</link>
          <guid>https://zola-paper.pages.dev/posts/fixing-h264-codec-fedora/</guid>
          <description xml:base="https://zola-paper.pages.dev/posts/fixing-h264-codec-fedora/">&lt;p&gt;I freshly installed Fedora Linux Workstation 43 on my PC and tried to play a video. The video played, but it was blank. It turned out there was an issue with the missing video decoder required to run this video: &lt;strong&gt;H.264&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;fixing-h264-codec-fedora.png&quot; alt=&quot;Retro style pixel art of a Linux terminal fixing codecs&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The issue is that this encoder is proprietary and does not come with Fedora by default due to licensing restrictions. To fix this, I had to dive into the world of FFmpeg and package management.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#table-of-contents&quot; aria-label=&quot;Anchor link for: table-of-contents&quot;&gt;Table of contents&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;the-mystery-of-the-missing-ffmpeg&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-mystery-of-the-missing-ffmpeg&quot; aria-label=&quot;Anchor link for: the-mystery-of-the-missing-ffmpeg&quot;&gt;The Mystery of the “Missing” FFmpeg&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;There is a huge library of multimedia encoders and decoders out there called FFmpeg. I needed to check if FFmpeg was installed in Fedora and if it included the H.264 encoder.&lt;&#x2F;p&gt;
&lt;p&gt;I used the command:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;dnf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; list installed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; grep&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; ffmpeg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Surprisingly, I got nothing. However, when I ran &lt;code&gt;ffmpeg&lt;&#x2F;code&gt; directly in the terminal, the command worked! It was strange to me how a command-line utility could be available even when it didn’t appear as “installed” in my package list.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;finding-the-culprit-ffmpeg-free&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#finding-the-culprit-ffmpeg-free&quot; aria-label=&quot;Anchor link for: finding-the-culprit-ffmpeg-free&quot;&gt;Finding the Culprit: ffmpeg-free&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;It turns out Fedora comes with a “free” version of FFmpeg that excludes proprietary encoders. To solve the mystery, I first checked where the command was running from:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;which&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; ffmpeg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Output: &#x2F;usr&#x2F;bin&#x2F;ffmpeg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then, I passed this path to RPM to check which package exactly owned this file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rpm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -qf &#x2F;usr&#x2F;bin&#x2F;ffmpeg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The result: &lt;code&gt;ffmpeg-free-7.1.2-2.fc43.x86_64&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This confirmed that the “free” version was installed, which lacks the H.264 proprietary encoder.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-solution-swapping-to-rpm-fusion&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-solution-swapping-to-rpm-fusion&quot; aria-label=&quot;Anchor link for: the-solution-swapping-to-rpm-fusion&quot;&gt;The Solution: Swapping to RPM Fusion&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;To fix this, I needed to enable the &lt;strong&gt;RPM Fusion&lt;&#x2F;strong&gt; repositories and swap the limited version for the full one.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-enable-repositories&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-enable-repositories&quot; aria-label=&quot;Anchor link for: 1-enable-repositories&quot;&gt;1. Enable Repositories&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;First, I enabled the free and non-free RPM Fusion repositories:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; dnf install https:&#x2F;&#x2F;mirrors.rpmfusion.org&#x2F;free&#x2F;fedora&#x2F;rpmfusion-free-release-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rpm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -E %fedora&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;.noarch.rpm https:&#x2F;&#x2F;mirrors.rpmfusion.org&#x2F;nonfree&#x2F;fedora&#x2F;rpmfusion-nonfree-release-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rpm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -E %fedora&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;.noarch.rpm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;2-swap-the-packages&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#2-swap-the-packages&quot; aria-label=&quot;Anchor link for: 2-swap-the-packages&quot;&gt;2. Swap the Packages&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Then, I used this command to install the proprietary version of FFmpeg and remove the pre-installed free one:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; dnf swap ffmpeg-free ffmpeg --allowerasing&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;fixing-lag-and-crashes&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fixing-lag-and-crashes&quot; aria-label=&quot;Anchor link for: fixing-lag-and-crashes&quot;&gt;Fixing Lag and Crashes&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The codec was installed successfully and the video played, but I hit another problem: the video was lagging heavily and the player sometimes crashed.&lt;&#x2F;p&gt;
&lt;p&gt;The default video player tries to find the right codec automatically, but sometimes it tries to roll back to the “free” version or struggles with dependencies. To enforce all multimedia apps to use the preferred encoding packages, I ran:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; dnf update @multimedia --setopt=&amp;quot;install_weak_deps=False&amp;quot; --exclude=PackageKit-gstreamer-plugin&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;what-does-this-command-do&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-does-this-command-do&quot; aria-label=&quot;Anchor link for: what-does-this-command-do&quot;&gt;What does this command do?&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@multimedia&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;: This is the group in which all multimedia-related apps are grouped by Fedora.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;install_weak_deps=False&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;: This prevents the system from pulling back in the limited “free” versions as optional dependencies.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--exclude=PackageKit-gstreamer-plugin&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;: This stops the GUI software store from interfering with our manual codec setup.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;After running this, the H.264 videos played perfectly smooth!&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
