# You Vibe-Coded Something That Works. Here's How to Make It Real Without Touching the Code Yourself.
*Published: 2026-05-02*
*Tags: ai, product-work*
*Source: https://chrislema.com/vibe-coded-something-that-works-now-what*
---You opened Replit, or Lovable, or Bolt. You described what you wanted. You watched it build.

A few hours later, something was running. Maybe it had a login page. Maybe it had a database. Maybe friends could click a link and use the thing that, last week, only existed in your head.

That's the hard part. Most people never get past the idea. You did.

And here's something you might not realize until later: what you have is a working draft. Like any working draft, there's a next pass. The next pass has a name and a shape, and you can hand it to the same AI that built the first version.

You don't need to learn to code. You need to learn what to ask for next.

## The False Choice You're Feeling

If you're like most people who just shipped their first vibe-coded thing, you're stuck in a binary that isn't real.

**Option A:** Stay non-technical. Keep building demos. Show them to friends. Don't try to put the thing in front of paying users or stakeholders, because you know it won't survive contact with reality.

**Option B:** Learn to code. Take a course. Pick up TypeScript. Bookmark the Cloudflare docs. Spend the next year becoming the technical person you weren't six months ago.

Both options sacrifice something you don't have to sacrifice.

Option A gives up the dream that got you excited in the first place. You wanted to ship a real product, not a demo for friends. Option B gives up the speed and joy that pulled you into vibe coding. The whole point was that you didn't have to become a developer.

Here's what I want to tell you. There's a third option, and it's the one nobody is selling.

You stay non-technical. You ship a real product. The thing that bridges the gap isn't your skill. It's the instructions you give your AI.

## Two Tracks, Same Starting Line

Let me show you what I mean by walking you through two paths.

**Track A is where you are right now.** You have a working app. It logs people in. It shows them a screen. It saves something to a database. It does the thing you described.

**Track B is where you could be.** I'm working on a multi-agent collaborative app for expertise extraction, using Jido on Elixir. I don't know Elixir. Jido is new. I'm not an expert in either, but that hasn't stopped me.

The app works. It's not launched yet, but it's usable, and it's serving real traffic. It integrates the core Jido framework along with AI libraries. And there's a separate SaaS I've been working on, our MCode Platform (v3), that has more than three hundred tests verifying it does what it should.

Same starting point as you. Same posture. I describe what I want, the AI builds it.

The difference between Track A and Track B isn't talent. It isn't time. It isn't a hidden coding course I took on weekends.

The difference is five layers. Each one is a set of instructions I figured out to give my AI. And once I figured them out, the AI got dramatically better at producing things that survive contact with reality.

I'm going to walk you through all five.

## Layer 1: Auth Boundaries

Here's the layer most vibe-coded apps skip first.

Your app probably has a login page. Maybe it even has user accounts. So you think you have authentication. You don't, not really. You have a login screen.

Here's the difference. Authentication is who someone is. Authorization is what they're allowed to do. Most vibe-coded apps verify identity once, sort of, and then trust everything that comes after. Which means anyone who figures out a session ID, or finds an unprotected endpoint, or sends a request from outside the app, can do whatever they want.

Here's the instruction I'd recommend you give your AI before it writes any login code:

*Verify identity once at the entry, trust verified context inside, and check permissions at the boundaries where capabilities change. Use PBKDF2 with a hundred thousand iterations for password hashing, not bcrypt. Use constant-time comparison for any token check, never the equals sign. Sessions expire, and logout actually invalidates the session on the server, not just in the browser.*

You don't have to know what each thing means to get value from it. If you do know, and you'd argue for different choices, that's fair. The point isn't that this is the only set of rules. It's that having a set of rules is the move.

Paste it into your next conversation. Ask your AI to audit the existing login against those rules, then show you where each one is enforced. Your AI will do the work. And it will explain what it did. And the next time you describe a feature that needs login, the AI already knows the rules.

## Layer 2: Error Handling

Here's the next one. Right now, your app probably handles errors the way most vibe-coded apps do: it doesn't.

When something goes wrong, the user sees a generic message. Or a blank screen. Or worse, the broken thing keeps running and quietly logs to a console nobody is reading. Your AI didn't do this on purpose. It just wasn't told to do otherwise.

Here's the instruction I'd recommend you give your AI:

*Handle errors at the boundaries of the system, not scattered through the business logic. When something fails, give the right information to the right person.*

*To the end user: a clear, plain-language message that tells them what happened and what they can do next. They can't fix the database connection or the API timeout. They can retry, contact support, or walk away. Tell them which.*

*To the developer (in the logs): enough context to actually fix it. What failed, what state it was in, what inputs caused it, what the system tried to do about it. Stack trace, request ID, timestamp. The forensic detail.*

*No silent fallbacks. No "log and continue" on critical paths. If the payment didn't go through, the user knows and the developer can trace exactly why. Stable failure beats clever recovery.*

The point of error handling isn't to look clean. It's to make sure the right person has the right information when something breaks. Your end user shouldn't see a stack trace. Your developer shouldn't have to guess at what happened from a generic "something went wrong."

Paste it in. Then ask your AI to walk you through every place an error could happen, what the user would see, and what the logs would capture.

## Layer 3: State Management

Most vibe-coded apps have a state problem they don't know about yet.

State is just a fancy word for *what's happening right now and how the system tracks it.* Is this order pending or complete? Did this background job finish? Did the email go out? Is this user mid-checkout, or did they leave?

Vibe-coded apps usually answer these questions implicitly. The order is complete because we set a flag at the end of the function. The job finished because the function returned. The email went out because we called the send function. Sounds reasonable. Falls apart the moment something doesn't go as planned, which on the internet is roughly always.

Here's the instruction I'd recommend you give your AI:

*Make state explicit and inspectable. Every important thing in the system, every order, every job, every multi-step flow, should have a status I can query at any moment: pending, running, complete, failed, stuck. Record every state transition. Never assume something worked just because the function returned. Never start a background process without tracking whether it finished. No fire-and-forget on anything that matters.*

If "state machine" sounds like a developer term, ignore the term. The idea is plain: anything important should always be able to tell you where it is. That's it.

The benefit isn't theoretical. When something goes wrong with your real users, you can find it. You can ask the system *what is happening right now?* and get an answer. That alone is the difference between an app that scales to ten users and one that scales to ten thousand.

## Layer 4: Deployment Verification

This is the layer that surprised me most.

When I started, I assumed deployment meant pushing to production and seeing the new version live. The vibe coding tools mostly behave this way. You hit a button. The site updates. You assume it worked.

It often didn't.

The rule I now follow, and the rule I encode for my AI, is that deployment isn't complete until verification is complete. Pushing the code is the easy part. Verifying that the right artifact is running, that the routes resolve, that the database migrations applied, that the environment variables loaded, that the rollback path works if something breaks. That's deployment.

For my own work, that means different tools depending on the stack. I use wrangler to deploy to Cloudflare. I use the fly.io CLI to deploy Jido to fly.io. Different commands, same rule: did the right artifact land in the right place, are the routes resolving, are the bindings or environment variables connected, is the new version actually serving traffic, or is the old one still cached somewhere? Each of those is a question with a verifiable answer.

You don't have to know wrangler or the fly.io CLI. You have to know that *deployment* and *successful deployment* are different events, and your AI should treat them that way.

Here's the instruction I'd recommend you give your AI:

*After every deployment, verify the result. Don't tell me it worked because the command didn't error. Tell me it worked because you checked the live system and saw the new version responding. Confirm the right artifact landed in the right environment, the routes resolve, the database migrations applied, the environment variables loaded, and the rollback path works if something breaks.*

The mechanics underneath this rule will look different on Cloudflare than on fly.io than on Vercel. That's fine. The rule doesn't care about the platform. It cares about the discipline.

Have your AI write the verification step. Once. After that, every deploy includes the check.

## Layer 5: Tests

I saved this for last because it's the one most people resist hardest.

Testing sounds like a developer thing. It sounds like extra work. It sounds like the boring part you'd skip if you were a developer, never mind a non-developer.

Here is what I know. Tests are not a developer ritual. They are a way of writing down what *good* looks like, so that next month, when you describe a new feature to your AI, the AI can check itself against what *good* looked like before. Tests are how the things you've already proven keep being true.

On the MCode Platform, the SaaS I've been building, there are now more than three hundred tests. I did not write any of them by hand. I described what the system should do. I described the cases that should pass and the cases that should fail. The AI wrote the tests. The AI runs the tests. When a test fails, the AI tells me which one and why.

The structure I use is a ladder. Smoke tests run first. They're fast and check that the critical paths still work. If smoke tests pass, API tests run. They check that the contracts between parts of the system still hold. If those pass, end-to-end tests run, which click through real flows like a user would. Each level gates the next.

There's also a final check before anything ships. Some things matter more than others. Login. Payments. Data not getting lost. If the tests for any of those don't pass, nothing ships. Not "we'll fix it later." Not "it's probably fine." Nothing ships.

Here's the instruction I'd recommend you give your AI:

*Build a smoke test layer for critical paths, an API test layer for contracts, and an end-to-end test layer for user flows. Run them in order, each layer gating the next. Fail closed on auth, payments, and data integrity, missing evidence on any of those is a blocker. Before I deploy, tell me which tests passed and which didn't.*

If three test layers sounds like overkill, this is the layer where I'd push back hardest. The whole point is that each layer catches different failures, fast ones at the bottom, slow ones at the top. Skipping the structure means you'll skip the catching.

Your AI will build it. You will not write a single line of test code. And the next time you describe a feature, the AI will write tests for it the same way.

## Where the Tracks Meet

Here's what I want you to see.

Track A is where you are. Track B is where I live. The path between them isn't a coding bootcamp. It's five layers of instructions, each one teaching your AI to build more carefully than the default.

Once those instructions exist, every future feature you describe gets the benefit. Your AI already knows the auth rules. It already knows how to handle errors. It already knows that state is explicit, that deployments verify, that critical paths have tests.

You stay non-technical. The instructions get more sophisticated. The thing your AI builds gets more real.

The convergence isn't dramatic. There's no moment where you suddenly become a developer. There's no graduation. There's just a Tuesday where you realize the app you built six months ago has been running fine the whole time, and the new feature you're adding right now is going to inherit everything you taught your AI along the way.

That's what shipping a real product looks like, from the inside, when you didn't start as a developer.

## Tomorrow Morning

Don't try to do all five layers at once. That's the developer instinct, and it's not yours.

Pick one. The one that feels most relevant to what your app actually does. If users log in, start with auth. If anything important happens that could fail, start with errors. If you have background jobs or multi-step flows, start with state.

Open your AI. Paste the rule for that layer. Ask it to audit your existing app against the rule. Have it tell you what's missing. Ask it to add what's missing.

That's the whole move.

Then next week, do the next layer. The week after that, the next one. By the end of the month, you'll have a thing that survives contact with reality. The same thing you built. With five more layers.

Same draft. Next pass.
