Back to Blog
ArticleJuly 23, 202632 min read

Claude Code for SaaS: A Production-Ready Workflow for Next.js, Supabase, and Billing

Claude Code can accelerate SaaS development, but speed alone does not make an application ready for customers. This guide explains how to use Claude Code with Next.js, Supabase, billing systems, security boundaries, automated testing, and production review gates.

Ryan Almasu

Written by

Ryan Almasu

Claude Code for SaaS

Claude Code can build a feature surprisingly quickly.

Give it access to a Next.js repository, describe a dashboard, and it may create the route, components, database query, form validation, and tests in a single session. For a prototype, that level of speed can feel transformative.

A production SaaS application is different.

The feature must respect authentication boundaries. Database access must remain protected even when a browser request is manipulated. Billing events must survive retries and arrive safely in the wrong order. Server Actions must authorize every mutation. Tests must prove the intended behavior, and future developers—or future AI sessions—must be able to understand what was built.

That is where the real Claude Code workflow begins.

Claude Code is most effective for SaaS development when it operates inside a prepared engineering system: concise repository context, explicit security rules, small implementation slices, deterministic tests, restricted permissions, and mandatory verification before merging.

Claude Code should not replace your application architecture. It should work within it.

This guide shows how to set up that relationship for a SaaS application built with Next.js, Supabase, TypeScript, and a billing provider such as Stripe, Lemon Squeezy, Paddle, Polar, or Dodo Payments.

What Does Claude Code Change About SaaS Development?

What Does Claude Code Change About SaaS Development?

Claude Code is an agentic coding tool that works directly inside a software project. It can inspect files, search the repository, edit code, execute commands, run tests, interact with Git, and use additional tools exposed through integrations.

That makes it fundamentally different from copying a code snippet out of a browser-based chatbot.

Instead of receiving an isolated answer, Claude can trace how authentication works, locate existing domain services, inspect database migrations, follow established component conventions, run the relevant tests, and revise its work based on the results.

Anthropic’s recommended workflows reflect this broader role. Its guidance emphasizes exploring the repository before editing, creating a plan, implementing against clear targets, running tests, and reviewing the result rather than treating the first generated solution as final.

For SaaS development, this creates three important advantages.

First, Claude Code can work with relationships across the repository. A billing change may require updates to a checkout action, webhook handler, entitlement service, database schema, customer portal, admin interface, and test fixtures. An agent that can inspect all of those areas has more context than a generator working from a single prompt.

Second, it can validate its own implementation. It can run TypeScript, linting, unit tests, integration tests, builds, and selected browser tests. These tools provide objective feedback that a conversational answer cannot.

Third, it can preserve existing conventions. When instructed properly, Claude can follow the repository’s current architecture rather than creating a parallel design every time a feature is added.

However, each advantage depends on the quality of the environment around the agent.

A poorly documented repository with weak tests and inconsistent abstractions does not become reliable simply because Claude can read more files. In that environment, the agent may reproduce existing inconsistencies faster.

Why Does Building a SaaS Require a Stricter Claude Code Workflow?

A marketing page has visible success criteria. It either resembles the intended design or it does not.

A SaaS backend often fails invisibly.

A subscription page may render correctly while trusting an unverified workspace ID. A Supabase query may work for the developer account while exposing another tenant’s records. A webhook may update a subscription twice when the payment provider retries an event. A Server Action may be hidden from the interface but still callable through a direct request.

These problems are difficult because a SaaS product contains several overlapping systems.

SystemWhat appears simpleWhat production requires
AuthenticationLog in and retrieve a userSession validation, account state, redirect safety and recovery flows
AuthorizationHide restricted buttonsServer-side permission checks, tenant boundaries and database policies
BillingRedirect to checkoutVerified webhooks, idempotency, reconciliation and entitlement state
DatabaseInsert and select recordsConstraints, migrations, RLS, grants and safe rollback behavior
Next.jsCreate a Server ActionInput validation, authorization, secret isolation and safe caching
DeploymentBuild successfullyEnvironment validation, health checks, observability and release gates
AI assistanceGenerate working codeRepository alignment, controlled scope, verification and human review

This is why “build the whole feature” is often the wrong first prompt.

The agent needs to understand which invariants cannot change, which existing modules own the behavior, what evidence is required, and what actions it is not permitted to take.

The purpose of a production-ready Claude Code workflow is therefore not to reduce the agent’s capability. It is to direct that capability toward a verifiable outcome.

The Production-Ready Claude Code Workflow

The Production-Ready Claude Code Workflow

