<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Junkyu Jeon</title>
    <description>The latest articles on Forem by Junkyu Jeon (@jakay).</description>
    <link>https://forem.com/jakay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3881762%2F9a9db767-95ae-450b-bb0c-4b74fd31e375.jpg</url>
      <title>Forem: Junkyu Jeon</title>
      <link>https://forem.com/jakay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jakay"/>
    <language>en</language>
    <item>
      <title>How to Write Better Prompts for Bolt, Lovable, and Cursor</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Mon, 20 Apr 2026 23:21:36 +0000</pubDate>
      <link>https://forem.com/jakay/how-to-write-better-prompts-for-bolt-lovable-and-cursor-do1</link>
      <guid>https://forem.com/jakay/how-to-write-better-prompts-for-bolt-lovable-and-cursor-do1</guid>
      <description>&lt;p&gt;You and your friend both use Cursor. You both ask for "a simple to-do app." Three hours later, your friend has a working app with auth, persistence, and a clean UI. You have a pile of files, a broken login, and a database that won't connect.&lt;/p&gt;

&lt;p&gt;Same model. Different prompt.&lt;/p&gt;

&lt;p&gt;People talk about prompt engineering like mysticism. It isn't. It's closer to a checklist. Here's the checklist — plus templates you can copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Most Prompts Fail
&lt;/h2&gt;

&lt;p&gt;Bad prompts share a few patterns. The AI isn't guessing maliciously — it's filling blanks you left empty.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No stack specified.&lt;/strong&gt; "Add a database" — which one? SQLite? Supabase? Postgres? AI picks. Often picks something that doesn't fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whole-app requests.&lt;/strong&gt; "Build me an Airbnb clone." 40 files of plausible-looking code, none of which fit together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vague verbs.&lt;/strong&gt; "Make it better." "Refactor this." No target — so it changes everything, including parts that were fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No examples.&lt;/strong&gt; "Format the date nicely." AI guesses what "nicely" means.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing constraints.&lt;/strong&gt; No edge cases, no max length, no expected error behavior. AI handles the happy path and ignores the rest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each blank is a decision the AI makes for you — usually not the one you'd have made.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anatomy of a Good Prompt
&lt;/h2&gt;

&lt;p&gt;Five parts. Don't need every part every time, but more = less guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Context
&lt;/h3&gt;

&lt;p&gt;What is this project? What stack? What conventions? If you have a CLAUDE.md or .cursorrules, this is already covered. If not, lead with one sentence: &lt;em&gt;"This is a Next.js 15 + Supabase + Tailwind app. We use shadcn/ui and follow App Router conventions."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Goal
&lt;/h3&gt;

&lt;p&gt;What specifically do you want? One concrete change. &lt;em&gt;"Add a button on the dashboard that exports the visible projects to CSV"&lt;/em&gt; — not &lt;em&gt;"add export functionality."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Constraints
&lt;/h3&gt;

&lt;p&gt;Must do? Must not do? &lt;em&gt;"Must work for up to 10,000 rows. Must not block the UI. Use the existing supabase client from &lt;code&gt;lib/supabase.ts&lt;/code&gt;."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Output Format
&lt;/h3&gt;

&lt;p&gt;How do you want the answer? Code only? Diff only? Specify it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Verification
&lt;/h3&gt;

&lt;p&gt;How will you know it worked? Tell the AI. &lt;em&gt;"After this, I should be able to click Export, get a CSV download named &lt;code&gt;projects-{date}.csv&lt;/code&gt;, and open it in Excel without warnings."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When the AI knows what success looks like, it writes code aimed at that result — not at "something CSV-ish."&lt;/p&gt;

&lt;h2&gt;
  
  
  5 Patterns That Make Prompts Work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Specify the Stack and Version
&lt;/h3&gt;

&lt;p&gt;"Use Next.js" is not enough. Next.js 13 Pages Router and Next.js 15 App Router are different worlds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt; &lt;em&gt;"Add a server-side API route."&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Good:&lt;/strong&gt; &lt;em&gt;"Add a Next.js 15 App Router route handler at &lt;code&gt;app/api/export/route.ts&lt;/code&gt;. Use the new Web Request/Response API, not the old req/res."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Show, Don't Tell
&lt;/h3&gt;

&lt;p&gt;Examples beat adjectives every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt; &lt;em&gt;"Format the price nicely."&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Good:&lt;/strong&gt; &lt;em&gt;"Format as USD with dollar sign and 2 decimals: '$1,234.56'. Under $1, show 4 decimals: '$0.0042'."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. One Feature at a Time
&lt;/h3&gt;

&lt;p&gt;Resist the mega-prompt. Split it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;em&gt;"Create &lt;code&gt;/profile&lt;/code&gt; route. Fetch current user from Supabase, display name/email/joined date."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Add Edit button. Toggles name/email to inputs."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Add Save button. Updates user via Supabase. Shows success toast."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Add avatar upload. Stores in Supabase Storage. Updates avatar_url."&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step verifiable. When something breaks, you know which step. With a mega-prompt, you don't.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Constrain the Data Shape
&lt;/h3&gt;

&lt;p&gt;Most bugs come from data the AI didn't expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt; &lt;em&gt;"Calculate the order total."&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Good:&lt;/strong&gt; &lt;em&gt;"Inputs: array of &lt;code&gt;{ price: number, quantity: number }&lt;/code&gt; (price in cents, quantity positive integer), and optional &lt;code&gt;discountPercent&lt;/code&gt; (0-100, default 0). Return cents. Empty array → return 0. Negative quantity → throw."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Ask for Tests Explicitly
&lt;/h3&gt;

&lt;p&gt;AI rarely writes tests unless asked. Almost never thinks about edge cases unless told.&lt;/p&gt;

&lt;p&gt;Add one line: &lt;em&gt;"Also write a test file covering: empty input, single item, max items, zero discount, 100% discount, one negative input that should throw."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The test file does double duty: forces edge-case thinking, &lt;em&gt;and&lt;/em&gt; gives you a regression net.&lt;/p&gt;
&lt;h2&gt;
  
  
  5 Anti-Patterns to Avoid
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. "Make it better"
&lt;/h3&gt;

&lt;p&gt;The vaguest possible prompt. Name what you want: &lt;em&gt;"Reduce the number of state variables. Right now there are 7 useState calls; consolidate where possible."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. "Add authentication"
&lt;/h3&gt;

&lt;p&gt;Auth isn't one feature. Sign-up, sign-in, sign-out, password reset, session, route protection, email verification — at minimum. Pick a flow:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Add Supabase email/password sign-in. One page at &lt;code&gt;/login&lt;/code&gt;. On success, redirect to &lt;code&gt;/dashboard&lt;/code&gt;. On failure, show Supabase error inline. Don't add sign-up or password reset yet."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Pasting whole logs without context
&lt;/h3&gt;

&lt;p&gt;200-line stack trace + "fix this" = AI guessing what part matters and what your code looks like.&lt;/p&gt;

&lt;p&gt;Paste the &lt;em&gt;relevant&lt;/em&gt; error line, the function it points to, one-line description of what you were doing.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. "Refactor this"
&lt;/h3&gt;

&lt;p&gt;Refactor for what? Performance? Readability? Reuse? Each goal points at different changes.&lt;/p&gt;

&lt;p&gt;Better: &lt;em&gt;"Extract the price-formatting logic from this component into a function in &lt;code&gt;lib/format.ts&lt;/code&gt;. Don't change behavior. Update component to import and use the new function."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Mid-stream stack swaps
&lt;/h3&gt;

&lt;p&gt;Three hours into a Supabase project. You read a tweet about Drizzle ORM. &lt;em&gt;"Switch the database to use Drizzle."&lt;/em&gt; AI tries. Half-rewrites a few files, leaves Supabase imports scattered, nothing works.&lt;/p&gt;

&lt;p&gt;Stack swaps mid-project are surgery, not a prompt. Plan it as its own deliberate session.&lt;/p&gt;
&lt;h2&gt;
  
  
  Real Before / After
&lt;/h2&gt;

&lt;p&gt;Same goal, two prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add export to CSV.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What the AI does: invents a button somewhere, picks a CSV library you don't have, exports fields it guesses are interesting, ignores filtering, blocks UI for large datasets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Supabase&lt;/span&gt; &lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="nx"&gt;Dashboard&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt; &lt;span class="nx"&gt;shows&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;projects&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="nx"&gt;dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;Goal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Export CSV&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt;
&lt;span class="nx"&gt;downloads&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;currently&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;Constraints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Build&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;CSV&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="nx"&gt;manually&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;created_at &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ISO&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;owner_email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Handle&lt;/span&gt; &lt;span class="nx"&gt;commas&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;quotes&lt;/span&gt; &lt;span class="nf"&gt;correctly &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RFC&lt;/span&gt; &lt;span class="mi"&gt;4180&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;YYYY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;MM&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;DD&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Must&lt;/span&gt; &lt;span class="nx"&gt;work&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="nx"&gt;without&lt;/span&gt; &lt;span class="nx"&gt;freezing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Diff&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;helper&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;explanation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;Verification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;With&lt;/span&gt; &lt;span class="nx"&gt;dashboard&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;click&lt;/span&gt;
&lt;span class="nx"&gt;Export&lt;/span&gt; &lt;span class="nx"&gt;CSV&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;only&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;named&lt;/span&gt;
&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opens&lt;/span&gt; &lt;span class="nx"&gt;cleanly&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Excel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second prompt: 60 seconds longer to write. Saves 30 minutes of back-and-forth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steal These Templates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  New Feature
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context: [stack + relevant existing code]
Goal: [one specific user-visible change]
Constraints:
- [must do X]
- [must not do Y]
- [data shape, edge cases]
Output: [code only / diff only / explain first]
Verification: After this, I should be able to [observable thing].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bug Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repro:
- Steps: [1, 2, 3]
- Input: [exact values]
- Expected: [what should happen]
- Actual: [what happens, including error]

Suspect file: [path]

Don't change behavior elsewhere. Show your hypothesis before
the fix, then minimal change to address the root cause.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Data Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;Supabase&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="nv"&gt;"[name]"&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;RLS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;who&lt;/span&gt; &lt;span class="k"&gt;reads&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;who&lt;/span&gt; &lt;span class="n"&gt;writes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;Generate&lt;/span&gt; &lt;span class="k"&gt;SQL&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt; &lt;span class="k"&gt;under&lt;/span&gt; &lt;span class="n"&gt;supabase&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
&lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;TypeScript&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When Prompting Alone Isn't Enough
&lt;/h2&gt;

&lt;p&gt;For a single feature, a good prompt is enough. For a whole app, you need a sequence — and getting the sequence right is its own skill. Data model before UI. Auth before features that need it. Deploy setup before you have anything to deploy.&lt;/p&gt;




&lt;p&gt;Better prompts aren't about being clever. They're about leaving fewer blanks. Every blank is a decision the AI makes for you — and it doesn't know your project, your users, or what you actually want.&lt;/p&gt;

&lt;p&gt;The skill compounds. Every well-crafted prompt teaches you what to specify next time. Within a few weeks, your hit rate moves from "sometimes works, mostly weird" to "ships first try, most of the time."&lt;/p&gt;

&lt;p&gt;Steal the templates. Add to them as you find what your stack needs. Your AI is the same as everyone else's — your prompts don't have to be.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/write-better-prompts-vibe-coding" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Debug AI-Generated Code: A Systematic Approach</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Sun, 19 Apr 2026 14:23:57 +0000</pubDate>
      <link>https://forem.com/jakay/how-to-debug-ai-generated-code-a-systematic-approach-407d</link>
      <guid>https://forem.com/jakay/how-to-debug-ai-generated-code-a-systematic-approach-407d</guid>
      <description>&lt;h2&gt;
  
  
  The Debugging Trap With AI Code
&lt;/h2&gt;

&lt;p&gt;You built something cool with Cursor or Bolt. It worked on the demo. Then a user tried it with a slightly different input — and it blew up. You paste the error into the AI, it confidently rewrites the function, and now a &lt;em&gt;different&lt;/em&gt; thing is broken. Welcome to the debugging trap.&lt;/p&gt;

&lt;p&gt;Research from GitClear and others suggests &lt;strong&gt;around 43% of AI-generated projects need real debugging work before they're production-ready&lt;/strong&gt;. AI coding tools are good at the happy path — the flow you described in the prompt. They are bad at the unspoken edges: empty arrays, zero values, null users, timezone boundaries, race conditions.&lt;/p&gt;

&lt;p&gt;And here's the trap: when something breaks, the natural move is to paste the error into the AI and say "fix this." The AI does what you asked. It patches the symptom. The bug moves somewhere else. You paste &lt;em&gt;that&lt;/em&gt; error. Repeat. Pretty soon you have a codebase held together with duct tape and nobody — not you, not the AI — understands what's actually happening.&lt;/p&gt;

&lt;p&gt;There's a better way. It isn't magic. It's the same debugging discipline engineers have used for decades, just applied intentionally to AI-authored code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Just Ask AI to Fix It" Doesn't Work
&lt;/h2&gt;

&lt;p&gt;AI is a great debugging &lt;em&gt;assistant&lt;/em&gt;, but a terrible debugging &lt;em&gt;driver&lt;/em&gt;. Here's why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI doesn't know which parts are working.&lt;/strong&gt; From its perspective, everything in the file is suspect. It will often "fix" code that was fine and leave the actual bug untouched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Without a reproduction case, AI guesses.&lt;/strong&gt; If you say "sometimes the total is wrong," the AI has no way to know &lt;em&gt;when&lt;/em&gt; or &lt;em&gt;why&lt;/em&gt;. It will generate plausible-looking code that addresses a plausible-sounding problem. Plausible is not correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each "fix" can introduce new bugs.&lt;/strong&gt; AI can't see your app's runtime behavior. It can't observe the actual values. It's writing code based on pattern-matching, not evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The fix-depth problem.&lt;/strong&gt; AI tends to fix symptoms, not causes. A wrong total? Add a check in the caller. A null crash? Wrap it in a try/catch. The real bug — upstream, where the bad data was born — stays alive and keeps spawning new symptoms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is to stop outsourcing the &lt;em&gt;thinking&lt;/em&gt; part of debugging. Use AI as a tool inside a process you control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Systematic 5-Step Debugging Approach
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Reproduce Reliably
&lt;/h3&gt;

&lt;p&gt;You cannot fix what you cannot reproduce. Before anything else, get the bug to happen on demand.&lt;/p&gt;

&lt;p&gt;Write down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exact steps that trigger it&lt;/li&gt;
&lt;li&gt;The exact input values&lt;/li&gt;
&lt;li&gt;The expected output&lt;/li&gt;
&lt;li&gt;The actual output (including error messages and stack traces)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then try to shrink it. This is called a &lt;strong&gt;minimal reproduction case (MRC)&lt;/strong&gt;. Remove everything that isn't part of the bug. If the bug shows up when you submit a form with 20 fields, can you reproduce it with 3? With 1? The smaller the MRC, the faster every subsequent step goes.&lt;/p&gt;

&lt;p&gt;If you can't reliably reproduce it, don't skip to "fix." Keep poking until you can. An intermittent bug you can't reproduce is a bug you cannot verify you've actually fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Isolate the Scope
&lt;/h3&gt;

&lt;p&gt;Once you can reproduce, figure out &lt;em&gt;where&lt;/em&gt; the bug lives. The answer is almost never "the whole codebase."&lt;/p&gt;

&lt;p&gt;Techniques that work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Binary search.&lt;/strong&gt; Comment out half the code path. Does the bug still happen? If yes, it's in the remaining half. If no, it's in the commented half. Keep halving until you land on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs at boundaries.&lt;/strong&gt; Put a &lt;code&gt;console.log&lt;/code&gt; at the entry and exit of each function in the suspect path. Print the inputs and outputs. The bug is between the last log that looks right and the first log that looks wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git bisect.&lt;/strong&gt; If it used to work and now doesn't, &lt;code&gt;git bisect&lt;/code&gt; will pinpoint the commit that introduced the bug.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Suspect path: submit -&amp;gt; validate -&amp;gt; calculateTotal -&amp;gt; persist&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[submit] input:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[submit] validated:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[submit] total:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boring? Yes. Effective? Extremely. Four logs will usually cut the search space in half.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Form a Hypothesis (Not a Guess)
&lt;/h3&gt;

&lt;p&gt;Once you've narrowed the scope, stop and think. Before you change any code, write down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What do I think is happening?&lt;/strong&gt; In one sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What would prove me right?&lt;/strong&gt; A specific log value, a specific state, a specific code path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What would prove me wrong?&lt;/strong&gt; Because you might be wrong, and you want to know fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds fussy. It isn't. The difference between a hypothesis and a guess is that a hypothesis is &lt;em&gt;falsifiable&lt;/em&gt;. "Maybe the discount is being applied twice" is a hypothesis — you can check it. "Something's wrong with the pricing" is a vibe, and vibes lead to vibe-fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Verify With Evidence, Not Vibes
&lt;/h3&gt;

&lt;p&gt;Test the hypothesis against reality. Read the actual values. Don't assume, observe.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;console.log&lt;/code&gt; the specific variable at the specific line&lt;/li&gt;
&lt;li&gt;Drop a &lt;code&gt;debugger;&lt;/code&gt; statement and step through in DevTools&lt;/li&gt;
&lt;li&gt;Set a breakpoint in your editor's debugger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This also applies to AI output. If the AI says "the bug is that &lt;code&gt;discount&lt;/code&gt; is undefined at this line," don't just trust it — &lt;code&gt;console.log(discount)&lt;/code&gt; and see. AI is often close but not exactly right, and acting on a confident wrong diagnosis wastes more time than verifying it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust but verify&lt;/strong&gt; applies to AI the same way it applies to your own assumptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Fix the Root, Not the Symptom
&lt;/h3&gt;

&lt;p&gt;Once you've confirmed the hypothesis, resist the urge to patch the nearest visible symptom. Trace upstream.&lt;/p&gt;

&lt;p&gt;If a function returned a wrong number, don't add &lt;code&gt;if (total &amp;lt; 0) total = 0&lt;/code&gt; in the caller. Ask: &lt;em&gt;why&lt;/em&gt; did it return a wrong number? Where did the bad input come from? Fix it there.&lt;/p&gt;

&lt;p&gt;A common anti-pattern: wrapping symptoms in if-statements and try/catches. It feels like progress because the error goes away. But the bad data is still flowing through your system; you've just muffled its scream. A month later, the same root cause shows up in a different shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use AI Effectively During Debugging
&lt;/h2&gt;

&lt;p&gt;AI belongs inside this process, not on top of it. Use it like a sharp tool with a specific job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Give AI the MRC, not the whole file.&lt;/strong&gt; A 10-line reproduction is easier for both of you to reason about than 400 lines of context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share the actual evidence.&lt;/strong&gt; The error message, the stack trace, the input values, the expected vs actual output. Not "it doesn't work."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask AI to explain its hypothesis before writing code.&lt;/strong&gt; "What do you think is happening, and why?" If the explanation doesn't match what you've observed, don't let it write the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the fix doesn't work, go back to Step 1.&lt;/strong&gt; Don't ask AI to "try again." That's how you end up with ten rounds of whack-a-mole. If the fix failed, your hypothesis was wrong. Re-reproduce, re-isolate, re-hypothesize.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also: good tests make debugging dramatically faster, because they tell you which parts of the code are already proven to work. Tests catch regressions while you debug, so you're not accidentally breaking other things while chasing the current bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real Debugging Example
&lt;/h2&gt;

