v0.dev는 놀랍도록 빠르다. 프롬프트 몇 줄로 Hero 섹션, Features, FAQ가 갖춰진 랜딩 페이지를 뽑아낸다. 문제는 그 다음이다. 생성된 코드를 실제 Next.js 프로젝트에 붙여넣는 순간, 디자인 토큰이 어긋나고, 다크 모드가 깨지고, Lighthouse 점수는 60점대에 머문다. v0.dev의 결함이 아니다. 이 도구의 출력물은 본질적으로 '디자인 목업 React'이지, 여러분의 프로젝트에 즉시 투입 가능한 프로덕션 코드가 아니다.
dev.to에 공개된 From v0 Output to Production Next.js in 90 Minutes 가이드는 이 간극을 메우는 6단계 워크플로우를 제시한다. 단계별 소요 시간을 합산하면 약 90분. 핵심은 v0이 채워주지 않는 영역—App Router 구조화, 디자인 시스템 정렬, SEO, 이미지·폰트 최적화, 분석 연결—을 체계적으로 보완하는 것이다. 여기에 Next.js 하이브리드 렌더링 설계까지 더하면, 'AI로 빠르게 만들고 → 올바른 구조로 프로덕션 수준으로 격상하는' 완결된 흐름이 완성된다.
1단계: 의존성 분석—충돌 먼저 잡는다
v0 코드를 내보내면 app/page.tsx, components/, package.json이 담긴 zip이 생성된다. 첫 번째 할 일은 package.json의 dependencies를 기존 프로젝트와 비교하는 것이다. v0는 lucide-react, class-variance-authority, tailwind-merge 같은 shadcn/ui 호환 패키지를 자동으로 포함한다. 버전 불일치가 있으면 그것이 이후 모든 에러의 근원이 된다. diff로 신규 패키지만 추려 pnpm add로 한 번에 설치하면 1단계는 끝난다.
2단계: App Router 구조화—모놀리식 컴포넌트를 분해한다
v0는 Hero, Features, Testimonial, FAQ, Footer를 app/page.tsx 한 파일에 쌓는다. 프로덕션에서는 이것을 app/(marketing)/ 라우트 그룹 아래로 분리하고, components/landing/ 하위에 각 섹션을 독립 컴포넌트로 쪼개야 한다. 라우트 그룹 패턴은 마케팅 페이지(헤더·푸터 레이아웃)와 앱 내부 페이지(사이드바 레이아웃)를 깔끔하게 분리해준다. 분해한 hero.tsx는 /pricing, /about 등 다른 페이지에서도 재사용 가능한 진짜 컴포넌트가 된다.
3단계: shadcn/ui 토큰 정렬—다크 모드가 작동해야 완성이다
가장 많이 깨지는 지점이다. v0는 bg-zinc-900, text-white 같은 절대값 컬러 클래스를 사용한다. 여러분의 프로젝트는 bg-background, text-foreground, border-border 같은 shadcn/ui 시맨틱 토큰 위에 서 있다. 두 세계를 섞으면 다크 모드 토글이 아무 반응도 하지 않는다. 수정은 일괄 치환이다. text-white → text-foreground, bg-zinc-900 → bg-foreground, text-zinc-500 → text-muted-foreground. globals.css에 CSS 변수가 정의되어 있는지 확인하고 다크 모드 전환을 테스트하면 3단계가 닫힌다.
4·5단계: SEO와 Core Web Vitals—숫자로 증명한다
v0 출력물의 메타데이터는 비어 있다. Next.js App Router의 Metadata API로 title, description, openGraph, twitter 카드를 채운다. app/opengraph-image.tsx의 ImageResponse를 활용하면 페이지별 동적 OG 이미지 생성도 가능하다. JSON-LD 스크립트를 추가하면 검색 결과의 리치 스니펫 가능성이 높아진다. 이 단계만으로 SEO 점수가 90점대로 진입한다.
Core Web Vitals 개선은 세 가지로 요약된다. Hero 이미지의 <img> 태그를 next/image의 <Image priority>로 교체해 LCP를 낮추고, v0가 외부 fetch로 불러오던 Inter 폰트를 next/font/google로 자체 호스팅해 CLS를 제거하며, @next/bundle-analyzer로 v0가 끌어온 미사용 라이브러리를 제거하고 framer-motion 같은 무거운 패키지는 dynamic import로 분리한다. 이 세 가지를 적용하면 LCP 2.5초 미만, CLS 0.1 미만, Lighthouse 90점대가 현실적인 목표가 된다.
6단계: 분석·A/B 테스트—트래픽 없이는 가설도 없다
@vercel/analytics와 @vercel/speed-insights를 RootLayout에 추가하면 페이지뷰와 Core Web Vitals가 자동으로 수집된다. GrowthBook이나 Statsig SDK를 연결해 Hero 헤드라인 2~3종을 랜덤 노출하면, 1,000명 방문 이후부터 통계적으로 유의미한 CTR 차이가 보이기 시작한다. 측정 없는 배포는 다음 가설을 세울 근거를 버리는 것과 같다.
렌더링 전략: v0 컴포넌트를 어디서 실행할 것인가
6단계 워크플로우가 끝나도 한 가지 설계 결정이 남는다. v0가 생성한 각 컴포넌트를 서버 컴포넌트로 둘 것인가, 클라이언트 컴포넌트로 만들 것인가. velog의 Next.js 하이브리드 렌더링 아티클이 정리한 원칙은 명확하다: 서버 컴포넌트를 기본으로, 인터랙션이 필요한 부분만 클라이언트 컴포넌트로 분리한다.
v0가 생성하는 랜딩 페이지 컴포넌트 대부분—Hero 텍스트, Features 카드, Testimonial, Footer—은 상태나 이벤트 리스너가 없다. 이들은 'use client' 선언 없이 서버 컴포넌트로 두면 된다. 서버에서 HTML을 완성해 전달하므로 초기 로딩이 빠르고 검색 엔진도 내용을 읽는다. 반면 이메일 입력 폼, 토글 버튼, 애니메이션 트리거처럼 useState나 useEffect가 필요한 컴포넌트에만 'use client'를 선언한다. 클라이언트 번들 크기를 최소화하는 것이 Core Web Vitals에 직결된다.
주의할 패턴이 하나 있다. 클라이언트 컴포넌트 안에서 서버 컴포넌트를 import하면 에러가 발생한다. 해법은 children prop 슬롯 패턴이다. 클라이언트 컴포넌트는 children이 배치될 위치만 책임지고, 서버 컴포넌트인 Page에서 조립한다. 이렇게 하면 클라이언트 컴포넌트가 리렌더링되더라도 서버 컴포넌트는 영향을 받지 않는다. v0가 생성한 모놀리식 컴포넌트를 2단계에서 분해할 때 이 경계를 함께 설계해 두면 나중에 재작업이 없다.
워크플로우의 진짜 의미
v0.dev는 '1시간짜리 프로토타입'을 만드는 도구다. 이 워크플로우가 하는 일은 그 프로토타입에 '90분짜리 프로덕션 기준'을 씌우는 것이다. 빠른 프로토타이핑 → 사용자 검증 → 고도화라는 흐름에서, AI 도구는 첫 번째 단계의 비용을 극적으로 낮춰준다. 하지만 두 번째 단계에서 실제 트래픽 앞에 내놓으려면 렌더링 전략, 디자인 시스템 정렬, SEO, 성능 최적화—이 네 가지를 인간이 직접 설계해야 한다. AI 출력물을 청사진으로 다루는 것과 완성품으로 착각하는 것 사이의 차이가 여기서 갈린다.
6개 체크리스트 항목 중 7개 이상을 통과하면 프로덕션 기준, 8개 전부를 통과하면 Core Web Vitals가 검색 순위에 긍정적 신호를 보내기 시작한다고 해당 가이드는 말한다. 숫자는 단순하지만, 그 뒤에 있는 설계 결정들은 결코 단순하지 않다. v0를 쓴다는 것은 그 결정들을 더 빨리 마주하게 해주는 것이지, 대신 내려주는 것이 아니다.