A reliable workflow separates research, planning, implementation, verification, and release.

StageClaude Code’s responsibilityHuman or system gate
ContextRead architecture, conventions and relevant filesConfirm the correct scope
PlanExplain the current flow and propose changesReview assumptions and risks
TestsDefine or update expected behaviorConfirm the tests represent the requirement
ImplementationMake the smallest complete changeKeep the diff within scope
VerificationRun focused checks, then broader checksInspect failures rather than bypassing them
ReviewSummarize behavior, tradeoffs and remaining risksReview sensitive logic manually
ReleasePrepare a commit or pull requestCI and branch protection decide mergeability

The important distinction is that Claude does not move from an idea directly to a production deployment.

It moves from an idea to evidence.

The evidence may include a passing authorization test, an RLS policy test, a webhook replay test, a successful production build, a browser screenshot, or a pull request diff that another reviewer can understand.

How to Install and Start Claude Code

Claude Code currently supports a native installation, Homebrew, WinGet, Linux package managers, and npm. Anthropic recommends its native installation for most users. After installation, you start Claude Code from the directory containing the project you want it to inspect.

For macOS, Linux, or WSL:

curl -fsSL https://claude.ai/install.sh | bash

cd path/to/your-saas
claude

On Windows, Claude Code can run natively or inside WSL. WSL 2 is particularly useful when you need Linux tooling and Claude Code’s sandboxed command execution. Native Windows remains appropriate when the project depends on Windows-specific tools.

After installation, verify the environment:

claude --version
claude doctor

Installing the tool is the easy part. Before assigning a real feature, prepare the repository so Claude understands how the application is intended to work.

How Should You Configure CLAUDE.md for a SaaS Project?

CLAUDE.md is a repository instruction file that Claude Code loads as project context.

It is the appropriate place for information that should apply across many sessions: package commands, architecture boundaries, coding conventions, testing expectations, security invariants, and the repository’s definition of done.

Claude Code can generate an initial version with /init, but an automatically discovered file should be treated as a starting point. The most valuable instructions are often the decisions that cannot be inferred by reading code alone.

Anthropic recommends keeping these instructions concise and specific. Its current documentation suggests targeting fewer than roughly 200 lines for each CLAUDE.md file and moving specialized instructions into path-scoped rules when the root file becomes too large.

Here is a practical starting point for a Next.js and Supabase SaaS:

# Project

This is a multi-tenant SaaS application built with:

- Next.js App Router
- TypeScript
- Supabase Auth and Postgres
- Row Level Security
- A provider-neutral billing domain
- Vitest and Playwright
- pnpm

# Required Commands

Before completing a code change, run the relevant focused tests.

Before declaring the task complete, run:

- pnpm lint
- pnpm typecheck
- pnpm test
- pnpm build

Run Playwright only for affected critical user flows.

# Architecture

- Keep domain logic outside React components.
- Server-only modules must import `server-only`.
- Browser components may never import admin database clients.
- Route Handlers and Server Actions are public server entry points.
- Validate external input at every server boundary.
- Reuse existing repositories and domain services before creating new ones.
- Do not call payment providers directly from UI components.

# Authentication and Authorization

- Authentication does not imply authorization.
- Verify the current user inside every sensitive Server Action and Route Handler.
- Verify workspace membership using server-derived identity.
- Never trust a user ID, role, price, plan or workspace ID supplied by the browser.

# Supabase

- Every table in an exposed schema requires an explicit RLS decision.
- New migrations must include required grants, policies and constraints.
- Never expose the service-role key to browser code.
- Do not solve an RLS failure by moving ordinary user queries to service-role access.
- Add tests for owner, member, unrelated user and anonymous access where relevant.

# Billing

- Verify webhook signatures using the raw request body.
- Treat webhook delivery as duplicated and unordered.
- Persist provider event IDs before applying side effects.
- Update internal entitlements through the billing domain service.
- Do not treat one provider status field as the complete product entitlement.
- Webhook handlers must be safe to replay.

# Git

- Do not force-push.
- Do not amend existing commits unless explicitly requested.
- Do not commit environment files or secrets.
- Summarize changed files and verification results before committing.

# Definition of Done

A task is complete only when:

1. The requested behavior works.
2. Authorization is enforced on the server.
3. Database policies match the intended access model.
4. Relevant automated tests pass.
5. Type checking, linting and production build pass.
6. No secret or unrelated file is included in the diff.
7. Documentation is updated when behavior or configuration changes.

This file does not need to describe every folder or dependency.

