This metrics tool terrifies bad developers

Start free trial
SitePoint Premium
Stay Relevant and Grow Your Career in Tech
  • Premium Results
  • Publish articles on SitePoint
  • Daily curated jobs
  • Learning Paths
  • Discounts to dev tools
Start Free Trial

7 Day Free Trial. Cancel Anytime.

Vibe coding in 2026 looks nothing like the casual experiment Andrej Karpathy described in early 2025. What started as a catchy label for prompting an LLM and "just vibing" with whatever code came back has matured into a structured AI-first development methodology. The tooling has evolved, the workflows have formalized, and the output quality has crossed a threshold that makes this approach viable for production software. For intermediate developers looking to accelerate their iteration speed, understanding how to wield vibe coding effectively is no longer optional curiosity. It is a practical skill with direct career implications.

Table of Contents

What Is Vibe Coding in 2026?

The Original Definition vs. Where We Are Now

When Karpathy introduced the term in early 2025, he described a workflow where a developer surrenders detailed control to an LLM, accepts the generated code largely as-is, and focuses on describing intent in natural language. The original framing was deliberately casual, paraphrasing Karpathy's description: "I just see things, say things, run things, and copy-paste things, and it mostly works."

The 2026 reality is significantly more disciplined. Vibe coding now means a developer writes natural-language specs, and AI generates code under structured human oversight, with multi-model orchestration, persistent project context, and layered validation. It is not no-code, and it is not low-code. The developer remains deeply involved in specification, architectural review, and quality control. The shift is in the medium of expression: instead of writing implementation code line by line, the developer writes precise specifications, reviews generated output, and steers AI agents through conversational refinement. The "vibe" has become less about casualness and more about fluency in directing AI systems toward correct, maintainable software.

The shift is in the medium of expression: instead of writing implementation code line by line, the developer writes precise specifications, reviews generated output, and steers AI agents through conversational refinement.

When to Use Vibe Coding (and When Not To)

Vibe coding excels in contexts where speed of iteration matters more than micro-level optimization. MVPs, prototypes, internal tools, solo projects, and CRUD-heavy applications are ideal candidates. It is particularly effective when the developer understands the domain well enough to evaluate output quality but wants to collapse the time between idea and working software.

It remains a poor fit for safety-critical systems (medical devices, avionics), highly regulated codebases requiring formal verification, and performance-sensitive low-level code where memory layout and instruction-level optimization matter. AI models generate plausible code, not provably correct code, and that distinction becomes dangerous in certain domains.

Should You Vibe Code This Project?

If the project is an MVP, prototype, or internal tool, vibe coding fits well. If you can personally review and understand every line of generated code, proceed. Projects involving safety-critical or life-affecting systems demand hand-coded implementations. Strict regulatory compliance requirements likewise call for manual control.

The core challenge being algorithmic performance at the systems level? Vibe coding adds risk there. On the other hand, rapid iteration with short feedback loops is exactly where this approach pays off. Solo or small-team projects without extensive code review infrastructure can benefit, but you must enforce your own review discipline.

The 2026 Vibe Coding Tech Stack

AI Models and Agents

The set of available models in 2026 offers genuine specialization options. As of publication, verify current model availability and exact names at each provider's documentation: Anthropic models, OpenAI models, Google Gemini models. For complex multi-file reasoning and long-context architectural tasks, Anthropic's Claude family handles diffs spanning tens of thousands of tokens without losing track of cross-file references, making it well-suited for planning phases and large refactors. OpenAI's frontier models produce usable output even when the prompt omits edge-case details, which helps during rapid prototyping with incomplete specs. In data-heavy workflows and polyglot codebases, Google's Gemini models generate correct data-pipeline code with fewer revision cycles, though your mileage will vary by task complexity.

The more significant shift, however, is in coding agents. Cursor's Agent mode, Claude Code, Windsurf, and GitHub Copilot's agent mode all operate as autonomous coding agents capable of reading project context, executing commands, modifying files across a codebase, and running tests. These are not autocomplete tools. They take a task description, formulate a plan, and execute multi-step implementation with file system access. Note that agent capabilities vary by plan, version, and provider; check each tool's current documentation for availability and pricing.