&lt;p&gt;Let's make this concrete. Here's a function an AI generated for calculating an order total with a discount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pricing.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;discountPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;discountPercent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your users are reporting that when they apply no discount, the total comes out as &lt;code&gt;NaN&lt;/code&gt;. Let's walk through the 5 steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Reproduce.&lt;/strong&gt; In a test or a REPL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// expected: 200, actual: NaN&lt;/span&gt;
&lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// NaN&lt;/span&gt;
&lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// NaN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed: it breaks when &lt;code&gt;discountPercent&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;, or missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Isolate.&lt;/strong&gt; Add logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;price:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quantity:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discount%:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discountPercent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subtotal:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;discountPercent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discount:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output for the zero case shows &lt;code&gt;subtotal: 200&lt;/code&gt;, &lt;code&gt;discount: 0&lt;/code&gt;, total: &lt;code&gt;200&lt;/code&gt;. So the zero case is actually fine. It's the &lt;em&gt;missing&lt;/em&gt; case that prints &lt;code&gt;discount: NaN&lt;/code&gt;, because &lt;code&gt;undefined / 100&lt;/code&gt; is &lt;code&gt;NaN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Hypothesis.&lt;/strong&gt; When &lt;code&gt;discountPercent&lt;/code&gt; is &lt;code&gt;undefined&lt;/code&gt; (not passed in), &lt;code&gt;undefined / 100&lt;/code&gt; produces &lt;code&gt;NaN&lt;/code&gt;, which then contaminates &lt;code&gt;subtotal - discount&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Verify.&lt;/strong&gt; &lt;code&gt;console.log(undefined / 100)&lt;/code&gt; in DevTools → &lt;code&gt;NaN&lt;/code&gt;. Confirmed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Fix the root.&lt;/strong&gt; The root cause is that the function doesn't handle the "no discount" case. Don't patch the caller; fix the signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pricing.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;discountPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;discountPercent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One character fixed the bug. But you wouldn't have known &lt;em&gt;which&lt;/em&gt; character without the 5 steps. Notice how much more useful this is than pasting "my total is NaN, fix it" into the AI — which might have rewritten the whole function, added a try/catch, or coerced every input to a number just in case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Tools Vibe Coders Should Know
&lt;/h2&gt;

&lt;p&gt;A small toolkit goes a very long way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser DevTools.&lt;/strong&gt; Console, Network, Sources. Learn the Sources tab specifically — breakpoints, watch expressions, and the call stack will save you more time than any AI prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategic &lt;code&gt;console.log&lt;/code&gt;.&lt;/strong&gt; Log at boundaries, print the variable name with the value (&lt;code&gt;console.log('user:', user)&lt;/code&gt;), and clean up when you're done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;debugger&lt;/code&gt; statement.&lt;/strong&gt; Drop &lt;code&gt;debugger;&lt;/code&gt; anywhere in your code and execution will pause there when DevTools is open. Step through line by line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error tracking (Sentry free tier).&lt;/strong&gt; Captures errors from real users in production with full stack traces and the state at the moment of failure. You stop debugging in the dark.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honest truth: for a lot of everyday bugs, a developer who knows DevTools is faster than any AI. The AI has to reason about possibilities. You can look at the actual value.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Debugging Reveals Architectural Problems
&lt;/h2&gt;

&lt;p&gt;Sometimes the problem isn't the bug in front of you. It's the shape of the code around it.&lt;/p&gt;

&lt;p&gt;Watch for these signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You keep fixing bugs in the same file or module&lt;/li&gt;
&lt;li&gt;Every fix breaks two other things&lt;/li&gt;
&lt;li&gt;You can't explain how the data flows from input to output&lt;/li&gt;
&lt;li&gt;The same logic is duplicated in three places, and you have to fix each one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When this happens, debugging is telling you something bigger: the code is tightly coupled, the abstractions are missing, the data flow is unclear. These are signs that refactoring — not more patches — is the real fix.&lt;/p&gt;

&lt;p&gt;And if every bug fix seems to break something else, you might be past the point where you can debug your way out of it. That's when it's time to bring in help to stabilize the codebase before you keep shipping on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Debugging is a skill, not a magic AI trick. Each bug you debug properly makes you measurably better at the next one — you build intuition about where bugs hide, which tools to reach for, and which AI fixes to trust.&lt;/p&gt;

&lt;p&gt;AI accelerates debugging when you use it inside a real process. It replaces debugging when you use it instead of one. The difference is whether you're in control, or whether you're stuck in a loop asking "fix it" until the code is unrecognizable and the bug is still there.&lt;/p&gt;

&lt;p&gt;Next time something breaks: reproduce, isolate, hypothesize, verify, fix the root. In that order. Every time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/debug-ai-generated-code" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;. If you're struggling with an AI-built codebase that's become unmanageable, &lt;a href="https://bivecode.com/rescue" rel="noopener noreferrer"&gt;BiveCode Rescue&lt;/a&gt; can help.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>debugging</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why AI Coding Goes Off the Rails — And How to Take Back Control</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Thu, 16 Apr 2026 06:44:54 +0000</pubDate>
      <link>https://forem.com/jakay/why-ai-coding-goes-off-the-rails-and-how-to-take-back-control-3nmb</link>
      <guid>https://forem.com/jakay/why-ai-coding-goes-off-the-rails-and-how-to-take-back-control-3nmb</guid>
      <description>&lt;h2&gt;
  
  
  The Honeymoon Phase
&lt;/h2&gt;

&lt;p&gt;You remember the moment. You opened Cursor, or Bolt, or Claude for the first time, typed something like "Build me a landing page with a hero section and a pricing table," and watched as fully functional code materialized in front of you. In minutes, not hours.&lt;/p&gt;

&lt;p&gt;You kept going. "Add a contact form." Done. "Make it responsive." Done. "Add dark mode." Done. You felt like a 10x developer. The possibilities felt limitless. You started telling friends about it. You stayed up late building things you'd been putting off for months.&lt;/p&gt;

&lt;p&gt;This phase is real. It's not a trick. AI coding tools genuinely are that powerful for getting something off the ground. The problem is what happens next.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Things Start Breaking
&lt;/h2&gt;

&lt;p&gt;It's subtle at first. Around the time your project hits 20-30 files, something shifts. The AI that was nailing everything starts... stumbling.&lt;/p&gt;

&lt;p&gt;You notice it in small ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It forgets decisions it made earlier.&lt;/strong&gt; You established a pattern for how components fetch data, but the AI starts using a completely different approach in new files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It contradicts its own patterns.&lt;/strong&gt; Half your files use one error-handling style, the other half use another. Neither is wrong, but the inconsistency makes everything harder to follow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It breaks existing features while adding new ones.&lt;/strong&gt; You ask for a new settings page and suddenly your authentication stops working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Its fixes create new problems.&lt;/strong&gt; You point out a bug, it patches it, and now something else is broken. You fix that, and the original bug comes back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It goes in circles.&lt;/strong&gt; Fix this, breaks that. Fix that, breaks this. You spend an hour and end up back where you started, except now the code is messier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this sounds familiar, you're not alone. This is the experience of almost every developer who uses AI tools on a project beyond a certain size. And it's not because the AI got dumber or because you're doing something wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Happens: The Context Problem
&lt;/h2&gt;

&lt;p&gt;Here's the thing most people don't understand about AI coding tools: &lt;strong&gt;they have a limited working memory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI models operate within something called a &lt;strong&gt;context window&lt;/strong&gt; — the amount of text they can "see" and think about at any given time. Think of it like a desk. When your project is small, all your files fit on the desk. The AI can see everything at once — every component, every function, every decision you've made together.&lt;/p&gt;