Its purpose is to preserve decisions.

A line such as “never trust a workspace ID from the browser” is valuable because it prevents an entire class of authorization mistakes. A long description of every UI component is less useful because Claude can inspect those components when they become relevant.

It is also important to understand that CLAUDE.md is context, not a hard security control. Claude attempts to follow it, but technical enforcement should live in permissions, sandbox restrictions, hooks, database policies, automated tests, and CI.

How Should Claude Explore a Repository Before Editing It?

A good implementation prompt begins with the current system, not the desired code.

Instead of saying:

Add team invitations.

Start with:

Investigate the existing workspace membership, email, authorization and audit-log flows. Do not edit code. Explain which modules currently own each responsibility, where invitations should fit, and which security boundaries the implementation must preserve.

This changes the task from generation to repository comprehension.

During this phase, Claude should identify:

  • The server entry point that will receive the request
  • The domain service that should own the operation
  • The database tables and policies involved
  • Existing validation conventions
  • Existing notification or email infrastructure
  • Tests that describe related behavior
  • Configuration or environment variables that may be required

For a large repository, exploration can be delegated to a read-only subagent. Claude Code’s subagents use separate context windows and return summarized findings, which prevents large searches and file contents from consuming the main implementation context.

The result should be a short architectural map, not merely a list of files.

For example:

Request:
  app/(dashboard)/settings/billing/actions.ts

Authorization:
  features/workspaces/server/require-workspace-role.ts

Domain operation:
  features/billing/server/create-checkout.ts

Provider adapter:
  features/billing/server/providers/stripe.ts

Persistence:
  features/billing/server/billing-repository.ts

Tests:
  features/billing/server/create-checkout.test.ts
  e2e/billing-checkout.spec.ts

This map gives you something concrete to review before any code changes.

Why Should Claude Plan Before It Writes Code?

Planning exposes incorrect assumptions while they are still inexpensive.

A useful plan does more than repeat the feature request. It explains the current behavior, proposes the smallest change, identifies affected interfaces, describes security implications, and defines verification.

A strong planning prompt might be:

Create an implementation plan for adding annual billing.

Do not edit code yet.

The plan must include:

1. The current monthly checkout and entitlement flow.
2. The minimum schema or catalog changes required.
3. How the selected price will be validated server-side.
4. How webhook processing will remain idempotent.
5. Unit, integration and browser tests to add or update.
6. Possible migration and rollback risks.
7. The exact files expected to change.

Prefer extending existing billing abstractions over creating a second flow.

The final sentence matters.

Without it, an AI agent may solve the visible requirement by creating a separate annual checkout path, separate price mapping, and separate entitlement logic. The feature may work while leaving the repository harder to maintain.

Planning gives you the opportunity to reject that design before implementation.

Claude Code includes a plan permission mode that allows repository exploration while preventing edits until the plan has been approved.

claude --permission-mode plan

For work involving billing, authentication, database migrations, permissions, or shared infrastructure, planning should be the default rather than an optional ceremony.

How Should Claude Implement a SaaS Feature?

How Should Claude Implement a SaaS Feature?

The safest implementation unit is a vertical slice.

A vertical slice completes one meaningful behavior across the required layers without attempting to redesign the entire system.

For example, “add billing” is too broad. It contains catalog design, checkout creation, webhook verification, customer mapping, subscription state, entitlement calculation, portal access, retries, refunds, cancellations, administration, and observability.

A better first slice is:

Allow an authenticated workspace owner to create a checkout session for one existing recurring plan. Validate the plan server-side and return the provider checkout URL. Do not implement webhook entitlement updates in this task.

The next slice can handle one verified checkout event. Another can handle cancellation. Another can expose the current entitlement.

This approach creates smaller diffs, clearer tests, and easier rollback.

It also helps Claude maintain context. A session focused on one behavior is less likely to confuse temporary provider state, internal entitlement state, and user interface state.

A practical implementation prompt should define four things:

Implement the approved checkout-session plan.

Scope:
- One recurring plan only
- Existing Stripe adapter
- Workspace owners only
- No database migration
- No UI redesign

Requirements:
- Derive the current user from the server session
- Verify workspace ownership on the server
- Resolve the provider price from the trusted server catalog
- Do not accept provider price IDs from FormData
- Reuse the existing billing service
- Return a typed success or error result

Verification:
- Add tests for unauthenticated users
- Add tests for a non-owner workspace member
- Add tests for an invalid catalog key
- Add a success test that asserts the provider adapter arguments
- Run focused tests, type checking and linting