Multi-model orchestration is a pattern gaining traction where teams route different phases of work to different models: one model for architectural planning, another for implementation, and a third for code review. Tools like LangChain and custom routing layers can facilitate this. This plays to each model's strengths rather than relying on a single model for everything.

Supporting Infrastructure

Context management is the backbone of effective vibe coding. Without persistent project context, AI agents lose coherence across sessions. Two key mechanisms have emerged: .cursor/rules files for Cursor-based workflows and CLAUDE.md files for Claude Code environments. These files define project conventions, tech stack constraints, coding standards, and behavioral directives that the AI agent reads before every interaction.

Place CLAUDE.md in the project root. Claude Code reads it automatically when invoked from that directory. Commit it to version control so all team members share the same agent context.

# CLAUDE.md — Project Context File

## Project Overview
Task management API built with Node.js 22, Express 5, and PostgreSQL 16.
Uses TypeORM for database access. REST API with JSON responses.

## Coding Standards
- Use TypeScript strict mode in all files
- All functions must have explicit return types
- Use named exports, no default exports
- Error handling via custom AppError class extending Error
- All database queries must use parameterized inputs (no string concatenation)
- Enforce parameterized queries at the TypeORM level; never construct SQL via template literals or concatenation

## File Conventions
- Controllers in src/controllers/, one file per resource
- Services in src/services/, business logic only, no HTTP concerns
- Routes in src/routes/, thin wiring between controllers and Express router
- Tests colocated in __tests__/ subdirectories mirroring src/ structure

## Dependencies Locked
- Do not add new dependencies without explicit approval
- Do not suggest Mongoose, Prisma, or Sequelize — project uses TypeORM only
- Rate limiting via express-rate-limit v7, keyed per user using JWT sub claim
- Auth tokens via jsonwebtoken; password hashing via bcrypt

Cursor rules files use plain text or MDC (Markdown with metadata) format, not YAML key-value pairs. See cursor.com/docs/context/rules for current syntax. Below is a correctly formatted example:

# .cursor/rules

This project uses TypeScript with Express 5 and TypeORM.
Node.js version: 22. Test runner: Vitest.

Prefer functional style where possible. Use classes only for TypeORM entities.

Never suggest: default exports, the `any` type, or console.log in production code.

Always include: explicit error handling and input validation with Zod.

Version control discipline with AI-generated code requires tighter branching strategies than traditional development. Each AI-generated feature should land on its own branch, with diffs reviewed before merge. AI-generated tests complement this workflow but require human-reviewed acceptance criteria to avoid the problem of tests that validate incorrect behavior.

The Vibe Coding Workflow, Step by Step

Step 1: Plan in Natural Language

The most consequential shift in vibe coding is front-loading effort into specification rather than implementation. "Prompt engineering" has evolved into a practice some developers call "specification engineering": writing structured, unambiguous project briefs that define scope, constraints, architecture preferences, and acceptance criteria before any AI tool is invoked.

A well-structured specification prompt eliminates entire categories of generated-code problems. Vague prompts produce vague code. Specific prompts produce specific, testable output.

# Project Specification: Task Management API

## Objective
Build a RESTful task management API supporting CRUD operations for tasks
and projects, with user authentication via JWT.

## Tech Stack (non-negotiable)
- Runtime: Node.js 22
- Framework: Express 5 (note: Express 5 introduces breaking changes from
  Express 4, including revised error handling for async routes — review the
  Express 5 migration guide before using Express 4 patterns)
- Language: TypeScript (strict mode)
- Database: PostgreSQL 16 via TypeORM
- Validation: Zod
- Testing: Vitest
- Auth: JWT (via jsonwebtoken) with bcrypt for password hashing
- Rate Limiting: express-rate-limit v7

## Core Resources
- Users: registration, login, profile retrieval
- Projects: CRUD, owned by a user
- Tasks: CRUD, belong to a project, assignable to a user