&lt;p&gt;But as your project grows, files start falling off the desk. The AI can only look at a slice of your codebase at a time. It literally &lt;strong&gt;cannot see&lt;/strong&gt; what's in the other files. It doesn't know what patterns were established. It doesn't remember what constraints exist. It doesn't recall the naming conventions you agreed on three sessions ago.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's like asking someone to renovate your house, but they can only see one room at a time. They might pick a great paint color for the kitchen — that clashes horribly with the living room they can't see.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And here's the part that really stings: &lt;strong&gt;each conversation starts fresh.&lt;/strong&gt; When you open a new chat or start a new session, the AI has zero memory of your previous interactions. It doesn't know about the bug you spent two hours fixing yesterday. It doesn't know about the architectural decision you made last week. Every time, it's meeting your project for the first time.&lt;/p&gt;

&lt;p&gt;This isn't a flaw that'll be fixed in the next update. It's a fundamental characteristic of how these models work. And once you understand it, the frustrating behavior makes perfect sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Snowball Effect
&lt;/h2&gt;

&lt;p&gt;Here's where it gets really painful. When things break, the natural instinct is to tell the AI: "Fix it." And the AI obliges — it patches the immediate symptom. But because it can't see the full picture, it's not fixing the root cause. It's applying a band-aid.&lt;/p&gt;

&lt;p&gt;Each band-aid adds complexity. And that complexity makes it even harder for the AI to understand the codebase on the next request. So the next fix is even more likely to be a band-aid. And the cycle continues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Something breaks&lt;/li&gt;
&lt;li&gt;AI patches the symptom&lt;/li&gt;
&lt;li&gt;The patch adds complexity&lt;/li&gt;
&lt;li&gt;The added complexity causes something else to break&lt;/li&gt;
&lt;li&gt;Go to step 1&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your codebase becomes a patchwork of contradictory patterns, redundant logic, and fragile workarounds. Eventually the AI is spending more tokens trying to understand the mess than actually solving the problem you asked about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is why vibe coding hits a wall.&lt;/strong&gt; Not because AI is bad at coding. Not because you're not technical enough. But because the approach of "just keep prompting" doesn't scale. The more you build, the less effective each prompt becomes, until you're fighting the tool instead of building with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift: From Prompting to Harness Engineering
&lt;/h2&gt;

&lt;p&gt;When people hit this wall, their first instinct is to write better prompts. More detailed instructions. Longer explanations. "Be very careful not to break the auth system when you add this feature."&lt;/p&gt;

&lt;p&gt;This helps a little. But it's treating the symptom, not the disease. You can't prompt your way out of a structural problem.&lt;/p&gt;

&lt;p&gt;The real unlock is something we call &lt;strong&gt;harness engineering&lt;/strong&gt; — designing the environment and structure that the AI works within.&lt;/p&gt;

&lt;p&gt;Think about it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt engineering&lt;/strong&gt; is telling the AI &lt;em&gt;what&lt;/em&gt; to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harness engineering&lt;/strong&gt; is setting up &lt;em&gt;where&lt;/em&gt; and &lt;em&gt;how&lt;/em&gt; the AI works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A "harness" is everything that surrounds the AI: the project structure, the rules it follows, the context it receives, the guardrails that catch its mistakes. It's the difference between dropping a builder in an empty field and saying "build a house," versus handing them blueprints, a materials list, and a building code.&lt;/p&gt;

&lt;p&gt;When you invest in the harness, the AI performs dramatically better — even with the exact same prompts you were using before. Because the environment compensates for the AI's limitations.&lt;/p&gt;

&lt;p&gt;Here's what harness engineering looks like in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; / &lt;code&gt;.cursorrules&lt;/code&gt; files&lt;/strong&gt; that give the AI persistent context about your project — your tech stack, your conventions, your constraints. Things the AI would otherwise forget between sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean folder structure&lt;/strong&gt; so the AI only needs to see the relevant files for any given task, keeping more of the important context on that "desk."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module boundaries&lt;/strong&gt; that limit the blast radius of AI changes. If the AI makes a mistake in the settings module, it shouldn't be able to break authentication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test suites&lt;/strong&gt; that catch when the AI breaks something, before you even notice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory systems&lt;/strong&gt; that preserve decisions across sessions — so the AI doesn't have to rediscover your architecture every time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these require you to be a senior engineer. They require you to think like an &lt;strong&gt;architect&lt;/strong&gt;, not a prompter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Steps to Take Back Control
&lt;/h2&gt;

&lt;p&gt;You don't need to rebuild everything from scratch. Here are concrete things you can do today to improve how AI works with your project:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Set Up a CLAUDE.md or .cursorrules
&lt;/h3&gt;

&lt;p&gt;This is the single highest-leverage thing you can do. Create a file that tells the AI everything it keeps forgetting: your tech stack, your folder structure, your naming conventions, your patterns, your constraints. The AI reads this at the start of every session, giving it the context it would otherwise lack.&lt;/p&gt;

&lt;p&gt;We wrote a full guide on how to do this well: &lt;a href="https://bivecode.com/blog/claude-md-guide" rel="noopener noreferrer"&gt;How to Make AI Tools Understand Your Codebase&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Break Your Project Into Modules
&lt;/h3&gt;

&lt;p&gt;Smaller, self-contained pieces mean less context needed per task. When your auth logic is in its own module with clear boundaries, the AI can work on it without needing to understand your entire app. Feature-based folder structure is a great starting point — see our guide on &lt;a href="https://bivecode.com/blog/structure-ai-project" rel="noopener noreferrer"&gt;how to structure your AI project&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Give AI One Job at a Time
&lt;/h3&gt;

&lt;p&gt;Instead of "Build the auth system with social login, password reset, 2FA, and admin panel," try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Add a basic email/password login page"&lt;/li&gt;
&lt;li&gt;"Add password reset functionality"&lt;/li&gt;
&lt;li&gt;"Add Google OAuth login"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each focused request is more likely to succeed because it requires less context and produces smaller, more reviewable changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Add Tests Before Adding Features
&lt;/h3&gt;

&lt;p&gt;Tests are your safety net. When the AI changes something, tests tell you immediately if it broke something else. You don't need 100% coverage. Even basic tests for your critical paths — login works, data saves correctly, pages load — will catch the most damaging regressions.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Review Before Accepting
&lt;/h3&gt;

&lt;p&gt;This is the hardest habit to build. AI-generated code comes out fast and looks professional, so it's tempting to accept it without reading. Don't. Spend 30 seconds scanning each change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it follow your existing patterns?&lt;/li&gt;
&lt;li&gt;Did it change files it shouldn't have touched?&lt;/li&gt;
&lt;li&gt;Does the logic actually make sense, or does it just look right?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to understand every line. But you should understand the &lt;em&gt;shape&lt;/em&gt; of the change.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Version Control Religiously
&lt;/h3&gt;