Stop and report before making unrelated refactors.

The scope prevents accidental expansion. The requirements preserve the security model. The verification section defines objective completion.

How Do You Protect Next.js Server Boundaries?

Next.js Server Components can access secrets, databases, and internal APIs, while Client Components must be treated according to browser security assumptions. Server-only modules can be marked with the server-only package so an accidental client import produces a build error.

More importantly, Server Actions and Server Functions must be treated as public HTTP entry points.

A function is not protected merely because the button that calls it is hidden. Next.js documentation explicitly recommends authenticating and authorizing every sensitive server mutation because Server Functions can be reached through direct POST requests.

A safe action separates transport data from trusted server state:

'use server'

import 'server-only'

import { z } from 'zod'

import { requireUser } from '@/features/auth/server/require-user'
import { requireWorkspaceRole } from '@/features/workspaces/server/require-workspace-role'
import { createCheckout } from '@/features/billing/server/create-checkout'

const checkoutInputSchema = z.object({
  workspaceId: z.string().uuid(),
  planKey: z.enum(['pro-monthly', 'pro-yearly']),
})

export async function createCheckoutAction(input: unknown) {
  const parsedInput = checkoutInputSchema.parse(input)
  const user = await requireUser()

  await requireWorkspaceRole({
    userId: user.id,
    workspaceId: parsedInput.workspaceId,
    allowedRoles: ['owner'],
  })

  return createCheckout({
    workspaceId: parsedInput.workspaceId,
    planKey: parsedInput.planKey,
    requestedByUserId: user.id,
  })
}

This function still accepts a workspace ID from the client, but it does not trust that ID. It checks whether the authenticated user is allowed to act on the selected workspace.

The planKey is also validated against a server-controlled catalog. The browser never decides that a product costs $10, that a user has an administrative role, or that a provider price ID belongs to a particular plan.

Ask Claude to review server boundaries explicitly:

Review every changed Server Action and Route Handler as though it were
a public API endpoint.

For each entry point, report:

- Which inputs are controlled by the requester
- Where authentication occurs
- Where authorization occurs
- Which values are derived from trusted server state
- Whether secrets can reach a Client Component
- Whether the operation can be replayed safely

This review often finds problems that a general “check the code” prompt misses.

How Should Claude Handle Supabase Row Level Security?

Supabase allows browser-accessible database APIs, but that model depends on Row Level Security.

Supabase recommends enabling RLS on tables in exposed schemas and creating policies that express which rows each role can access. Tables created through raw SQL require particular attention because RLS may need to be enabled explicitly.

Claude can write migrations, but the prompt must define the intended access matrix.

“Add RLS” is not a sufficient instruction.

A better requirement is:

ActorReadInsertUpdateDelete
Anonymous userNoNoNoNo
Authenticated workspace memberOwn workspaceNoLimited fieldsNo
Workspace ownerOwn workspaceOwn workspaceOwn workspaceOwn workspace
Service roleOperational accessOperational accessOperational accessOperational access

Then ask Claude to implement one policy at a time.

An illustrative membership-based read policy might look like this:

alter table public.projects enable row level security;

revoke all on table public.projects from anon;
grant select on table public.projects to authenticated;

create policy "workspace members can read projects"
on public.projects
for select
to authenticated
using (
  exists (
    select 1
    from public.workspace_members
    where workspace_members.workspace_id = projects.workspace_id
      and workspace_members.user_id = (select auth.uid())
  )
);

This example only covers SELECT. Inserts and updates should have separate policies, including appropriate WITH CHECK expressions, because permission to see a row does not automatically mean permission to create or change it.

Every RLS change should be tested from multiple identities.

A useful test matrix includes:

Owner:
- Can read a project in the owned workspace
- Can perform only the mutations allowed to owners

Member:
- Can read a project in the joined workspace
- Cannot perform owner-only operations

Unrelated authenticated user:
- Cannot read or mutate the project

Anonymous user:
- Cannot read or mutate the project

Service operation:
- Works only through the intended trusted server path

One of the most dangerous AI-generated fixes is replacing an ordinary authenticated query with the Supabase service-role client because an RLS policy is failing.

That removes the policy from the request rather than correcting it.

Your repository instructions should explicitly tell Claude that service-role access is for narrow trusted operations, not a universal solution to authorization failures.

How Should Claude Build Billing and Webhook Systems?

Billing is where a fast implementation most often becomes a fragile implementation.

A payment provider exposes customers, checkout sessions, transactions, invoices, subscriptions, refunds, disputes, and events. Your SaaS still needs its own model of what the customer may use.

These concepts should remain separate:

ConceptPurpose
Product catalogDefines plans, intervals, one-time offers, meters and credit packages
Provider stateStores customer, subscription, invoice and transaction references
Internal entitlementDetermines what the customer may access now
Event historyRecords webhook receipt, processing, rejection, retries and errors
Reconciliation stateTracks whether internal records match the provider

The provider is the authority for payment facts. Your application remains responsible for translating those facts into product access.

That translation cannot assume perfect webhook delivery.

Stripe, for example, documents that webhook events can be retried, manually resent, and delivered in a different order from the order in which they were created. Stripe recommends designing handlers that do not depend on event order and preventing duplicate processing.

A reliable handler therefore follows a pattern such as:

  1. Read the raw request body.
  2. Verify the provider signature.
  3. Parse the event.
  4. Insert the provider event ID into an event table.
  5. Stop safely when the event already exists.
  6. Apply the domain transition inside a transaction where possible.
  7. Record success or failure.
  8. Return a deliberate HTTP response.

The list is short, but each step protects against a real production failure.

A simplified provider-neutral structure could look like this:

import 'server-only'

type VerifiedBillingEvent = {
  id: string
  type: string
  occurredAt: Date
  payload: unknown
}

export async function processBillingEvent(
  event: VerifiedBillingEvent,
): Promise<'processed' | 'duplicate'> {
  return database.transaction(async (transaction) => {
    const inserted = await transaction.billingEvents.insertIfAbsent({
      provider: 'stripe',
      providerEventId: event.id,
      eventType: event.type,
      occurredAt: event.occurredAt,
      payload: event.payload,
      status: 'processing',
    })

    if (!inserted) {
      return 'duplicate'
    }

    try {
      await billingEventRouter.handle({
        event,
        transaction,
      })

      await transaction.billingEvents.markProcessed(event.id)
      return 'processed'
    } catch (error) {
      await transaction.billingEvents.markFailed({
        providerEventId: event.id,
        error,
      })

      throw error
    }
  })
}

The actual implementation will depend on the database and provider SDK. The architectural point is that duplicate detection is part of the persistence model rather than an in-memory check.

Claude should also avoid applying every incoming payload directly to your entitlement table.

For important events, it may be safer to use the event as a trigger, retrieve the current provider object, and then calculate the intended internal state. This reduces the damage caused by event reordering.

A good webhook prompt is highly specific:

Implement processing for `subscription.updated`.

Preserve these invariants:

- The signature is already verified before this function is called.
- The provider event ID is unique in billing_events.
- Reprocessing the same event must not apply the transition twice.
- Do not assume events arrive in chronological order.
- Do not grant access from a client-supplied plan name.
- Resolve the internal product from the trusted catalog mapping.
- Preserve cancellation access until the paid period ends.
- Update provider state and internal entitlement separately.
- Record an actionable failure code for retry and administration.

Add tests for duplicate delivery, stale delivery, unknown catalog mapping,
missing customer mapping and successful entitlement change.

That prompt is longer than “handle subscription updates,” but it encodes the behavior that makes the handler dependable.

How Can Tests Improve Claude Code Results?

Tests are not merely a final quality check. They are executable instructions.

Natural-language prompts can be interpreted in several ways. A test specifies an input, environment, and expected output.

Anthropic’s own Claude Code guidance recommends test-driven workflows for behavior that can be verified automatically: write the tests, confirm they fail, implement the code without weakening the tests, and iterate until they pass.

For a SaaS application, different test layers answer different questions.

Test layerPrimary questionExample
Unit testDoes one rule behave correctly?A cancelled subscription remains active until ends_at
Contract testDoes an adapter preserve the domain contract?Stripe and another provider return the same internal checkout shape
Integration testDo application and database rules work together?An unrelated user cannot read another workspace
Route testDoes the server boundary reject invalid requests?A non-owner receives 403 from an admin route
End-to-end testCan a user complete the critical flow?Sign in, open billing and start checkout
Build checkIs the repository valid as a deployable application?Next.js production build succeeds

Not every feature requires every layer.

A formatting utility may need only a unit test. An authentication callback or billing webhook deserves deeper coverage because a mistake can affect security or revenue.

The strongest prompt tells Claude which evidence is necessary:

Before implementing the change:

1. Add a failing unit test for the entitlement rule.
2. Add an integration test proving a different workspace cannot mutate it.
3. Run the tests and confirm the expected failures.
4. Implement the smallest change that passes them.
5. Do not modify the assertions merely to make the suite green.
6. Run the focused tests after each meaningful change.
7. Run type checking, linting and the production build at the end.

