To use WordPress with V0, keep WordPress as your headless CMS and build the front end in V0 as a Next.js app. Create a Next.js API route that fetches posts from the WordPress REST API (yoursite.com/wp-json/wp/v2/posts), then prompt V0 to generate React components that display the data. V0 handles the UI; WordPress handles content management.
Using WordPress as a Headless CMS with V0
The headless WordPress approach is increasingly popular among founders who have existing WordPress content but want a modern, fast front end. WordPress handles what it is great at — content editing, SEO plugins like Yoast, media management, and familiar editorial workflows. V0 and Next.js handle the front end, giving you complete control over design and performance without WordPress theme limitations.
WordPress exposes all of its content through a REST API that is available by default on any WordPress installation. Public posts, pages, categories, tags, media, and custom post types are all accessible without authentication at endpoints like yoursite.com/wp-json/wp/v2/posts. For private drafts or password-protected content, you authenticate with Application Passwords, a feature built into WordPress 5.6 and later that generates API tokens without exposing your admin password.
In this architecture, V0 generates beautiful React components and pages. A Next.js API route acts as a proxy layer between your front end and WordPress, handling authentication and data transformation. Vercel hosts the Next.js app and deploys it automatically from GitHub. The result is a fast, SEO-friendly, visually polished site that your team can still update through the familiar WordPress editor.
Integration method
V0 generates the Next.js front-end components and pages that display your WordPress content. A Next.js API route proxies requests to the WordPress REST API, handling authentication for private content and keeping your WordPress credentials server-side. The V0-generated Next.js app fetches content at request time or build time, rendering it with your custom design instead of WordPress themes.
Prerequisites
- A V0 account with a Next.js project at v0.dev
- A WordPress site (self-hosted or WordPress.com Business plan) with the REST API enabled (it is enabled by default on all modern WordPress installations)
- Your WordPress site URL (e.g., https://your-wordpress-site.com)
- For private content: a WordPress Application Password generated at yoursite.com/wp-admin/profile.php under Application Passwords
- A Vercel account with your V0 project deployed via GitHub
Step-by-step guide
Verify Your WordPress REST API Is Accessible
Verify Your WordPress REST API Is Accessible
Before writing any code, confirm that your WordPress site's REST API is accessible and returning data. Open a browser and navigate to https://your-wordpress-site.com/wp-json/wp/v2/posts — replace the domain with your actual WordPress URL. You should see a JSON response containing an array of your recent posts with fields like id, title, content, excerpt, date, author, featured_media, and link. If you see an empty array, your WordPress site may have no published posts yet. If you see an error like 'rest_no_route' or 'no_route', the REST API may have been disabled by a security plugin. Common culprits are plugins like Wordfence or Disable REST API — you may need to whitelist certain endpoints in those plugins or re-enable the API for public endpoints. Also check the /wp-json/wp/v2/pages endpoint for pages, and if you use custom post types, they are typically available at /wp-json/wp/v2/{custom-post-type}. Take note of the URL structure and the fields returned — you will use this when crafting your API route. The WordPress REST API documentation at developer.wordpress.org/rest-api is the best reference for available endpoints and query parameters like per_page, orderby, order, categories, and search.
Pro tip: Test your WordPress API with a tool like Hoppscotch (hoppscotch.io) or by pasting the URL in your browser. This confirms connectivity before you write any Next.js code.
Expected result: Visiting https://your-wordpress-site.com/wp-json/wp/v2/posts in a browser returns a JSON array of published posts. You can see the title, content, and other fields for each post.
Create the WordPress API Proxy Route
Create the WordPress API Proxy Route
Create a Next.js API route that proxies requests to your WordPress REST API. This proxy layer serves two purposes: it keeps any authentication credentials server-side so they are never exposed to the browser, and it gives you a place to transform or filter the WordPress data before it reaches your V0 components. Create the file app/api/wordpress/posts/route.ts. This route accepts GET requests, fetches from your WordPress REST API using the native fetch() function (available in Next.js without any additional imports), and returns the data as JSON. The WORDPRESS_API_URL environment variable holds your WordPress site's base URL so you do not hardcode it. For public posts, no authentication headers are needed. For private drafts or password-protected content, add an Authorization header using a WordPress Application Password. Generate an Application Password in your WordPress admin by going to yoursite.com/wp-admin/profile.php, scrolling to the Application Passwords section at the bottom, typing a name for the app (like 'V0 Frontend'), and clicking Add New Application Password. WordPress generates a space-separated password string — store it in Vercel as WORDPRESS_APP_PASSWORD along with your WordPress admin username as WORDPRESS_APP_USERNAME. The Authorization header format is Basic followed by a base64 encoding of username:password. The API route also handles pagination — WordPress's REST API returns 10 posts by default, and you can request more with the per_page parameter (maximum 100 per request). Pass through the page query parameter from the incoming request so your V0 component can implement pagination.
Create a Next.js API route at app/api/wordpress/posts/route.ts that fetches posts from a WordPress REST API. Read WORDPRESS_API_URL from environment variables. Support a 'page' query parameter for pagination. Return the posts as JSON. Handle errors gracefully with a 500 response.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23export async function GET(request: NextRequest) {4 const { searchParams } = new URL(request.url);5 const page = searchParams.get('page') || '1';6 const perPage = searchParams.get('per_page') || '10';7 const category = searchParams.get('category') || '';89 const wordpressUrl = process.env.WORDPRESS_API_URL;1011 if (!wordpressUrl) {12 return NextResponse.json(13 { error: 'WordPress API URL not configured' },14 { status: 500 }15 );16 }1718 // Build the WordPress REST API URL19 const apiUrl = new URL(`${wordpressUrl}/wp-json/wp/v2/posts`);20 apiUrl.searchParams.set('page', page);21 apiUrl.searchParams.set('per_page', perPage);22 apiUrl.searchParams.set('_embed', 'true'); // includes featured images and author data23 if (category) apiUrl.searchParams.set('categories', category);2425 // Build headers — add auth only if credentials are configured26 const headers: Record<string, string> = {27 'Content-Type': 'application/json',28 };2930 const username = process.env.WORDPRESS_APP_USERNAME;31 const password = process.env.WORDPRESS_APP_PASSWORD;3233 if (username && password) {34 const credentials = Buffer.from(`${username}:${password}`).toString('base64');35 headers['Authorization'] = `Basic ${credentials}`;36 }3738 try {39 const response = await fetch(apiUrl.toString(), {40 headers,41 next: { revalidate: 300 }, // Cache for 5 minutes42 });4344 if (!response.ok) {45 throw new Error(`WordPress API returned ${response.status}`);46 }4748 const posts = await response.json();49 const totalPages = response.headers.get('X-WP-TotalPages');50 const total = response.headers.get('X-WP-Total');5152 return NextResponse.json({53 posts,54 pagination: {55 totalPages: Number(totalPages),56 total: Number(total),57 currentPage: Number(page),58 },59 });60 } catch (error) {61 console.error('WordPress API error:', error);62 return NextResponse.json(63 { error: 'Failed to fetch posts from WordPress' },64 { status: 500 }65 );66 }67}Pro tip: The _embed=true query parameter tells WordPress to include related data like featured images and author info in the response, saving additional API calls.
Expected result: Calling /api/wordpress/posts returns a JSON object with a posts array and pagination metadata. Featured images and author names are included in each post via the _embed parameter.
Add Environment Variables to Vercel
Add Environment Variables to Vercel
Your API route reads configuration from environment variables. You need to add these to Vercel so they are available in production. Go to your Vercel Dashboard, open your project, click the Settings tab, then click Environment Variables in the left sidebar. Add WORDPRESS_API_URL as the base URL of your WordPress site without a trailing slash, for example https://your-wordpress-site.com. This is not a secret (it is your public website URL), but keeping it in an environment variable makes it easy to change without editing code. Set it to apply to Production, Preview, and Development environments. If you need to access private content, also add WORDPRESS_APP_USERNAME (your WordPress admin username, typically admin or a dedicated API user) and WORDPRESS_APP_PASSWORD (the Application Password you generated in WordPress admin — it looks like abcd efgh ijkl mnop with spaces). Store both as environment variables in Vercel. The password in particular should be treated as a secret — do not put it in your code or commit it to GitHub. For a smooth local development experience, also create a .env.local file in your project root with the same variables. V0's generated .gitignore already excludes this file from version control. After adding all variables in Vercel, trigger a redeployment by pushing a commit or clicking Redeploy in the Vercel Dashboard.
1# .env.local — for local development only, never commit to Git2WORDPRESS_API_URL=https://your-wordpress-site.com3WORDPRESS_APP_USERNAME=your-admin-username4WORDPRESS_APP_PASSWORD=your-app-password-from-wordpressPro tip: Create a dedicated WordPress user for API access rather than using your admin account. Give it just Editor or Contributor role to limit what the API key can do if it is ever compromised.
Expected result: Vercel Dashboard shows the environment variables saved. Your API route can fetch WordPress posts without errors in the Vercel deployment.
Generate the Blog Components with V0
Generate the Blog Components with V0
With your API route in place and environment variables configured, prompt V0 to generate the React components that consume the data. The key is to tell V0 the exact shape of the data your API returns so it can write correct TypeScript types and access the right fields. For a blog listing page, you need a component that fetches from /api/wordpress/posts on component mount (or at the page level), maps over the posts array, and renders each post as a card with title, excerpt, featured image, date, and author. For individual post pages, create a dynamic route at app/blog/[slug]/page.tsx that fetches a single post by slug from a second API route at /api/wordpress/posts/[slug]/route.ts. WordPress post titles and content fields may contain HTML — the title.rendered and content.rendered fields from the WordPress API are HTML strings, not plain text. To render them safely in React, use dangerouslySetInnerHTML={{ __html: post.content.rendered }} for the content area. V0 should generate this correctly if you mention it in your prompt, but it is worth verifying in the generated code. For featured images, WordPress returns them in the _embedded['wp:featuredmedia'] array when you use the _embed=true parameter in your API route. Ask V0 to handle the case where no featured image is set by using a fallback placeholder image. Also ask V0 to generate good loading states (skeleton loaders while content fetches), error states (a message if the API is unavailable), and empty states (a message if no posts are found). These make the integration feel polished and handle real-world edge cases gracefully.
Create a blog page at app/blog/page.tsx that fetches posts from /api/wordpress/posts and displays them in a responsive card grid. Each card shows the featured image (from _embedded['wp:featuredmedia'][0].source_url), post title (from title.rendered using dangerouslySetInnerHTML), excerpt (from excerpt.rendered), author name (from _embedded.author[0].name), formatted date, and a Read More link to /blog/{post.slug}. Include loading skeleton cards and an error state.
Paste this in V0 chat
Pro tip: Ask V0 to add Next.js Image optimization by using the next/image component for featured images. This automatically resizes and optimizes images for faster page loads.
Expected result: V0 generates a blog listing page with WordPress post data displayed in a polished grid layout. Clicking a post title navigates to the individual post page.
Create the Individual Post Page Route
Create the Individual Post Page Route
A complete headless WordPress integration needs dynamic post pages — individual URLs for each blog post or article. In Next.js App Router, this is a dynamic route at app/blog/[slug]/page.tsx where [slug] matches the WordPress post slug (the URL-friendly version of the post title). Create a companion API route at app/api/wordpress/posts/[slug]/route.ts that accepts GET requests and fetches a single post from WordPress by slug using the filter query parameter: /wp-json/wp/v2/posts?slug={slug}&_embed=true. WordPress returns an array even for single slug lookups, so your API route should return the first element. For good SEO, use Next.js's generateMetadata function to set the page title and description from the WordPress post data. This is one of the major advantages of the headless approach — you can use the WordPress SEO metadata (or Yoast SEO plugin data if installed) while still having full control over how it is rendered in the HTML head. For improved performance, consider adding generateStaticParams to pre-render your most recent posts at build time. This generates static HTML for those posts during Vercel's build process, making them load instantly instead of being generated on-demand. For a blog with hundreds of posts, you might only statically generate the most recent 20-50 and let the rest be server-rendered on demand.
Create an individual post page at app/blog/[slug]/page.tsx that fetches a single post from /api/wordpress/posts/[slug]. Display the featured image as a full-width hero, the post title as an H1, author name and formatted date below the title, and the post content rendered safely from content.rendered. Add a Back to Blog link at the top. Set the page title from the post title using generateMetadata.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23interface RouteParams {4 params: { slug: string };5}67export async function GET(8 request: NextRequest,9 { params }: RouteParams10) {11 const { slug } = params;12 const wordpressUrl = process.env.WORDPRESS_API_URL;1314 if (!wordpressUrl) {15 return NextResponse.json(16 { error: 'WordPress API URL not configured' },17 { status: 500 }18 );19 }2021 const apiUrl = `${wordpressUrl}/wp-json/wp/v2/posts?slug=${encodeURIComponent(slug)}&_embed=true`;2223 const headers: Record<string, string> = {};24 const username = process.env.WORDPRESS_APP_USERNAME;25 const password = process.env.WORDPRESS_APP_PASSWORD;2627 if (username && password) {28 const credentials = Buffer.from(`${username}:${password}`).toString('base64');29 headers['Authorization'] = `Basic ${credentials}`;30 }3132 try {33 const response = await fetch(apiUrl, {34 headers,35 next: { revalidate: 600 }, // Cache individual posts for 10 minutes36 });3738 if (!response.ok) {39 throw new Error(`WordPress API returned ${response.status}`);40 }4142 const posts = await response.json();4344 if (!posts || posts.length === 0) {45 return NextResponse.json({ error: 'Post not found' }, { status: 404 });46 }4748 return NextResponse.json(posts[0]);49 } catch (error) {50 console.error('WordPress single post fetch error:', error);51 return NextResponse.json(52 { error: 'Failed to fetch post' },53 { status: 500 }54 );55 }56}Pro tip: Add next: { revalidate: 600 } to your WordPress fetch calls to cache responses for 10 minutes. This drastically reduces the load on your WordPress server and speeds up your Vercel deployment.
Expected result: Individual post pages load at /blog/{post-slug} with the correct content from WordPress. The page title in the browser tab matches the WordPress post title.
Common use cases
Company Blog with Custom Design
A startup wants their blog to match their custom brand design rather than a generic WordPress theme. V0 generates a blog listing page and individual post pages with their exact design system. WordPress remains the editorial tool that the marketing team already knows.
Create a blog listing page that displays posts fetched from /api/wordpress/posts. Each post shows a featured image, title, author, date, category badge, and excerpt. Use a responsive two-column grid on desktop and single column on mobile. Include a Load More button for pagination.
Copy this prompt to try it in V0
Knowledge Base from WordPress Pages
A SaaS product uses WordPress Pages to maintain a knowledge base. V0 generates a documentation-style layout with sidebar navigation, full-width content area, and table of contents. The Next.js app fetches pages from WordPress by slug and renders them with consistent design.
Build a documentation page layout with a sticky sidebar showing category navigation on the left and the article content on the right. Fetch the article content from /api/wordpress/pages/{slug} and render the HTML content safely. Include breadcrumbs at the top and previous/next article links at the bottom.
Copy this prompt to try it in V0
Portfolio Site with WordPress as CMS
A freelance designer uses WordPress Custom Post Types to manage portfolio projects. V0 generates a portfolio grid page and detailed project pages. The client can add new portfolio items through WordPress admin while the V0-designed front end automatically picks them up.
Create a portfolio grid page that fetches custom post type 'project' items from /api/wordpress/portfolio. Each item shows a cover image, project title, client name, and technology tags. Clicking an item opens a full-screen detail view with project description and image gallery.
Copy this prompt to try it in V0
Troubleshooting
API route returns empty array [] even though WordPress has published posts
Cause: The WordPress REST API only returns posts with 'published' status by default. Posts in draft, pending, or private status require authentication. Also, some security plugins disable the REST API for non-logged-in users.
Solution: First, test the WordPress API URL directly in your browser. If you see an empty array for published posts, check if a security plugin (Wordfence, Disable REST API) is blocking the endpoint. Add authentication headers to your API route using WordPress Application Passwords if accessing private content.
Featured images are undefined or null in the response
Cause: The _embed parameter was not included in the WordPress API request, so featured media data is not included in the response. Or a post was published without a featured image.
Solution: Ensure your API URL includes _embed=true as a query parameter. Add null-safety checks in your V0 component when accessing the featured image: post._embedded?.['wp:featuredmedia']?.[0]?.source_url ?? '/fallback-image.jpg'.
1// Safe featured image access2const featuredImageUrl = 3 post._embedded?.['wp:featuredmedia']?.[0]?.source_url ?? 4 '/placeholder-image.jpg';Post content renders as escaped HTML like <p> instead of formatted paragraphs
Cause: The post content is being rendered as a text string rather than as HTML. This happens when you use {post.content.rendered} in JSX without dangerouslySetInnerHTML.
Solution: Use dangerouslySetInnerHTML to render WordPress HTML content: <div dangerouslySetInnerHTML={{ __html: post.content.rendered }} />. Make sure you trust the source (your own WordPress site) before doing this.
1<div 2 className="prose prose-lg max-w-none"3 dangerouslySetInnerHTML={{ __html: post.content.rendered }} 4/>CORS error when the V0 component tries to call the WordPress API directly from the browser
Cause: The V0 component is trying to fetch directly from yoursite.com/wp-json/wp/v2/posts in the browser instead of going through your Next.js API route. WordPress may not have CORS headers configured for your Vercel domain.
Solution: Change all WordPress API calls in your React components to go through your Next.js API route (/api/wordpress/posts), not directly to WordPress. The API route runs server-side on Vercel where CORS does not apply.
1// WRONG — causes CORS errors2fetch('https://your-wordpress-site.com/wp-json/wp/v2/posts')34// CORRECT — goes through your Next.js API route5fetch('/api/wordpress/posts')Best practices
- Always proxy WordPress API calls through Next.js API routes instead of calling WordPress directly from React components — this avoids CORS issues and keeps credentials server-side.
- Use the _embed=true parameter in all WordPress API requests to get featured images and author data in a single request.
- Add Next.js fetch caching (next: { revalidate: 300 }) to WordPress API calls to reduce load on your WordPress server.
- Never store WordPress Application Passwords in code — use Vercel environment variables and .env.local files that are excluded from Git.
- Use dangerouslySetInnerHTML only for WordPress content from your own site — never render HTML from user-submitted or untrusted sources.
- Create a dedicated WordPress user for API access with minimal permissions rather than using your admin account.
- Test your headless setup with Lighthouse to verify page performance — one major benefit of headless WordPress is faster load times than WordPress themes.
- Keep WordPress updated to the latest version to ensure REST API security patches are applied.
Alternatives
TYPO3 is an alternative enterprise CMS with a REST API for headless setups, preferred by large European organizations with complex content structures.
Airtable is a simpler alternative for content management with a spreadsheet-like UI — ideal for smaller content libraries without WordPress's editorial complexity.
HubSpot is an alternative if you want content management combined with CRM, email marketing, and lead capture in a single platform.
Frequently asked questions
Do I need to install any WordPress plugins to use V0 with WordPress?
No plugins are required. The WordPress REST API is built into WordPress core since version 4.7 and is available by default. You only need a plugin if you want to expose custom post types (Custom Post Type UI plugin), add GraphQL support (WPGraphQL), or manage CORS headers (if calling WordPress directly from the browser, which we recommend against).
Will my WordPress SEO settings work with a headless setup?
SEO plugins like Yoast add meta data to WordPress that can be accessed via the REST API through the yoast_head_json field on each post. You can read these values in your Next.js API route and pass them to the generateMetadata function in your Next.js page components. This lets you preserve your Yoast SEO settings in the headless frontend.
Can V0 fetch WordPress custom post types?
Yes. WordPress custom post types are available at /wp-json/wp/v2/{post-type-slug} when they are registered with show_in_rest: true. Create a separate API route for each custom post type you want to use, following the same pattern as the posts route. Ask V0 to generate the components once you have the API route returning data.
How do I handle WordPress pagination with V0?
The WordPress REST API returns pagination information in the X-WP-Total and X-WP-TotalPages response headers. Pass these values back in your Next.js API route response. Then prompt V0 to generate a pagination component that reads these values and calls /api/wordpress/posts?page=2, etc. for subsequent pages.
Is this approach good for a high-traffic website?
Yes — this is one of the main benefits of headless WordPress. Your Next.js app on Vercel handles traffic scaling automatically, and Next.js fetch caching means most requests never hit WordPress at all. Your WordPress server only handles admin traffic and API requests that miss the cache. For very high traffic, consider a dedicated WordPress hosting plan or a managed WordPress service like WP Engine.
Can I still use the WordPress block editor (Gutenberg) in a headless setup?
Yes. Content authors use the WordPress admin and block editor exactly as before. The blocks are serialized to HTML that is available in the content.rendered field of the REST API response. Your Next.js app renders this HTML using dangerouslySetInnerHTML. Some advanced Gutenberg blocks with JavaScript interactivity will not work in the rendered HTML, but standard text, image, and layout blocks display fine.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation