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

7 min read

TL;DR 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.

On this page
  1. What does a v0 project's next.config turn off?
  2. What broke when I turned type checking back on?
  3. A hand-written Supabase type that turned every query into never
  4. A shared onChange handler passed to a select
  5. A build that needs a key it doesn't have
  6. A checklist for taking a v0 app to production
  7. Where qodesign fits
  8. FAQ
  9. Why does v0 set ignoreBuildErrors to true?
  10. Is it safe to deploy a v0 app with ignoreBuildErrors on?
  11. How do I fix "Property does not exist on type never" with Supabase?
  12. Why does next build fail with "Missing publishableKey" when tsc passes?
  13. Sources

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.configProjectsWhat it does
typescript.ignoreBuildErrors: true97 of 121next build skips type errors
images.unoptimized: true98 of 121every <Image> is served as-is
eslint.ignoreDuringBuilds: true52 of 121skips lint during the build on Next 14 and 15
at least one dependency set to "latest"69 of 121installs 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". 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". 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. 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".

Spotlight Lamp Login running live. A sign-in page in the dark: a desk lamp finds each field and floods the screen when you sign in.

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.

ProjectNext.jstsc errorsnext build
A16.0.1018failed on a type error
B16.2.61failed on a type error
C16.2.60failed while prerendering: missing Clerk key
D16.0.100passed

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. 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. 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, 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 and Clerk custom sign-in page guides show our animated Lamp Login wired to each provider. The qodesign for v0 page 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