When a test fails, ask Claude to explain the failure before editing.

This prevents the agent from blindly changing whichever line is nearest to the error.

How Should Claude Code Permissions Be Configured?

Claude Code’s usefulness comes from its ability to read files, execute commands, and make changes. Those capabilities also create risk.

Claude Code supports allow, ask, and deny permission rules. Current documentation states that deny rules take precedence over ask rules, and ask rules take precedence over allow rules. Permissions can be stored in project configuration and distributed to the team.

For a SaaS repository, begin conservatively.

Allow routine read operations and focused validation commands. Prompt for writes, Git operations, dependency changes, database actions, and deployment commands. Deny access to secrets and destructive commands.

An illustrative project configuration might be:

{
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "Bash(pnpm lint*)",
      "Bash(pnpm typecheck*)",
      "Bash(pnpm test*)",
      "Bash(pnpm build*)",
      "Bash(git status*)",
      "Bash(git diff*)"
    ],
    "ask": [
      "Edit",
      "Write",
      "Bash(pnpm add*)",
      "Bash(pnpm remove*)",
      "Bash(git commit*)",
      "Bash(git push*)",
      "Bash(supabase db push*)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.local)",
      "Read(./.env.production)",
      "Bash(rm -rf *)"
    ]
  },
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true
  }
}

Adjust this to the commands and operating system used by your project. Avoid copying a broad allowlist that grants more access than the workflow requires.

Claude Code also supports OS-level sandboxing for Bash commands. Sandboxing can restrict filesystem and network access, reducing the blast radius of an unsafe or compromised command. Anthropic describes permissions and sandboxing as complementary layers: permissions decide which tools may run, while the sandbox enforces boundaries around Bash and its child processes.

Do not use unrestricted permission bypass as a normal development mode for a repository containing deployment credentials, production access, signing keys, or customer data.

More autonomy is valuable only after the environment has been designed to contain mistakes.

Where Should Hooks and Deterministic Automation Be Used?

Some rules should not depend on whether the model remembers to follow them.

Claude Code hooks can run commands at specific lifecycle events. They can format edited files, block commands, inject context, audit configuration changes, or validate work when the agent stops.

Useful hooks for a SaaS repository may include:

  • Formatting TypeScript files after edits
  • Blocking writes to generated database types
  • Rejecting changes to environment files
  • Running a focused security contract after server code changes
  • Warning when a migration adds a public table without an RLS statement
  • Recording an audit trail when Claude configuration changes

Hooks should remain fast and deterministic.

A complex test suite belongs in CI. A quick formatter, protected-path check, or migration lint is a better fit for an interactive hook.

The distinction is simple:

Use instructions for judgment. Use hooks and CI for enforcement.

How Should AI-Generated Changes Be Reviewed?

How Should AI-Generated Changes Be Reviewed?

A passing test suite is necessary, but it is not the entire review.

Before merging a Claude-generated change, review it from four perspectives.

Behavioral Review

Confirm that the feature solves the requested user problem.

AI implementations sometimes satisfy the literal prompt while missing an existing product convention. A new settings form may save correctly but bypass the audit log used by every other settings mutation.

Security Review

Inspect authentication, authorization, data exposure, input validation, database policies, secret handling, and external calls.

Pay particular attention to new Server Actions, Route Handlers, service-role clients, SQL functions, webhook routes, redirect URLs, and administrative operations.

Architecture Review

Look for duplicate abstractions, misplaced domain logic, unnecessary dependencies, and modules that cross server-client boundaries.

A working implementation can still make the next feature harder.

Operational Review

Ask what happens when dependencies fail.

Does a webhook retry safely? Does an email failure prevent the main transaction from completing? Can an administrator see and retry a failed event? Will logs contain a request or event identifier? Can the change be rolled back?

Claude can perform an initial review using a fresh context or separate subagent, but sensitive changes still benefit from human inspection. Anthropic’s own guidance describes workflows where one Claude implements a change and another independently reviews it, reducing the chance that the original reasoning simply validates itself.

How Should CI and Branch Protection Complete the Workflow?

Local verification is helpful. CI is authoritative.

A basic Next.js SaaS pipeline should usually include dependency installation, linting, type checking, tests, a production build, and a limited set of critical end-to-end flows.

name: CI

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  quality:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v6

      - uses: pnpm/action-setup@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: pnpm

      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm typecheck
      - run: pnpm test
      - run: pnpm build