&lt;p&gt;Commit every working state. Every one. When the AI inevitably breaks something, you can roll back to the last good version instead of trying to untangle the damage. &lt;code&gt;git commit&lt;/code&gt; is your undo button. Use it early and often.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mindset Shift
&lt;/h2&gt;

&lt;p&gt;Here's the most important thing to internalize: &lt;strong&gt;you're not a prompter. You're an architect.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The AI is the builder. It's fast, it's capable, and it works tirelessly. But builders need blueprints. They need someone who understands the whole structure, who can see across all the rooms at once, who makes sure the kitchen paint works with the living room.&lt;/p&gt;

&lt;p&gt;That's you.&lt;/p&gt;

&lt;p&gt;The best AI-assisted developers don't write the most code. They don't write the fanciest prompts. They design the best &lt;strong&gt;structures&lt;/strong&gt; for AI to work within. They set up the harness so well that the AI almost can't fail.&lt;/p&gt;

&lt;p&gt;And that's a skill worth developing — because as AI tools get better, the people who know how to direct them effectively will build things the rest of the world didn't think were possible.&lt;/p&gt;




&lt;p&gt;If your project has already gone off the rails and you're not sure how to get it back on track, &lt;a href="https://bivecode.com/rescue" rel="noopener noreferrer"&gt;we can help&lt;/a&gt;. We specialize in taking AI-built codebases and giving them the structure they need to keep growing.&lt;/p&gt;

&lt;p&gt;And if you want to prevent this from happening on your next project, start with our guide on &lt;a href="https://bivecode.com/blog/structure-ai-project" rel="noopener noreferrer"&gt;how to structure your AI project from day one&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/why-ai-coding-goes-off-rails" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Cursor Rules vs CLAUDE.md: When to Use Which (And How They Work Together)</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Thu, 16 Apr 2026 06:39:12 +0000</pubDate>
      <link>https://forem.com/jakay/cursor-rules-vs-claudemd-when-to-use-which-and-how-they-work-together-5hg</link>
      <guid>https://forem.com/jakay/cursor-rules-vs-claudemd-when-to-use-which-and-how-they-work-together-5hg</guid>
      <description>&lt;p&gt;If you're using AI coding tools seriously, you've probably heard of both &lt;code&gt;.cursorrules&lt;/code&gt; and &lt;code&gt;CLAUDE.md&lt;/code&gt;. They solve the same fundamental problem: &lt;strong&gt;giving AI persistent context about your project.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without them, every AI session starts from scratch. The AI doesn't know your tech stack, your folder conventions, your naming patterns, or the quirks that make your project unique. You end up repeating the same instructions over and over.&lt;/p&gt;

&lt;p&gt;Both files fix this. But they work differently, they're read by different tools, and they have different strengths. Most developers pick one and ignore the other. That's a mistake — they're most powerful when used together.&lt;/p&gt;

&lt;h2&gt;
  
  
  What .cursorrules Does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.cursorrules&lt;/code&gt; is Cursor's project-level configuration file. When you open a project in Cursor, it automatically reads this file and applies the rules to every AI interaction within that project.&lt;/p&gt;

&lt;p&gt;Key characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool-specific&lt;/strong&gt; — only Cursor reads it. Other AI tools ignore it completely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instruction-focused&lt;/strong&gt; — best for telling the AI what to do and what not to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Applies to all interactions&lt;/strong&gt; — every chat, every inline edit, every Cmd+K generation follows these rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports glob patterns&lt;/strong&gt; — you can scope rules to specific file types or directories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical &lt;code&gt;.cursorrules&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an expert in TypeScript, React, Next.js App Router, and Tailwind CSS.

Key Principles:
- Write concise, technical TypeScript code
- Use functional and declarative programming patterns
- Prefer iteration and modularization over duplication
- Use descriptive variable names with auxiliary verbs (isLoading, hasError)

Naming Conventions:
- Components: PascalCase (UserProfile.tsx)
- Hooks: camelCase with "use" prefix (useAuth.ts)
- Utilities: camelCase (formatDate.ts)
- Constants: SCREAMING_SNAKE_CASE

DO NOT:
- Use `any` type
- Use default exports (except for pages)
- Put business logic in components
- Use inline styles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What CLAUDE.md Does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; is Claude's project context file. Claude Code, Claude in the terminal, and other Claude-powered tools read it automatically when they enter your project directory.&lt;/p&gt;

&lt;p&gt;Key characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool-specific&lt;/strong&gt; — Claude tools read it. Cursor doesn't (unless you explicitly include it in context).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context-focused&lt;/strong&gt; — best for explaining your project's architecture, decisions, and constraints. Not just rules, but &lt;em&gt;why&lt;/em&gt; those rules exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hierarchical&lt;/strong&gt; — you can have a root &lt;code&gt;CLAUDE.md&lt;/code&gt; plus subdirectory-specific ones (e.g., &lt;code&gt;src/components/CLAUDE.md&lt;/code&gt;). Claude merges them based on what files it's working with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports project commands&lt;/strong&gt; — you can define shortcuts for common tasks like building, testing, and deploying.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical &lt;code&gt;CLAUDE.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project Overview&lt;/span&gt;
Next.js 15 SaaS app for invoice management.
&lt;span class="p"&gt;-&lt;/span&gt; Frontend: React 19, TypeScript, Tailwind CSS
&lt;span class="p"&gt;-&lt;/span&gt; Backend: Next.js API routes, Drizzle ORM
&lt;span class="p"&gt;-&lt;/span&gt; Database: PostgreSQL (Supabase)
&lt;span class="p"&gt;-&lt;/span&gt; Auth: Better Auth with Google OAuth
&lt;span class="p"&gt;-&lt;/span&gt; Payments: Stripe
&lt;span class="p"&gt;-&lt;/span&gt; Deployment: Vercel

&lt;span class="gh"&gt;# Architecture&lt;/span&gt;
Feature-based folder structure:
src/
  app/         # Pages and API routes
  features/    # Feature modules (auth/, billing/, invoices/)
  components/  # Shared UI components
  lib/         # Utilities, DB client, API helpers

&lt;span class="gh"&gt;# Important Constraints&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Auth middleware runs on Edge Runtime — no Node.js APIs
&lt;span class="p"&gt;-&lt;/span&gt; All monetary values stored as integers (cents), displayed as decimals
&lt;span class="p"&gt;-&lt;/span&gt; Invoices use optimistic locking — always check version before update

