# The Hidden Cost of Rewriting: Time vs. Templates

> Discover the real time cost of building auth, databases, and design systems from scratch versus using pre-built kits.
> By Dave · 2026-07-16
> Source: https://otf-kit.dev/blog/the-real-price-of-a-rewrite

## The price nobody budgets for

Three rewrites from zero will eat two months of a solo build. Most of that cost shows up as context switches, not as line items. The "from-scratch" crowd quotes a price in components shipped. The real price is in the evenings you spent learning what you didn't yet know you needed.

I've done both, twice. Here is the time accounting, in evenings, not dollars.

## What "auth from scratch" actually means

The signup form is two fields and a button. The system behind it is twelve things, and you will discover three of them in production:

- Password hashing (argon2 or bcrypt — not SHA, not MD5)
- Session table and a middleware that reads the cookie on every request
- Email verification flow with a signed token
- Password reset flow, also signed, with an expiry
- At least one OAuth provider (Google + GitHub is the floor)
- Rate limiting on `/login` and `/signup`
- CSRF token on every form post
- Secure cookie flags: HttpOnly, SameSite=Lax, Secure in prod
- User-facing pages: `/login`, `/signup`, `/forgot`, the verify-email landing
- Error states: wrong password, expired token, already-verified email, locked after N failures
- A logout endpoint that invalidates the session row, not just clears the cookie
- An audit log, even a tiny one, because the day you don't have it is the day you need it

The signup route is the smallest piece. The handler looks roughly like this:

```ts
// app/api/auth/signup/route.ts
export async function POST(req: Request) {
  const { email, password } = await req.json()

  if (await rateLimit.exceeded(`signup:${email}`)) {
    return new Response('Too many requests', { status: 429 })
  }
  if (await db.findUserByEmail(email)) {
    // generic message — don't leak which side failed
    return Response.json({ ok: true })
  }

  const passwordHash = await argon2.hash(password)
  const user = await db.insert(users).values({ email, passwordHash }).returning()

  await sendVerificationEmail(user.email, signToken({ sub: user.id, email }))
  return Response.json({ ok: true })
}
```

Now multiply that handler shape by: login, logout, OAuth callback, reset-request, reset-confirm, verify, and `/me`. **A tight implementation, before any polish, is 30 to 50 solo-builder evenings.** The first time. With one self-test per route.

## What "DB layer from scratch" actually means

You're not really choosing a database. You're choosing five things at once:

- Where the schema lives (a migrations folder you trust)
- How the schema reaches the app — typed client, ORM, or raw queries you wrap yourself
- How the app reaches the schema in prod — connection pooling, serverless HTTP driver, the boring parts
- How you model soft-delete (or admit you don't, and stop pretending)
- How you index for the queries you'll actually run

The schema for *just* auth looks like this:

```sql
create table users (
  id                uuid primary key default gen_random_uuid(),
  email             text unique not null,
  password_hash     text not null,
  email_verified_at timestamptz,
  created_at        timestamptz not null default now(),
  deleted_at        timestamptz
);
create index users_email_lower_idx on users (lower(email));

create table sessions (
  id         text primary key,
  user_id    uuid not null references users(id) on delete cascade,
  expires_at timestamptz not null,
  created_at timestamptz not null default now()
);
create index sessions_user_idx on sessions (user_id);
```

Two tables. One set of indexes. Then add `accounts` and `verification_tokens` if you took the OAuth path. Then add `subscriptions`, `workspaces`, `members`, and the Stripe webhook. Then the org-invite flow.

**A bare DB layer for a real SaaS is 20 to 40 evenings** if you're writing migrations and a typed query helper yourself.

## What "design system from scratch" actually means

A Button is half a day. A Button with hover, focus-visible, disabled, loading, four variants, three sizes, dark mode, and a press state that feels right on mobile is *two days*. A Dialog with focus-trap, escape-to-close, click-outside, scroll-lock, portal, and ARIA roles is *three days*. Then Toast, Dropdown, Tabs, Tooltip, Checkbox, Switch, Select, Combobox, Form, Sheet, AlertDialog…

You're not building one Button. You're building roughly ten primitives to a quality bar they can be used 200 times without surprises.

Plus tokens. Plus a light/dark switch that doesn't break the contrast ratio. Plus an icon set (or paying $50–$300 for one). Plus responsive breakpoints that actually work on a phone in landscape.

**A real, themable, a11y-passing design system is 60 to 100 evenings solo.** This is the number experienced frontend engineers will read and call conservative. It is.



![solo builder hours from zero vs from a wired kit](https://cdn.otf-kit.dev/blog/the-real-price-of-a-rewrite/inline-1.png)



## The compounding: these aren't three sequential jobs

The naive math is `40 + 30 + 80 = 150 hours`. The real math is bigger, because the layers block each other:

- Auth needs a `users` row. You can't start auth until the DB exists.
- The signup form needs a `<Button>` and an `<Input>`. You can't render auth until the design system exists.
- The design system needs real consumers to harden — and the signup form *is* that consumer.
- Each layer ships a tiny prototype, then you go back and change the previous layer to match.

For a solo builder, there's no parallelization. **Sequential and interleaved is the right mental model, not sequential.** Budget 25–40% more than the sum of the parts. Call it **180 to 250 evenings** from a clean repo to a deployed MVP with auth, a real DB, and a UI you'd actually ship.

## The second cost, the one nobody quotes

The auth library you pick today will be the wrong one in six months. The ORM you commit to will get a new query builder you want. The component primitive you choose will release a v2 with breaking changes. That is the cost of *maintaining* a from-scratch stack: **every model or tool release is a half-day of "do I migrate yet?"**

A wired kit is version-locked. It pins the implementation of every layer it ships. When the auth lib you wrote gets old, *you* own the migration. When a kit's underlying auth lib gets old, you opt in on a release that's already been tested by everyone using the kit.

The hours saved aren't on day one. They're on day 180, day 365, day 500. That's the half of the cost nobody budgets for, because the budget was written on day 1.



![maintenance drag of a from-scratch stack across 12 months](https://cdn.otf-kit.dev/blog/the-real-price-of-a-rewrite/inline-2.png)



## What starting from a wired kit actually buys you

On day one, a wired full-stack kit gives you:

- Auth routes, middleware, DB schema, and the user-facing pages — all wired to each other, all tested against each other
- Migrations and indexes checked in
- A component library where the same `<Button>` renders on web, iOS, and Android from one codebase
- A token set so light and dark flip across platforms with a single edit
- A deploy script that wires a custom domain, DNS, and TLS, plus a mobile build, in one command
- Editor config files for the major AI coding agents — CLAUDE.md, .cursorrules, and 20+ tested prompts — so your agent *extends* the kit instead of regenerating it from scratch every session

The price is what a tank of gas costs. Same effect for the next month: roughly **3 evenings from `git clone` to a deployed URL with auth live**, instead of 150+. The next 6 months of maintenance drag shrink to whatever migrations you choose to opt into.

## The honest tradeoff

If the goal is to *learn* how auth works end to end: build it from scratch, then throw it away. That's a teaching exercise with the right cost attached.

If the goal is to *ship*: start wired. The teaching happens between v1 and v2, when you have real users breaking real flows, which is the only kind of learning that survives contact with a launch.

The two paths aren't the same. The build-from-scratch posts always quote the polished final number, never the 1am "why is the cookie not being set" number. The kit posts sometimes under-quote what you don't get to learn firsthand. Both are real. Pick the one that matches what you actually need from the next twelve weeks of evenings.