# v0 to Production Next.js: What Breaks When You Turn Type Checking Back On

> 97 of 121 public v0 projects set typescript.ignoreBuildErrors: true. Clone from v0's GitHub sync, run tsc --noEmit, turn the flag off, generate Supabase types instead of hand-writing them, set build-time env vars, pin "latest" deps and drop the dead eslint key on Next 16.

- URL: https://qodesign.dev/blog/v0-to-production-nextjs-what-breaks
- Author: Teodor Avadani (https://qodesign.dev/blog/author/teodor-avadani)
- Published: 2026-10-21
- Updated: 2026-10-21
- Category: Next.js fixes
- Publisher: qodesign (https://qodesign.dev), a shop of animated React + Tailwind CSS components for Next.js, each sold with its full source code and an AI prompt.

Moving a v0 app to production mostly means turning back on the checks v0 turns off. Most v0 projects ship with `typescript.ignoreBuildErrors: true` in `next.config`, so `next build` passes no matter how many type errors the code has. Before you rely on the app, clone it from v0's GitHub sync, run `npx tsc --noEmit`, set `ignoreBuildErrors` back to `false`, give the build the same environment variables production has, and pin any dependency set to `"latest"`. In four public v0 projects I tested, two failed the build on type errors and one failed on a missing environment variable.

I make [qodesign](/), a set of React + TypeScript components with Tailwind (most also use Framer Motion), and a lot of the people who buy them start in v0. So I wanted numbers instead of guesses. On October 21, 2026 I searched GitHub for public repositories whose README carries v0's "Automatically synced with your v0.app deployments" line and that were pushed in the previous week. That returned 121 projects. I read the `next.config` and `package.json` of each one, then cloned four small ones, installed them and built them. I'm not naming the four, since they belong to other people and the point is the pattern.

## What does a v0 project's next.config turn off?

Here is what the 121 projects set:

| Setting in next.config | Projects | What it does |
| --- | --- | --- |
| `typescript.ignoreBuildErrors: true` | 97 of 121 | `next build` skips type errors |
| `images.unoptimized: true` | 98 of 121 | every `<Image>` is served as-is |
| `eslint.ignoreDuringBuilds: true` | 52 of 121 | skips lint during the build on Next 14 and 15 |
| at least one dependency set to `"latest"` | 69 of 121 | installs whatever is newest on the day you build |

Next.js's own docs describe `ignoreBuildErrors` as letting Next.js ["dangerously produce production code even when your application has errors"](https://nextjs.org/docs/app/api-reference/config/next-config-js/typescript). It defaults to `false`. In a v0 chat that trade makes sense: a preview that renders beats a red error screen. Once real users and real data are involved, it means the build no longer tells you when something is wrong.

The same docs say `images.unoptimized: true` serves each image ["as-is from the src instead of changing quality, size, or format"](https://nextjs.org/docs/app/api-reference/components/image). That's fine for SVG icons. For a hero photo it means visitors download the full-size file.

The `eslint` key is a smaller leftover. Next.js 16 [removed the `eslint` option from the config file and stopped running lint during `next build`](https://nextjs.org/docs/app/guides/upgrading/version-16). 14 of the 75 projects on Next 16 still carry it. It does nothing there, and you can delete it.

The Next.js versions were spread out too. 41 projects were on 16.0.10, the rest ranged from 14.1.0 to 16.4.2, and one had `next` itself set to `"latest"`.

## What broke when I turned type checking back on?

For each of the four clones I ran `npx tsc --noEmit`, then changed `ignoreBuildErrors` to `false` and ran `next build`.

| Project | Next.js | tsc errors | next build |
| --- | --- | --- | --- |
| A | 16.0.10 | 18 | failed on a type error |
| B | 16.2.6 | 1 | failed on a type error |
| C | 16.2.6 | 0 | failed while prerendering: missing Clerk key |
| D | 16.0.10 | 0 | passed |

Each failure is worth a closer look, because each is a pattern you will probably meet in your own v0 app.

### A hand-written Supabase type that turned every query into `never`

Project A had 18 errors, and 14 of them were the same kind: `Property 'slug' does not exist on type 'never'`, and the same message for `tags`, `id`, `name`, `description` and `updated_at`. The code wasn't misspelling any of them. The project types its Supabase client with a hand-written `Database` interface, and every query result came back typed as `never`, so any property access on a row failed.

The hand-written type looked like this:

```ts
export interface Database {
  public: {
    Tables: {
      taxonomies: {
        Row: Taxonomy
        Insert: Omit<Taxonomy, 'id' | 'created_at' | 'updated_at'>
        Update: Partial<Omit<Taxonomy, 'id'>>
      }
      // ...five more tables in the same shape
    }
  }
}
```

With `@supabase/supabase-js` 2.93.3 installed, I made two edits to that file: every `interface` became a `type` alias, and each table got `Relationships: []`, with empty `Views` and `Functions` next to `Tables`:

```ts
export type Database = {
  public: {
    Tables: {
      taxonomies: {
        Row: Taxonomy
        Insert: Omit<Taxonomy, 'id' | 'created_at' | 'updated_at'>
        Update: Partial<Omit<Taxonomy, 'id'>>
        Relationships: []
      }
      // ...same for the other tables
    }
    Views: {}
    Functions: {}
  }
}
```

The errors dropped from 18 to 5, and all 14 `never` errors went away. I made both edits together, so I can't tell you which one did the work. The five that were left were the interesting part, because the `never` errors had been hiding them: one regular expression flag that needs `target` ES2018 or later in `tsconfig.json` (the project targets ES6), and four places in the data code where a query or a cast doesn't match the declared row types. Some of those may be the types being out of date rather than the code being wrong. Either way, those are the questions you want the build asking.

The lasting fix is to stop writing the type by hand. Supabase's docs show how to generate it from your real schema with [`npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > database.types.ts`](https://supabase.com/docs/guides/api/rest/generating-types). Generate it again whenever the schema changes.

### A shared onChange handler passed to a select

Project B had a single error, in a contact form:

```
Type '(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void'
is not assignable to type 'ChangeEventHandler<HTMLSelectElement, HTMLSelectElement>'.
```

One handler served all six fields, and its type only listed inputs and textareas. A `<select>` in the same form used it too. With `@types/react` 19.3.0 that's an error. Adding the select to the union fixed it, and tsc then reported 0 errors:

```tsx
const handleChange =
  (field: keyof typeof form) =>
  (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
    setForm((prev) => ({ ...prev, [field]: e.target.value }))
  }
```

My guess is that the code ran fine at runtime, since all three elements have a `value`. That's the case where ignoring the error costs nothing. You only learn which case you're in by reading the errors, though.

### A build that needs a key it doesn't have

Project C passed tsc with 0 errors and still failed `next build`:

```
Error occurred prerendering page "/_not-found".
Error: @clerk/clerk-react: Missing publishableKey.
```

Next.js prerenders static pages at build time, and the Clerk provider in the root layout throws when its publishable key is missing. On Vercel the key lives in the project's environment variables, so the build there works. On a new host, in CI or on your laptop, it doesn't exist until you add it. v0's FAQ says the [full experience, environment variables included, assumes the GitHub repo is connected to a Vercel project](https://v0.app/docs/faqs). Before you move hosts, list every `process.env` name the code reads and set each one where the build runs. A passing tsc doesn't prove a passing build, so run both.

## A checklist for taking a v0 app to production

1. Connect the chat to GitHub and clone the repo. v0's docs say it [works on a branch named like `v0/main-abc123` and commits every code change automatically](https://v0.app/docs/faqs), so you always have the latest code to check.
2. Run `npx tsc --noEmit` and read the error count before changing anything.
3. Fix the errors, then set `typescript.ignoreBuildErrors` to `false` (or delete it, since `false` is the default).
4. If you use Supabase, generate `database.types.ts` with the CLI instead of keeping a hand-written `Database` type.
5. Set every environment variable the build needs on the machine that runs the build, then run `next build`.
6. Replace each `"latest"` in `package.json` with the version your lockfile actually installed, so a future build doesn't pick up a new major version on its own.
7. On Next.js 16, delete the `eslint` block from `next.config`. Run your linter as its own step.
8. Decide about `images.unoptimized`. Keep it for SVGs and tiny icons if you like, but large photos should go through the image optimizer or be compressed before upload.
9. Add a script such as `"check": "tsc --noEmit && next build"` and make it pass before anything merges into the branch that deploys.

You can keep using v0 for UI work after this, and the build will catch type errors it introduces.

## Where qodesign fits

Two of the four projects I tested had auth wired in, one with Supabase and one with Clerk. If the login screen is the part you want to look less generic, the [Supabase login page](/react/supabase-login-page) and [Clerk custom sign-in page](/react/clerk-custom-sign-in-page) guides show our animated Lamp Login wired to each provider. The [qodesign for v0 page](/for/v0) covers how our files drop into a v0 project.

## FAQ

### Why does v0 set ignoreBuildErrors to true?

v0 doesn't explain it in its docs, so this is my reading: a preview that builds is more useful in a chat than one that stops on a type error. It turns into a problem when the same setting ships to production, because `next build` then passes with errors in the code.

### Is it safe to deploy a v0 app with ignoreBuildErrors on?

The app can work, and project D in my test built cleanly anyway. But you can't know which case you're in without running `tsc`. In my test, 2 of 4 projects had type errors, and in one of them those errors hid others.

### How do I fix "Property does not exist on type never" with Supabase?

It usually means the client's `Database` type doesn't match what `@supabase/supabase-js` expects, so query results come back as `never`. Generate the type with `npx supabase gen types typescript` and pass it to `createClient<Database>()`. In my test, converting a hand-written interface to a type alias with `Relationships`, `Views` and `Functions` also cleared the `never` errors.

### Why does next build fail with "Missing publishableKey" when tsc passes?

`tsc` only checks types. `next build` also runs your pages to prerender them, and a provider like Clerk throws during that step if its environment variable isn't set. Add the variable where the build runs.

## Sources

- [Next.js docs: typescript option (ignoreBuildErrors)](https://nextjs.org/docs/app/api-reference/config/next-config-js/typescript)
- [Next.js docs: Image component (unoptimized)](https://nextjs.org/docs/app/api-reference/components/image)
- [Next.js docs: upgrading to version 16](https://nextjs.org/docs/app/guides/upgrading/version-16)
- [v0 docs: FAQs (Git integration, Vercel projects)](https://v0.app/docs/faqs)
- [Supabase docs: generating TypeScript types](https://supabase.com/docs/guides/api/rest/generating-types)