&lt;span class="gh"&gt;# Commands&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Build: npm run build
&lt;span class="p"&gt;-&lt;/span&gt; Test: npm run test
&lt;span class="p"&gt;-&lt;/span&gt; Dev: npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Key Differences
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;.cursorrules&lt;/th&gt;
&lt;th&gt;CLAUDE.md&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read by&lt;/td&gt;
&lt;td&gt;Cursor only&lt;/td&gt;
&lt;td&gt;Claude tools only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary strength&lt;/td&gt;
&lt;td&gt;Coding rules and patterns&lt;/td&gt;
&lt;td&gt;Project context and architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tone&lt;/td&gt;
&lt;td&gt;Imperative ("Do this, don't do that")&lt;/td&gt;
&lt;td&gt;Descriptive ("Here's how the project works")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchy&lt;/td&gt;
&lt;td&gt;Single file at project root&lt;/td&gt;
&lt;td&gt;Root + subdirectory files, merged contextually&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Enforcing code style and patterns&lt;/td&gt;
&lt;td&gt;Explaining architecture and constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project commands&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Supported (build, test, lint)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glob scoping&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Via subdirectory files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When to Use Which
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use .cursorrules when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your team primarily uses Cursor as their AI coding tool&lt;/li&gt;
&lt;li&gt;You want to enforce strict coding patterns across all AI interactions&lt;/li&gt;
&lt;li&gt;Your rules are mostly about code style: naming, imports, error handling patterns&lt;/li&gt;
&lt;li&gt;You want rules that apply to inline edits and quick generations, not just chat&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use CLAUDE.md when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your team uses Claude Code or Claude-powered tools&lt;/li&gt;
&lt;li&gt;You need to document complex architecture that requires explanation, not just rules&lt;/li&gt;
&lt;li&gt;Your project has non-obvious constraints (edge runtime limitations, data format decisions, etc.)&lt;/li&gt;
&lt;li&gt;You want project commands integrated into the AI workflow&lt;/li&gt;
&lt;li&gt;Different parts of the codebase need different context (use subdirectory CLAUDE.md files)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use both when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Team members use different AI tools (some on Cursor, some on Claude)&lt;/li&gt;
&lt;li&gt;You want consistent AI behavior regardless of which tool is used&lt;/li&gt;
&lt;li&gt;You want the best of both: Cursor's pattern enforcement + Claude's architectural context&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Use Them Together
&lt;/h2&gt;

&lt;p&gt;Here's the approach that works best: &lt;strong&gt;put architectural context in CLAUDE.md, put coding rules in .cursorrules, and keep them in sync.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What goes in CLAUDE.md only:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Project overview and tech stack&lt;/li&gt;
&lt;li&gt;Architecture decisions and their reasoning&lt;/li&gt;
&lt;li&gt;Non-obvious constraints ("monetary values are stored as cents because...")&lt;/li&gt;
&lt;li&gt;Common tasks with step-by-step instructions&lt;/li&gt;
&lt;li&gt;Build/test/deploy commands&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What goes in .cursorrules only:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The persona prompt ("You are an expert in...")&lt;/li&gt;
&lt;li&gt;Cursor-specific behaviors (how to handle inline edits, tab completions)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What goes in both (keep synced):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Naming conventions&lt;/li&gt;
&lt;li&gt;Folder structure&lt;/li&gt;
&lt;li&gt;Import patterns&lt;/li&gt;
&lt;li&gt;Error handling approach&lt;/li&gt;
&lt;li&gt;"Do not" rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, there's some duplication. That's the cost of supporting multiple tools. The alternative — having rules in one file and not the other — means your AI behavior is inconsistent depending on which tool you're using. That's worse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping them in sync
&lt;/h3&gt;

&lt;p&gt;The practical approach: treat &lt;code&gt;CLAUDE.md&lt;/code&gt; as the source of truth for architecture and constraints, and &lt;code&gt;.cursorrules&lt;/code&gt; as the source of truth for coding patterns. When you update a shared rule (like naming conventions), update both files. It takes 30 seconds and prevents drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Making them too long
&lt;/h3&gt;

&lt;p&gt;Both files compete for the AI's attention window. A 2,000-line rules file means the AI is spending context on your instructions instead of on understanding the code it's working with. Aim for 200-500 lines max for each file.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Being vague
&lt;/h3&gt;

&lt;p&gt;"Write clean code" is useless in both files. "Use named exports for all non-page files" is actionable. Be specific enough that there's only one way to interpret the rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Never updating them
&lt;/h3&gt;

&lt;p&gt;Your project evolves. Your rules should too. If you switched from REST to tRPC three months ago but your CLAUDE.md still describes REST patterns, the AI will generate REST code. Review your files monthly.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Not including examples
&lt;/h3&gt;

&lt;p&gt;AI tools are pattern matchers. A rule like "use the repository pattern for database access" is vague. A rule with a 5-line code example of what that looks like in your project is crystal clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started Today
&lt;/h2&gt;

&lt;p&gt;If you have neither file, here's the 30-minute setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create CLAUDE.md&lt;/strong&gt; — write your project overview, tech stack, folder structure, and top 5 constraints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create .cursorrules&lt;/strong&gt; — write your coding conventions, naming rules, and "do not" list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy shared rules&lt;/strong&gt; — naming conventions, folder structure, and import patterns should appear in both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test it&lt;/strong&gt; — ask both tools to create a new component. Does the output match your conventions? If not, your rules aren't specific enough.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you already have one file, creating the other takes 15 minutes — most of the thinking is already done.&lt;/p&gt;




&lt;p&gt;For a deeper dive into CLAUDE.md specifically, check out our &lt;a href="https://bivecode.com/blog/claude-md-guide" rel="noopener noreferrer"&gt;CLAUDE.md setup guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/cursor-rules-vs-claude-md" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Vibe Coding Security Checklist: 7 Things to Check Before You Ship</title>
      <dc:creator>Junkyu Jeon</dc:creator>
      <pubDate>Thu, 16 Apr 2026 06:36:05 +0000</pubDate>
      <link>https://forem.com/jakay/the-vibe-coding-security-checklist-7-things-to-check-before-you-ship-4e66</link>
      <guid>https://forem.com/jakay/the-vibe-coding-security-checklist-7-things-to-check-before-you-ship-4e66</guid>
      <description>&lt;p&gt;Here's a stat that should keep you up at night: &lt;strong&gt;24.7% of AI-generated code contains security vulnerabilities.&lt;/strong&gt; Nearly 45% of those hit the OWASP Top 10 — the most common, most exploitable categories of web security flaws.&lt;/p&gt;

&lt;p&gt;This isn't because AI is bad at coding. It's because AI optimizes for making things &lt;em&gt;work&lt;/em&gt;, not making them &lt;em&gt;safe&lt;/em&gt;. When you ask it to "add a user login," it builds a functional login. It doesn't think about session fixation, brute force protection, or what happens when someone puts a SQL statement in the email field.&lt;/p&gt;

&lt;p&gt;If you've built an app with Cursor, Bolt.new, Lovable, or any AI coding tool and you're about to ship it to real users — run through this checklist first. Each item takes 5-15 minutes. All of them together could save you from a breach that kills your product.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Secrets Are Not in Your Code
&lt;/h2&gt;