Projects with database integration tests or Playwright should add the required services and separate expensive browser checks where appropriate.

GitHub branch protection and rulesets can require selected status checks before merging. This turns the verification suite into a repository rule rather than a suggestion that an agent or developer may ignore.

Keep required job names unique and stable. GitHub warns that duplicate job names across workflows can create ambiguous required-check results and prevent pull requests from merging.

Claude may prepare the change. CI decides whether the repository accepts it.

Common Claude Code Mistakes in SaaS Development

Several failure patterns appear repeatedly when an agent is asked to move too quickly.

MistakeWhy it happensBetter instruction
Coding before understanding the repositoryThe prompt asks only for an outcome“Investigate and explain the current flow before editing”
Creating a second architectureExisting abstractions are not identified“Extend the current domain service and explain any new abstraction”
Trusting client-supplied roles or identifiersThe UI makes values appear trustworthy“Derive identity on the server and re-authorize every mutation”
Using service-role access to bypass RLSIt quickly makes the query work“Correct the policy; service role is forbidden for this user flow”
Processing a webhook twiceThe happy path uses one test event“Assume duplicate and unordered delivery”
Treating provider status as entitlementIt reduces the number of models“Keep provider state and product access separate”
Weakening tests after failurePassing tests becomes the immediate target“Do not change assertions without explaining why the requirement changed”
Running the entire suite after every editThe agent chooses the obvious command“Run focused tests during implementation and broad checks at completion”
Refactoring unrelated filesThe agent tries to improve surrounding code“Stop before unrelated cleanup and report it separately”
Exposing secrets to client codeServer and client boundaries are unclear“Mark privileged modules server-only and inspect the import graph”
Accepting every command automaticallyConvenience is prioritized over containment“Allow routine validation; ask or deny sensitive operations”

These are workflow failures more than model failures.

A stronger model may notice more problems, but it cannot infer every business invariant that the repository never documents or tests.

Complete Example: Adding a Lifetime Purchase Flow

Consider a SaaS product that already supports recurring subscriptions and now needs a one-time lifetime offer.

A weak prompt would be:

Add lifetime billing with Stripe.

That request leaves too many decisions undefined. Claude may create a checkout page, but it still needs to decide how a lifetime purchase maps to a customer, whether the offer can be purchased twice, how access interacts with an existing subscription, how refunds revoke access, and what the webhook should do after duplicate delivery.

A production workflow would divide the change into stages.

Stage One: Investigate the Existing Billing Model

Ask Claude to inspect the catalog, checkout service, customer mapping, event table, entitlement calculator, administration tools, and tests.

The expected output is a description of the current recurring flow and the gaps that prevent one-time offers.

No code changes occur yet.

Stage Two: Define the Domain Behavior

Before discussing Stripe, define the application rules:

Offer:
- Key: shipflash-lifetime
- Billing model: licensed_one_time
- Quantity: one license
- Re-purchase: blocked while entitlement is active

Successful payment:
- Creates a permanent entitlement
- Stores provider customer and transaction references
- Does not create a fake recurring subscription

Refund:
- Revokes the entitlement only when the qualifying transaction is refunded
- Must be idempotent
- Must preserve audit history

Existing subscription:
- Do not cancel it automatically
- Show the conflict to an administrator for the first version

These rules become tests and domain types.

Stage Three: Add the Catalog Entry

Claude adds the offer to the trusted server catalog rather than accepting an arbitrary provider price from the browser.

The checkout action accepts shipflash-lifetime, resolves the provider mapping internally, verifies the current user and workspace, and then invokes the billing adapter.

Stage Four: Implement the Checkout Slice

The implementation creates a one-time checkout session and stores enough internal context to recover the workspace and offer when the provider event arrives.

Tests cover authentication, ownership, unknown catalog entries, duplicate active purchases, and adapter arguments.

At this stage, the checkout can be created, but access is not granted merely because the browser returns from the provider.

Stage Five: Implement Verified Fulfilment

A verified provider event enters the webhook pipeline.

The event ID is persisted. The provider transaction is mapped to the internal offer. The domain service creates the entitlement if it does not already exist. Duplicate delivery returns success without duplicating access.

The browser success page reads internal state. It does not manufacture an entitlement from a success=true query parameter.

Stage Six: Add Refund Handling

Refund processing finds the original qualifying transaction and revokes the associated entitlement according to the product rules.

Tests replay the refund event twice and confirm that the entitlement history remains correct.

Stage Seven: Verify Operations

Claude runs focused tests throughout the implementation, followed by the complete type check, lint, test suite and build.