## Constraints
- All endpoints return JSON with consistent envelope: { data, error, meta }
- Pagination on all list endpoints (cursor-based, not offset — cursor-based
  pagination uses an opaque token, typically an encoded primary key or timestamp,
  to fetch the next page, avoiding the drift issues of numeric offsets)
- Rate limiting: 100 requests per minute per user, enforced via
  express-rate-limit keyed on JWT sub claim
- Input validation on every POST/PUT endpoint
- No ORM lazy loading — all relations explicitly loaded

## Acceptance Criteria
- All endpoints have corresponding Vitest integration tests
- Auth middleware rejects expired/malformed tokens with 401
- Creating a task without a valid project ID returns 422
- Pagination returns correct next_cursor values

Step 2: Scaffold with AI Agents

With the specification defined, the next step is using a coding agent (Cursor Agent mode, Claude Code) to generate the project structure, install dependencies, and create boilerplate. The critical discipline here is reviewing the scaffold before proceeding. AI agents sometimes make architectural decisions that diverge from the specification, such as choosing a different ORM, adding unrequested middleware, or structuring folders in unexpected ways.

Prerequisites before scaffolding:

  • Node.js 22.x installed (node --version to confirm)
  • npm ≥10.x (npm --version)
  • PostgreSQL 16 running locally or via Docker, with a connection string ready
  • A .env file at the project root containing DATABASE_URL, JWT_SECRET, and any other secrets (never commit this file; add it to .gitignore)

Ensure your package.json pins dependency versions exactly to ensure reproducible builds. A minimal starting point:

{
  "scripts": {
    "build": "tsc --project tsconfig.json",
    "start": "node dist/app.js",
    "dev": "tsx watch src/app.ts",
    "migrate": "typeorm migration:run -d dist/data-source.js",
    "test": "vitest run",
    "test:watch": "vitest"
  },
  "dependencies": {
    "express": "5.0.1",
    "typeorm": "0.3.20",
    "pg": "8.13.0",
    "zod": "3.23.8",
    "jsonwebtoken": "9.0.2",
    "bcrypt": "5.1.1",
    "express-rate-limit": "7.4.1",
    "reflect-metadata": "0.2.2"
  },
  "devDependencies": {
    "typescript": "5.5.4",
    "vitest": "2.1.8",
    "tsx": "4.19.0",
    "@types/express": "5.0.0",
    "@types/jsonwebtoken": "9.0.7",
    "@types/bcrypt": "5.0.2",
    "@types/node": "22.9.0"
  },
  "engines": {
    "node": ">=22.0.0"
  }
}

Note: Ensure pg driver version ≥8.11 for PostgreSQL 16 SCRAM-SHA-256 authentication compatibility. The reflect-metadata package is required at runtime by TypeORM for decorator reflection; it must be in dependencies, not devDependencies.

task-api/
├── src/
│   ├── controllers/        # HTTP request handlers, one file per resource
│   │   ├── auth.controller.ts
│   │   ├── project.controller.ts
│   │   └── task.controller.ts
│   ├── services/           # Business logic, no HTTP/Express imports
│   │   ├── auth.service.ts
│   │   ├── project.service.ts
│   │   └── task.service.ts
│   ├── routes/             # Express router wiring only
│   │   └── index.ts
│   ├── entities/           # TypeORM entity classes
│   │   ├── user.entity.ts
│   │   ├── project.entity.ts
│   │   └── task.entity.ts
│   ├── middleware/          # Auth, validation, rate limiting, error handler
│   ├── utils/              # Shared helpers: response envelope, pagination
│   ├── data-source.ts      # TypeORM DataSource configuration
│   └── app.ts              # Express app initialization
├── __tests__/              # Mirrors src/ structure
├── .env                    # DATABASE_URL, JWT_SECRET (do not commit)
├── CLAUDE.md               # Project context for AI agents
├── tsconfig.json
├── package.json
└── vitest.config.ts

Your data-source.ts should look like this:

