Vercel’s v0 is genuinely impressive. Describe a UI and it outputs clean Next.js App Router code with Tailwind and shadcn components, ready to paste into your project. What it doesn’t output is anything SEO-related. No metadata exports, no sitemap, no structured data, and almost everything marked "use client" by default. If you ship the generated code as-is, Google and AI crawlers will struggle to read your content, let alone rank it.
The good news: v0 chose the right foundation. Next.js App Router has excellent SEO capabilities built in. You just need to switch them on. This guide walks through exactly what to add after generating your UI.
What v0 actually is (and what it isn’t)
v0 is a UI code generator, not a hosting platform or a full-stack framework config tool. When you prompt v0, it produces React component code, usually targeting Next.js App Router conventions. You take that code and drop it into your own Next.js project. The SEO work happens in that project, not inside v0 itself.
This distinction matters because people sometimes expect v0 to handle deployment or configuration. It doesn’t. Think of it as an extremely capable component library that writes the code for you. The project structure, routing, metadata, and deployment are all yours to configure. For a deeper look at Next.js SEO in general, the Next.js SEO guide covers the full picture.
The core SEO problem: “use client” everywhere
The biggest SEO risk in v0-generated code is the "use client" directive. v0 adds it freely because it often generates interactive components: dropdowns, modals, form inputs, carousels. Those need client-side JavaScript to work. But the directive causes the component to render entirely in the browser, which means the initial HTML sent to crawlers is empty.
Googlebot can execute JavaScript, but AI crawlers like the ones powering Perplexity, ChatGPT, and Bing’s AI features often do not. An AI crawler that hits a page full of client-side components sees an empty shell. It can’t cite what it can’t read. The same problem affects React SEO more broadly, but v0 makes it worse because the directive gets applied at a high level rather than just to truly interactive parts.
The fix is to push "use client" as far down the component tree as possible. If a page component just lays out sections, it doesn’t need to be a client component. The interactive button inside one of those sections does. Separate the two, and the page renders on the server with its full content in the initial HTML.
How to audit your generated components
Read through any component v0 gave you and ask: does this actually need browser APIs, event listeners, or state that only makes sense client-side? If the answer is no, remove "use client". Common false positives:
- Static layout wrappers
- Hero sections with no interaction
- Pricing tables that just display data
- Blog post bodies
Anything with useState, useEffect, onClick handlers, or browser-only APIs legitimately needs the directive. Everything else probably doesn’t.
Adding real metadata
v0 generates placeholder metadata at best, and often none at all. Next.js App Router exports a metadata object (or a generateMetadata function for dynamic pages) from each page.tsx. That metadata becomes the <title>, <meta name="description">, and Open Graph tags in the rendered HTML.
Here’s what a proper metadata export looks like for a static page:
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Project Management for Design Teams | Acme",
description:
"Acme helps design teams track projects, share feedback, and ship faster. Free for teams up to 5.",
openGraph: {
title: "Project Management for Design Teams | Acme",
description:
"Acme helps design teams track projects, share feedback, and ship faster.",
url: "https://acme.com",
siteName: "Acme",
type: "website",
},
alternates: {
canonical: "https://acme.com",
},
};
Replace the v0 placeholder title with a real one that includes your target keyword near the front. Write a description between 150 and 160 characters that actually describes the page. Set canonical to prevent duplicate content issues, especially if your site is deployed across multiple URLs or environments.
For blog posts and other dynamic pages, use generateMetadata instead, which receives the route params and can fetch data to build the title and description programmatically. The Next.js SEO guide has examples of both patterns.
Adding a sitemap and robots.txt
v0 doesn’t generate these. Next.js App Router has a clean way to add both without any extra packages.
Create app/sitemap.ts to generate your sitemap dynamically:
import { MetadataRoute } from "next";
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: "https://acme.com",
lastModified: new Date(),
changeFrequency: "monthly",
priority: 1,
},
{
url: "https://acme.com/about",
lastModified: new Date(),
changeFrequency: "yearly",
priority: 0.8,
},
];
}
For a blog or any content-driven section, extend this to map over your content array and return one entry per page.
Create app/robots.ts for your robots directives:
import { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
},
sitemap: "https://acme.com/sitemap.xml",
};
}
Both files get picked up automatically by Next.js and served at /sitemap.xml and /robots.txt. Google Search Console expects a sitemap at a predictable URL, and many AI crawlers check robots.txt before indexing.
Adding JSON-LD schema
Structured data helps both Google and AI engines understand what a page is about. It’s the difference between a page that ranks and a page that gets featured in a rich result or cited in an AI answer. v0 won’t add it, but it’s a straightforward addition to any page component.
For a landing page or SaaS homepage, WebSite and Organization schema cover the basics:
export default function HomePage() {
const jsonLd = {
"@context": "https://schema.org",
"@type": "WebSite",
name: "Acme",
url: "https://acme.com",
description: "Project management for design teams.",
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* rest of your page */}
</>
);
}
For blog posts, use Article schema. For product pages, use Product. For FAQs, FAQPage can trigger an accordion in Google results. The schema markup guide has the full breakdown of which types matter for which use cases.
The AI-citation angle
Search engines and AI answer engines use different signals to decide what to cite. Google uses links, content quality, and technical correctness. Perplexity, ChatGPT, and Google AI Overviews lean more heavily on whether they can actually read your content at crawl time, whether it’s structured well, and whether it’s authoritative on a specific topic.
If your v0-generated app is all client-rendered, AI crawlers see an empty page and won’t cite it. Fix the "use client" situation and your content becomes readable. Add schema and your content becomes understandable at a semantic level. That’s the combination that gets cited.
The AI-citation angle also favors pages with a clear, specific topic over pages that cover everything loosely. If you generate a multi-section landing page with v0, consider whether individual sections deserve their own routes with their own focused metadata. Crawlers, human and AI alike, reward specificity. For more on this, the AI SEO guide covers how AI engines decide what to surface.
If you want to track whether your content is actually appearing in AI answers, tools like Fokal run visibility checks across ChatGPT, Perplexity, and AI Overviews on a schedule, so you can see citation rate over time rather than guessing.
What to do next
v0 gives you a solid head start on the UI. The SEO layer is a separate step, and it’s mostly mechanical: push "use client" down to leaf components, add a metadata export to each page, create sitemap.ts and robots.ts, and drop in JSON-LD on key pages. None of this requires touching v0 again.
If you’re building on top of a React SEO foundation more broadly, the patterns here carry over. The core issue across all JavaScript-heavy stacks is the same: server rendering keeps content visible to crawlers that don’t execute JavaScript, and explicit metadata ensures each page has a unique, indexable identity.
Post-generation SEO checklist
- Audit each component for unnecessary
"use client"directives and push them to leaf nodes - Add a
metadataexport (orgenerateMetadata) to everypage.tsx - Set a real title (target keyword near the front), description (150-160 chars), and canonical URL
- Add Open Graph tags for social sharing
- Create
app/sitemap.tslisting every public URL - Create
app/robots.tswith a sitemap reference - Add JSON-LD schema to your homepage and any key landing pages
- Test with Google’s Rich Results Test to confirm schema is valid
- Submit your sitemap to Google Search Console
- Check at least one page with “View Source” to confirm content appears in the initial HTML, not just after JavaScript runs