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.
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.
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.
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.
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:
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.
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.
Run npx tsc --noEmit and read the error count before changing anything.
Fix the errors, then set typescript.ignoreBuildErrors to false (or delete it, since false is the default).
If you use Supabase, generate database.types.ts with the CLI instead of keeping a hand-written Database type.
Set every environment variable the build needs on the machine that runs the build, then run next build.
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.
On Next.js 16, delete the eslint block from next.config. Run your linter as its own step.
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.
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.
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.
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.
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.
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.
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.