import "reflect-metadata";
import { DataSource } from "typeorm";

if (!process.env.DATABASE_URL) {
  throw new Error(
    "DATABASE_URL environment variable is required but not set."
  );
}

export const AppDataSource = new DataSource({
  type: "postgres",
  url: process.env.DATABASE_URL,
  synchronize: false,
  logging: process.env.NODE_ENV !== "production",
  entities: [__dirname + "/entities/**/*.js"],
  migrations: [__dirname + "/migrations/**/*.js"],
});

Note: The entity and migration globs reference compiled .js files because TypeORM loads these paths at runtime from the dist/ output directory. If you are using ts-node or tsx for development without a compile step, use a conditional to swap the extension:

const isTsNode = process[Symbol.for("ts-node.register.instance")] != null;

export const AppDataSource = new DataSource({
  type: "postgres",
  url: process.env.DATABASE_URL,
  synchronize: false,
  logging: process.env.NODE_ENV !== "production",
  entities: [
    isTsNode
      ? __dirname + "/entities/**/*.ts"
      : __dirname + "/entities/**/*.js",
  ],
  migrations: [
    isTsNode
      ? __dirname + "/migrations/**/*.ts"
      : __dirname + "/migrations/**/*.js",
  ],
});

Your app.ts entry point must import reflect-metadata before anything else and explicitly initialize the DataSource:

// Must be the FIRST import in the application entry point.
import "reflect-metadata";

import express from "express";
import { AppDataSource } from "./data-source";
// ... other imports

const app = express();

async function bootstrap(): Promise<void> {
  try {
    await AppDataSource.initialize();
  } catch (err) {
    throw new Error(
      `Database initialization failed: ${
        err instanceof Error ? err.message : String(err)
      }`
    );
  }

  // Register middleware and routes after DB is ready.
  app.use(express.json());
  // app.use(routes)...

  app.listen(process.env.PORT ?? 3000, () => {
    console.info(`Server listening on port ${process.env.PORT ?? 3000}`);
  });
}

bootstrap().catch((err) => {
  console.error("Fatal startup error:", err);
  process.exit(1);
});

Your rate limiter middleware should implement the JWT sub keying described in the specification, not rely on the default IP-based keying:

import rateLimit from "express-rate-limit";
import type { Request } from "express";
import jwt from "jsonwebtoken";

/**
 * Extracts the JWT `sub` claim for rate-limit keying.
 * Falls back to IP if token is absent or malformed
 * (unauthenticated routes still get IP-based limiting).
 */
export function jwtSubKeyGenerator(req: Request): string {
  const authHeader = req.headers.authorization;
  if (authHeader?.startsWith("Bearer ")) {
    const token = authHeader.slice(7);
    try {
      // decode only — verification happens in auth middleware upstream.
      const decoded = jwt.decode(token);
      if (
        decoded &&
        typeof decoded === "object" &&
        typeof decoded.sub === "string"
      ) {
        return `user:${decoded.sub}`;
      }
    } catch {
      // Fall through to IP-based key.
    }
  }
  return `ip:${req.ip ?? "unknown"}`;
}

export const userRateLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 100,
  keyGenerator: jwtSubKeyGenerator,
  standardHeaders: "draft-7",
  legacyHeaders: false,
  handler: (_req, res) => {
    res.status(429).json({
      data: null,
      error: { message: "Too many requests. Please retry after 60 seconds." },
      meta: {},
    });
  },
});

Your vitest.config.ts should contain at minimum:

import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,
    environment: "node",
    testTimeout: 10_000,
    hookTimeout: 15_000,
    setupFiles: ["reflect-metadata"],
    coverage: {
      provider: "v8",
      reporter: ["text", "lcov"],
      exclude: ["src/migrations/**", "src/entities/**"],
    },
  },
});

Verify that every folder matches the specification. Confirm dependency versions in package.json. Check that TypeORM's DataSource is configured with type: "postgres" targeting PostgreSQL 16, not SQLite or MySQL. Run npm install and confirm no peer dependency conflicts via npm ls. This checkpoint takes minutes and prevents hours of downstream correction.