&lt;p&gt;This is the most common and most dangerous mistake in AI-built apps. AI tools love to hardcode API keys, database URLs, and secrets directly in the source code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys in JavaScript/TypeScript files (search for &lt;code&gt;sk-&lt;/code&gt;, &lt;code&gt;pk_&lt;/code&gt;, &lt;code&gt;key_&lt;/code&gt;, &lt;code&gt;secret&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Database connection strings in source code&lt;/li&gt;
&lt;li&gt;JWT secrets hardcoded in auth files&lt;/li&gt;
&lt;li&gt;Third-party service credentials (Stripe, SendGrid, AWS) in client-side code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Search your codebase for exposed secrets&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"sk_live&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;sk_test&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;secret&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;api_key&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;apiKey"&lt;/span&gt; src/

&lt;span class="c"&gt;# Move all secrets to environment variables&lt;/span&gt;
&lt;span class="c"&gt;# .env.local (never committed to git)&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres://...
&lt;span class="nv"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk_live_...
&lt;span class="nv"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-secret-here

&lt;span class="c"&gt;# .gitignore (make sure this exists)&lt;/span&gt;
.env
.env.local
.env.production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical:&lt;/strong&gt; If secrets were ever committed to git, they're in the history even after removal. Rotate them immediately — generate new keys and revoke the old ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. All User Input Is Validated
&lt;/h2&gt;

&lt;p&gt;AI-generated code almost never validates input properly. It trusts whatever the user sends, which opens the door to injection attacks, crashes, and data corruption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form inputs used directly without validation&lt;/li&gt;
&lt;li&gt;API route parameters used without type checking&lt;/li&gt;
&lt;li&gt;Database queries built from user input (SQL injection risk)&lt;/li&gt;
&lt;li&gt;File uploads without type/size restrictions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use Zod for input validation in API routes&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CreateUserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CreateUserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... safe to use&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validate on the server, always. Client-side validation is for UX — server-side validation is for security. Never trust the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Authentication Is Actually Protecting Routes
&lt;/h2&gt;

&lt;p&gt;AI tools often generate auth that &lt;em&gt;looks&lt;/em&gt; right but has gaps. The login page works, but the API routes behind it? Wide open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API routes that should require authentication but don't check for a session&lt;/li&gt;
&lt;li&gt;Middleware that checks auth on some routes but misses others&lt;/li&gt;
&lt;li&gt;Admin-only endpoints accessible to regular users (broken access control)&lt;/li&gt;
&lt;li&gt;Direct object references — can user A access user B's data by changing an ID in the URL?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick test:&lt;/strong&gt; Open your browser's dev tools, find an API call your app makes, copy it as a cURL command, remove the auth cookie/token, and run it. If it still returns data, your route isn't protected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Every protected API route should start with this&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// For user-specific data, always filter by the authenticated user&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// NOT from URL params&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. No Sensitive Data in Client-Side Code
&lt;/h2&gt;

&lt;p&gt;Everything in your frontend JavaScript is visible to anyone who opens dev tools. AI tools frequently put things on the client side that should stay on the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys in &lt;code&gt;.env&lt;/code&gt; files prefixed with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; that shouldn't be public&lt;/li&gt;
&lt;li&gt;Business logic that reveals pricing algorithms, discount rules, or internal calculations&lt;/li&gt;
&lt;li&gt;Error messages that expose database schema, file paths, or internal system details&lt;/li&gt;
&lt;li&gt;User data from other users leaking into API responses (overfetching)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only prefix env variables with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; if they're truly meant to be public&lt;/li&gt;
&lt;li&gt;Move business logic to server-side API routes or server components&lt;/li&gt;
&lt;li&gt;Return generic error messages to the client, log detailed errors on the server&lt;/li&gt;
&lt;li&gt;Shape API responses to include only what the requesting user needs to see&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Rate Limiting Exists
&lt;/h2&gt;

&lt;p&gt;Without rate limiting, anyone can hammer your API thousands of times per second. This leads to DDoS vulnerability, brute force attacks on login, and runaway costs on pay-per-use services (like AI APIs).&lt;/p&gt;

&lt;p&gt;AI-generated code almost never includes rate limiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to protect:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Login/signup endpoints&lt;/strong&gt; — prevent brute force (5-10 attempts per minute)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI generation endpoints&lt;/strong&gt; — prevent cost abuse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password reset&lt;/strong&gt; — prevent email bombing (2-3 per hour)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;General API&lt;/strong&gt; — prevent abuse (100-1000 requests per minute)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Using Upstash Redis for serverless rate limiting&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Ratelimit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@upstash/ratelimit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@upstash/redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ratelimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEnv&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slidingWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1 m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-forwarded-for&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ratelimit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Too many requests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ... handle request&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Dependencies Are Not a Liability
&lt;/h2&gt;

&lt;p&gt;AI tools add npm packages liberally. Each dependency is code you didn't write running in your app. Some may have known vulnerabilities, be abandoned, or even be malicious.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check for known vulnerabilities&lt;/span&gt;
npm audit

&lt;span class="c"&gt;# See what you've got installed&lt;/span&gt;
npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="c"&gt;# Check for unused dependencies&lt;/span&gt;
npx depcheck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix critical and high vulnerabilities. Remove packages you don't use.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. HTTPS, Headers, and CORS Are Configured
&lt;/h2&gt;

&lt;p&gt;The boring infrastructure stuff that AI never sets up but attackers always check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Essential security headers&lt;/strong&gt; (add to &lt;code&gt;next.config.ts&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;securityHeaders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Content-Type-Options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nosniff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Frame-Options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DENY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-XSS-Protection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1; mode=block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Referrer-Policy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;strict-origin-when-cross-origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/(.*)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;securityHeaders&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CORS:&lt;/strong&gt; If your API is only used by your own frontend, don't enable CORS at all. If you need CORS, whitelist specific origins — never use &lt;code&gt;*&lt;/code&gt; in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 15-Minute Security Sprint
&lt;/h2&gt;

&lt;p&gt;If you can only do one thing from this list, here's the priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Secrets audit&lt;/strong&gt; (5 min) — grep for exposed keys. Highest-impact, easiest-to-exploit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth check&lt;/strong&gt; (5 min) — test your API routes without authentication. Find the gaps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input validation&lt;/strong&gt; (5 min) — add Zod to your most critical endpoint.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These three catch the majority of real-world attacks on AI-built apps.&lt;/p&gt;




&lt;p&gt;A security breach doesn't just break your app. It breaks trust. Users whose data gets leaked don't come back.&lt;/p&gt;

&lt;p&gt;AI made it possible to build an app in a weekend. But shipping it without a security review is like driving a car without brakes — it works fine until it doesn't, and when it fails, it fails catastrophically.&lt;/p&gt;

&lt;p&gt;Spend the hour. Run the checklist. Ship with confidence.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://bivecode.com/blog/vibe-coding-security-checklist" rel="noopener noreferrer"&gt;bivecode.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