A fresh review checks:

  • No provider secrets reached client code
  • The service role was not introduced into browser-driven operations
  • Event IDs are unique
  • Entitlement writes are transactional
  • The success page depends on server state
  • Refund processing is replay-safe
  • Existing subscription behavior is unchanged

This workflow takes more steps than generating a checkout button.

It also produces a feature that can survive real customers, retries, refunds, and future maintenance.

How Shipflash Improves the Claude Code Workflow

Claude Code performs best when it begins with a coherent repository.

If authentication, billing, authorization, webhooks, content management, administration, testing, and operational patterns already follow clear boundaries, the agent can extend those systems instead of inventing them during each task.

That is the role of a SaaS foundation.

Shipflash provides a production-oriented Next.js and Supabase starting point with essential product systems and an AI-friendly codebase. The goal is not to generate an entire application from a sentence. It is to give developers and coding agents a maintainable foundation they can understand, test, and extend.

Instead of asking Claude to design authentication, billing abstractions, webhook persistence, role enforcement, database policies, and repository structure at the same time, you can focus the session on the product behavior that makes your SaaS different.

For more context on the difference between fast generation and durable product engineering, read Vibe Coding a SaaS: From Prototype to Production.

You can also explore How to Build a Production-Ready SaaS Foundation with Next.js, Supabase, Billing, and an AI-Friendly Codebase for a deeper look at the systems underneath a maintainable SaaS product.

Frequently Asked Questions About Claude Code for SaaS

Is Claude Code safe to use on a production SaaS repository?

Claude Code can be used safely when its access is deliberately controlled. Use project permission rules, protect secret files, enable sandboxing where supported, review sensitive commands, keep production credentials outside the development environment, and require CI before merging.

The repository should assume that any developer or agent can make a mistake. Security should come from layered technical controls rather than perfect instruction following.

Should Claude Code write database migrations?

Claude Code can write migrations effectively when the desired schema, access model, constraints, grants, policies, compatibility requirements, and rollback risks are specified.

Every generated migration should still be reviewed. Test it against a disposable or local database before applying it to a shared environment, and inspect destructive operations manually.

Can Claude Code implement billing webhooks?

Yes, but the task must include more than parsing an event.

Define signature verification, raw-body handling, unique event persistence, duplicate delivery, event ordering, customer mapping, catalog mapping, entitlement transitions, failure recording, retries, and reconciliation. Add replay tests before relying on the handler for customer access.

Is a CLAUDE.md file enough to make generated code reliable?

No.

CLAUDE.md improves context and consistency, but it is not enforcement. Reliability comes from combining repository instructions with clear architecture, type safety, database constraints, RLS, focused permissions, hooks, automated tests, CI, branch protection, and human review.

How large should a Claude Code task be?

The task should be small enough that its behavior can be explained, implemented, and verified as one coherent change.

A good task often corresponds to one vertical product behavior, such as creating a checkout session, processing one webhook transition, adding one role-protected action, or implementing one settings workflow.

Large migrations can still be completed with Claude Code, but they should be decomposed into tracked stages with explicit checkpoints.

Should Claude Code be allowed to commit and push automatically?

Automatic commits can be reasonable inside an isolated branch or worktree after required checks pass.

Automatic pushes, merges, deployments, database migrations, and production operations deserve stricter controls. Require approval for actions with a large or difficult-to-reverse blast radius.

Conclusion

Claude Code changes how quickly a SaaS team can move through a codebase.

It can inspect unfamiliar modules, connect behavior across files, generate tests, implement changes, run verification, explain failures, and prepare reviewable commits. Used casually, the same speed can produce authorization gaps, duplicate billing logic, permissive database access, and changes that work only for the happy path.

The difference is the workflow.

Prepare the repository with concise instructions. Ask Claude to investigate before editing. Approve a plan for sensitive work. Implement small vertical slices. Treat Server Actions as public endpoints. Make RLS part of the schema rather than an afterthought. Design webhooks for duplicate and unordered delivery. Use tests as executable requirements. Restrict permissions and enforce completion through CI.

Then Claude Code becomes more than a code generator.

It becomes an engineering collaborator operating inside a system designed to keep speed, security, and maintainability aligned.

Build from a foundation Claude can understand.

Shipflash gives founders, developers, indie hackers, and small teams a production-ready SaaS base with essential product systems and an AI-friendly architecture—so your next Claude Code session can focus on building the product rather than rebuilding the foundation.

Looking for more?

Explore our full collection of articles, tutorials, and industry updates.