Step 3: Iterate with Conversational Refinement

Building features after the scaffold is where the conversational nature of vibe coding shines. Each feature is implemented through follow-up prompts that reference the existing codebase. The key principle: describe the bug or desired behavior, not the fix. Telling an AI agent "the pagination returns duplicates when items are inserted during traversal" produces better results than "add a WHERE clause to filter by created_at."

Context window management matters. When an agent starts contradicting earlier decisions or introducing inconsistent patterns, start a fresh session with your context files (CLAUDE.md, .cursor/rules) re-anchored. See the "Context Collapse and Prompt Drift" section below for symptoms and remedies. Modular prompting, where each prompt addresses a single concern, helps prevent drift.

Step 4: Review, Test, and Harden

You must still review every line of AI-generated code. Models hallucinate API methods that do not exist, introduce subtle logic errors in edge cases, and generate code with common security vulnerabilities such as SQL injection via string interpolation, missing authentication checks on endpoints, or overly permissive CORS configurations. Audit CORS configuration to allow only known origins; avoid origin: '*' in production and use a whitelist of trusted domains.

AI-assisted testing works well here: prompt the agent to generate tests specifically targeting edge cases, boundary conditions, and failure modes. Then review those tests for correctness. A test that validates wrong behavior is worse than no test at all.

Hardening checklist before shipping: Verify all dependencies against known vulnerability databases. Run static analysis. Confirm authentication and authorization on every endpoint. Validate that error responses do not leak internal state. Check that rate limiting actually functions under concurrent load.

Prompt Engineering for Vibe Coding

Anatomy of an Effective Coding Prompt

Effective coding prompts follow a consistent structure: role definition, context, task, constraints, and desired output format. Specificity drives quality, but over-specification can constrain the model from making reasonable implementation choices.

A weak prompt: "Write a login endpoint." A strong prompt: "In the auth.controller.ts file, implement a POST /auth/login endpoint that accepts email and password via JSON body, validates input with the existing Zod schema in validators/auth.ts, calls auth.service.login(), and returns a JWT in the response envelope format defined in utils/response.ts. Return 401 for invalid credentials with a generic error message that does not distinguish between wrong email and wrong password."

The difference is not verbosity for its own sake. Every additional constraint in the strong prompt eliminates a class of ambiguous output.

Every additional constraint in the strong prompt eliminates a class of ambiguous output.

Building Your Prompt Library

Mature vibe coding practitioners maintain version-controlled prompt libraries categorized by task type: scaffolding, CRUD generation, debugging, refactoring, test writing, code review, documentation, API design, migration, and performance optimization. Prompts are parameterized with placeholder variables (these are documentation conventions for human use; replace the {{placeholders}} with actual values before pasting into an AI tool) so they can be reused across projects.

Prompt: REST Endpoint Generation

Role: Senior backend developer
Context: Working in [framework] with [language]. Project uses [orm] for data access.
Task: Generate a [method] [route] endpoint in [controller_file] that [behavior_description].
Constraints: Use existing validation schemas. Follow response envelope in utils/response.ts. Return appropriate HTTP status codes. Include error handling for [expected_failure_modes].

The endpoint generation template above follows a fixed four-part structure because endpoint specs rarely need more flexibility. Debugging prompts, by contrast, benefit from a two-phase structure: diagnosis first, then fix. This prevents the model from jumping to a solution before identifying the actual cause.

Prompt: Debug from Stack Trace

Role: Debugging specialist
Context: [language] project using [framework], running on [runtime_version].
Task: Analyze this stack trace and identify the root cause:

[paste stack trace here]

Constraints: Do not suggest fixes yet. First explain what is happening and why. Identify the exact file and line. Note if this could be a dependency version issue.

Test generation prompts need the most specificity because models tend to produce shallow happy-path tests when given vague instructions. Enumerating the failure categories explicitly forces broader coverage.

Prompt: Unit Test Generation

Role: Test engineer
Context: Testing [file_under_test] using [test_runner].
Task: Write unit tests covering: happy path, invalid input, missing required fields, unauthorized access, and database connection failure.
Constraints: Mock external dependencies. Each test must have a descriptive name following the pattern "should [expected_behavior] when [condition]". Use arrange-act-assert structure.

Common Pitfalls and How to Avoid Them

The "It Works, Ship It" Trap

The most dangerous pattern in vibe coding is accepting generated code because it passes a superficial test. Code that runs is not necessarily code that is correct, secure, or maintainable. Technical debt accumulates faster with AI-generated code because the developer did not write it and likely does not know its internal logic. Regardless of what proportion of code is AI-generated, the developer must critically evaluate every line before shipping. There is no safe threshold below which review can be skipped.

Context Collapse and Prompt Drift

Picture this: your agent just renamed a service method to getTasksByProject, and two prompts later it calls fetchProjectTasks as if the rename never happened. When an AI agent begins contradicting its earlier architectural decisions or introducing inconsistent naming conventions, either the context window limit has been reached or attention has degraded over a long session. Symptoms include switching between camelCase and snake_case mid-session, suggesting libraries that conflict with the defined tech stack, or reimplementing utility functions that already exist in the project. The fix is modular prompting (one concern per prompt), explicit context files re-anchored at session start, and willingness to start fresh sessions rather than pushing through degraded output.

Security and Dependency Risks

AI models train on vast codebases that include outdated and vulnerable patterns. Generated code frequently suggests dependencies with known CVEs, constructs SQL queries via string concatenation, omits input sanitization, or implements authentication flows with subtle flaws. Mitigate this with automated dependency scanning (npm audit for known CVEs in the npm advisory database, Snyk for broader coverage including license compliance and supply chain risk; these are complementary, not equivalent), explicit security constraint prompts in every specification, and manual review of all authentication, authorization, and data access code. Neither automated tool replaces manual review of authentication and authorization logic.

Vibe Coding in Production: Real-World Patterns

Solo Developer and Small Team Workflows

Practitioners report finishing CRUD-heavy MVPs in hours rather than days using structured vibe coding workflows, though no published benchmarks confirm a specific ratio. The practical boundary is clear: vibe code the repetitive, well-understood parts (CRUD endpoints, form validation, data transformation layers) and hand-code the novel, complex, or security-sensitive parts (authentication logic, payment processing, custom algorithms). This hybrid approach captures the speed benefit without accepting unacceptable risk.

Enterprise Adoption Signals

Organizations are beginning to create internal vibe coding guidelines that specify which project types qualify for AI-first development, which models are sanctioned for use with proprietary code, and what review processes apply to AI-generated output. Before using any cloud-hosted model with proprietary code, review the provider's data processing terms. Many enterprises require opt-out of training data use, private deployments, or on-premise models for proprietary codebases. The role of "AI-first developer," a practitioner whose primary skill is directing AI agents to produce production-quality software, is emerging as a distinct position on engineering teams. This is not replacing traditional software engineering. It is augmenting it with a new mode of working that requires its own expertise.

Where Vibe Coding Is Heading

Tools like Cursor's background agents (announced in early 2025) and Claude Code's ability to chain shell commands point toward agent-based pipelines that handle planning, implementation, testing, and deployment as a continuous, semi-autonomous workflow. Natural language is replacing code as the primary interface for specifying software, not just a convenience layer over it. The developer's role is shifting from writer of code to director of coding agents, a role that demands deeper architectural understanding, not less. Knowing what correct software looks like matters more when the developer's job is to evaluate output rather than produce it. The fundamentals of computer science, system design, and security reasoning are not becoming obsolete. They are becoming the primary lens through which AI-generated code is judged fit for production.

The developer's role is shifting from writer of code to director of coding agents, a role that demands deeper architectural understanding, not less.

SitePoint TeamSitePoint Team

Sharing our passion for building incredible internet things.

© 2000 – 2026 SitePoint Pty. Ltd.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.