Skip to main content
RapidDev - Software Development Agency
v0-integrationsNext.js API Route

How to Integrate Facebook Ads with V0

To use Facebook Ads with V0 by Vercel, create a Next.js API route that calls the Meta Marketing API with a long-lived access token stored as a server-only environment variable. V0 generates the campaign dashboard UI; your API route fetches ad account data and returns it as JSON. This keeps your Meta token secure and off the client.

What you'll learn

  • How to generate a Meta Marketing API access token and store it securely in Vercel
  • How to create a Next.js API route that fetches Facebook Ads campaign data
  • How to build an ad performance dashboard UI with V0 that displays campaign metrics
  • How to handle Meta API pagination and rate limits in your API route
  • How V0's Facebook Ads integration differs from search-based advertising platforms like Google Ads
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read30 minutesMarketingApril 2026RapidDev Engineering Team
TL;DR

To use Facebook Ads with V0 by Vercel, create a Next.js API route that calls the Meta Marketing API with a long-lived access token stored as a server-only environment variable. V0 generates the campaign dashboard UI; your API route fetches ad account data and returns it as JSON. This keeps your Meta token secure and off the client.

Building a Facebook Ads Dashboard with V0 and the Meta Marketing API

Facebook Ads (now part of Meta) runs on the Meta Marketing API — a REST API that lets you read campaign performance data, manage audiences, create ads, and pull insights on spend, impressions, clicks, and conversions. For V0 developers, this API is most valuable for building internal dashboards that surface ad performance data without requiring your team to log into Meta Business Manager every time.

The integration follows the standard Next.js API route pattern: V0 generates the React dashboard interface, and a server-side API route holds your Meta access token and makes authenticated calls to the Marketing API. This separation is critical because Meta access tokens are long-lived credentials that should never appear in browser network requests or JavaScript bundles. The API route acts as a secure proxy, fetching data from Meta and returning only what your UI needs.

V0 is particularly well-suited for generating ad performance dashboards — campaign metric tables, spend trend charts, audience breakdown cards, and ROAS (return on ad spend) summaries. Once you have the API route returning real data, you can ask V0 to build progressively richer analytics views. This tutorial covers the complete path from generating a dashboard in V0 to a deployed Vercel app pulling live Facebook Ads data.

Integration method

Next.js API Route

V0 generates the React UI components for your ad performance dashboard. A Next.js API route at app/api/facebook-ads/route.ts holds your Meta access token as a server-only environment variable and proxies requests to the Meta Marketing API. The frontend fetches from your API route — the token never reaches the browser.

Prerequisites

  • A V0 account at v0.dev with a Next.js project
  • A Meta Developer account at developers.facebook.com with a created app
  • Your app's Marketing API access enabled (Meta App Dashboard → Add Products → Marketing API)
  • A long-lived Meta access token with ads_read and ads_management permissions
  • Your Meta Ad Account ID (found in Meta Business Manager under Business Settings → Ad Accounts)

Step-by-step guide

1

Generate the Campaign Dashboard UI in V0

Open V0 and describe the ad performance dashboard you want to build. The goal at this stage is to get a polished React component that displays the data shape you'll later fetch from the Meta Marketing API. Think about which metrics matter most to your use case — campaign-level data includes name, status, objective, budget, impressions, clicks, CTR, spend, and conversions. Ad set-level data adds targeting details. Ad-level data adds creative performance. Ask V0 to use realistic placeholder data that mirrors the Meta API response shape. For campaign data, a typical object looks like: { id, name, status, objective, daily_budget, insights: { impressions, clicks, spend, ctr, actions } }. When V0 uses this shape in local state, swapping in real API data later becomes a direct replacement rather than a restructuring exercise. Also ask V0 to include loading skeletons and error states. The Meta Marketing API can be slow (especially for insights with large date ranges), so loading states are important for user experience. In Design Mode, refine the visual density of the table or cards to match the volume of data you expect. A campaign table typically works better than a card grid when you have 20+ campaigns.

V0 Prompt

Create a Facebook Ads campaign dashboard with a data table. The table should have columns: Campaign Name, Status (with colored badge), Objective, Daily Budget, Impressions, Clicks, CTR, Spend, and ROAS. Add a date range picker above the table defaulting to 'Last 7 Days'. Show a summary row at the bottom with totals. Include skeleton loading rows and an error state that shows a retry button. Use placeholder data with 5-6 campaigns of varying performance.

Paste this in V0 chat

Pro tip: Ask V0 to include a currency formatter and number abbreviator in the component — Meta API returns spend in the account's currency as a string (e.g., '1234.56') and impression counts as integers that benefit from k/M formatting.

Expected result: A fully designed campaign dashboard renders in V0 with realistic placeholder data, a date range picker, skeleton loading state, and a sortable table structure ready to receive real Meta API data.

2

Create the Meta Marketing API Route

Create a Next.js API route at app/api/facebook-ads/campaigns/route.ts that fetches campaign data from the Meta Marketing API. The Meta Marketing API follows the Facebook Graph API structure: the base URL is https://graph.facebook.com/v19.0/, and you access your ad account's campaigns at /act_{AD_ACCOUNT_ID}/campaigns. The most important aspect of this route is fetching insights alongside campaign data using the fields parameter. Meta's API supports requesting nested data with dot notation: ?fields=name,status,objective,daily_budget,insights{impressions,clicks,spend,ctr,actions}. However, insights data for custom date ranges requires passing date_preset or time_range as a query parameter. Handle the response carefully: Meta returns a data array of campaign objects, each with an insights key that contains its own data array (because insights can be paginated). The API uses cursor-based pagination via a paging.cursors.after value — for most dashboards, the first page of results (default 25 campaigns) is sufficient, but for large ad accounts you may need to follow the paging.next URL. Always handle Meta API rate limiting. The Marketing API enforces rate limits based on your app's Business Use Case tier. When rate limited, the API returns a 400 with error code 17 or 80000-80099. Add retry-after handling and pass through appropriate HTTP status codes to your frontend so the UI can show a 'Rate limited — try again in X seconds' message rather than a generic error.

V0 Prompt

Create a Next.js API route at app/api/facebook-ads/campaigns/route.ts. It should read META_ACCESS_TOKEN and META_AD_ACCOUNT_ID from process.env (no NEXT_PUBLIC_ prefix). Fetch from https://graph.facebook.com/v19.0/act_{accountId}/campaigns with fields=name,status,objective,daily_budget,insights{impressions,clicks,spend,ctr} and a date_preset from the query string (default 'last_7d'). Return the campaigns array as JSON. Handle 400/429 Meta API errors with appropriate status codes.

Paste this in V0 chat

app/api/facebook-ads/campaigns/route.ts
1import { NextRequest, NextResponse } from 'next/server';
2
3const GRAPH_API_VERSION = 'v19.0';
4const GRAPH_BASE = `https://graph.facebook.com/${GRAPH_API_VERSION}`;
5
6export async function GET(req: NextRequest) {
7 const { searchParams } = new URL(req.url);
8 const datePreset = searchParams.get('date_preset') || 'last_7d';
9
10 const accessToken = process.env.META_ACCESS_TOKEN;
11 const adAccountId = process.env.META_AD_ACCOUNT_ID;
12
13 if (!accessToken || !adAccountId) {
14 return NextResponse.json(
15 { error: 'Meta credentials not configured' },
16 { status: 500 }
17 );
18 }
19
20 const fields = [
21 'name',
22 'status',
23 'objective',
24 'daily_budget',
25 'lifetime_budget',
26 `insights.date_preset(${datePreset}){impressions,clicks,spend,ctr,actions}`,
27 ].join(',');
28
29 const url = new URL(`${GRAPH_BASE}/act_${adAccountId}/campaigns`);
30 url.searchParams.set('fields', fields);
31 url.searchParams.set('access_token', accessToken);
32 url.searchParams.set('limit', '50');
33
34 try {
35 const res = await fetch(url.toString(), { cache: 'no-store' });
36 const json = await res.json();
37
38 if (!res.ok || json.error) {
39 const code = json.error?.code;
40 const status = code === 17 || (code >= 80000 && code <= 80099) ? 429 : 400;
41 return NextResponse.json(
42 { error: json.error?.message || 'Meta API error', code },
43 { status }
44 );
45 }
46
47 return NextResponse.json({ campaigns: json.data || [] });
48 } catch (err) {
49 return NextResponse.json(
50 { error: 'Failed to fetch from Meta API' },
51 { status: 500 }
52 );
53 }
54}

Pro tip: Use date_preset values like 'last_7d', 'last_30d', 'last_90d', 'this_month' — these are Meta's built-in presets and avoid the complexity of formatting custom time_range objects.

Expected result: GET /api/facebook-ads/campaigns?date_preset=last_7d returns a JSON object with a campaigns array containing real Meta API data including nested insights. The access token never appears in browser network requests.

3

Connect the V0 Dashboard to the API Route

Now wire the V0-generated campaign dashboard component to your Next.js API route. Since this is an internal dashboard (not a public-facing page), you can use a React component with useState and useEffect to fetch data on mount, or make the parent page a Server Component that fetches directly. For the date range picker, maintain the selected preset as React state and re-fetch when it changes. The API route accepts a date_preset query parameter, so you construct the fetch URL dynamically: fetch('/api/facebook-ads/campaigns?date_preset=' + selectedPreset). Process the Meta API response to normalize the data shape for your table. The insights field on each campaign is a nested object with a data array — you need to extract the first element for display: campaign.insights?.data?.[0]. The actions array inside insights contains conversion events (purchases, leads, etc.) — each action has action_type and value fields. To calculate ROAS, find the action where action_type is 'offsite_conversion.fb_pixel_purchase' and divide its value by the campaign's spend. Add a refresh button that re-fetches data and a last-updated timestamp so users know how recent the data is. Meta's Marketing API caches insights data, so refreshing more frequently than every few minutes won't yield new data during active campaigns. For error handling, distinguish between a rate limit error (show a countdown until retry is safe) and a credential error (show a configuration error message with instructions to check environment variables).

V0 Prompt

Update the campaign dashboard component to fetch data from /api/facebook-ads/campaigns with the selected date preset as a query parameter. Extract campaign metrics from response.campaigns, normalizing insights from campaign.insights?.data?.[0]. Calculate ROAS from spend and purchase action values. Show a loading skeleton during fetch, handle rate limit errors with a retry countdown, and add a 'Refresh' button with last-updated timestamp.

Paste this in V0 chat

app/components/CampaignDashboard.tsx
1'use client';
2
3import { useState, useEffect, useCallback } from 'react';
4
5interface CampaignInsights {
6 impressions: string;
7 clicks: string;
8 spend: string;
9 ctr: string;
10 actions?: Array<{ action_type: string; value: string }>;
11}
12
13interface Campaign {
14 id: string;
15 name: string;
16 status: string;
17 objective: string;
18 daily_budget?: string;
19 insights?: { data: CampaignInsights[] };
20}
21
22interface NormalizedCampaign extends Campaign {
23 metrics: CampaignInsights | null;
24 roas: number | null;
25}
26
27function normalizeCampaign(campaign: Campaign): NormalizedCampaign {
28 const metrics = campaign.insights?.data?.[0] ?? null;
29 const purchaseAction = metrics?.actions?.find(
30 (a) => a.action_type === 'offsite_conversion.fb_pixel_purchase'
31 );
32 const roas =
33 purchaseAction && metrics?.spend
34 ? parseFloat(purchaseAction.value) / parseFloat(metrics.spend)
35 : null;
36 return { ...campaign, metrics, roas };
37}
38
39export default function CampaignDashboard() {
40 const [campaigns, setCampaigns] = useState<NormalizedCampaign[]>([]);
41 const [loading, setLoading] = useState(true);
42 const [error, setError] = useState<string | null>(null);
43 const [datePreset, setDatePreset] = useState('last_7d');
44 const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
45
46 const fetchCampaigns = useCallback(async () => {
47 setLoading(true);
48 setError(null);
49 try {
50 const res = await fetch(`/api/facebook-ads/campaigns?date_preset=${datePreset}`);
51 const json = await res.json();
52 if (!res.ok) throw new Error(json.error || 'Failed to fetch');
53 setCampaigns((json.campaigns as Campaign[]).map(normalizeCampaign));
54 setLastUpdated(new Date());
55 } catch (e) {
56 setError(e instanceof Error ? e.message : 'Unknown error');
57 } finally {
58 setLoading(false);
59 }
60 }, [datePreset]);
61
62 useEffect(() => { fetchCampaigns(); }, [fetchCampaigns]);
63
64 // Render campaign table...
65 return <div>{/* table rendering */}</div>;
66}

Pro tip: Meta's insights API returns numeric values as strings (e.g., spend: '123.45') — always parse them with parseFloat() before arithmetic operations.

Expected result: The dashboard fetches live campaign data from your API route, displays it in the table, recalculates metrics when the date preset changes, and shows appropriate loading and error states.

4

Add Environment Variables in Vercel and Deploy

Meta Marketing API credentials consist of two values: a long-lived access token and your ad account ID. Both are sensitive and should be stored as server-only Vercel environment variables — no NEXT_PUBLIC_ prefix, since they only need to be available in your API routes. To generate a long-lived access token: go to developers.facebook.com → your app → Tools → Graph API Explorer. Select your app and the ads_read and ads_management permissions, generate a short-lived token, then exchange it for a long-lived token (valid 60 days) using the Token Debugger or the token exchange endpoint: https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id={APP_ID}&client_secret={APP_SECRET}&fb_exchange_token={SHORT_LIVED_TOKEN}. For production apps, consider using a System User token instead — System Users are non-human accounts in Meta Business Manager that generate tokens which never expire (until manually revoked) and are not tied to an individual employee's account. Go to Meta Business Manager → Business Settings → System Users to create one. In the Vercel Dashboard, navigate to your project → Settings → Environment Variables. Add META_ACCESS_TOKEN with your long-lived or system user token, and META_AD_ACCOUNT_ID with your account ID (without the act_ prefix — your API route adds that). Set both for Production and Preview environments. For Preview environments, consider using a test ad account ID so preview deployments don't affect real campaign data. After adding the variables, trigger a redeployment from the Vercel Dashboard or push a commit. Verify by visiting your deployed dashboard URL and confirming campaign data loads.

.env.local
1# .env.local never commit this file
2# Meta Marketing API credentials (server-only, no NEXT_PUBLIC_ prefix)
3META_ACCESS_TOKEN=EAABsbCS...
4META_AD_ACCOUNT_ID=1234567890

Pro tip: System User tokens in Meta Business Manager never expire and are safer for production than personal access tokens that require manual renewal every 60 days.

Expected result: Environment variables are set in Vercel. The deployed app fetches real Facebook Ads campaign data. The access token is only visible in Vercel's encrypted environment variables, never in browser network requests.

Common use cases

Ad Campaign Performance Dashboard

Build an internal dashboard that shows all active campaigns with impressions, clicks, CTR, spend, and ROAS in a sortable table. Marketing teams can check performance at a glance without logging into Meta Business Manager. The API route fetches campaign-level insights with a date range filter.

V0 Prompt

Create an ad campaigns dashboard with a data table showing campaign name, status, budget, impressions, clicks, CTR, spend, and ROAS. Add a date range picker at the top to filter the time period. Show a summary bar with total spend, total clicks, and average ROAS. Use color coding: green for campaigns performing above target ROAS, red for below.

Copy this prompt to try it in V0

Audience Size and Targeting Explorer

Display all saved audiences and custom audiences in your Meta ad account, showing estimated reach, audience type (custom, lookalike, saved), and last update time. Useful for auditing your audience library and spotting stale audiences that need refreshing.

V0 Prompt

Build an audience management page with cards for each Facebook audience. Each card should show the audience name, type (Custom Audience or Lookalike), estimated size with a range, and the date it was last updated. Add a search bar to filter audiences by name and a badge showing 'Needs Refresh' for audiences not updated in 90+ days.

Copy this prompt to try it in V0

Ad Creative Performance Comparison

Show individual ad creative performance side by side — image, headline, CTR, frequency, and cost per result. Helps creative teams identify winning formats and retire underperforming ads based on real data rather than intuition.

V0 Prompt

Create a creative performance comparison page with a grid of ad cards. Each card should show the ad name, a placeholder for the ad image, headline text, CTR percentage, frequency score, cost per result, and an overall performance badge (Top Performer, Average, Underperforming). Add a filter to show only video ads or image ads.

Copy this prompt to try it in V0

Troubleshooting

API route returns 400 with error message 'Invalid OAuth access token - Cannot parse access token'

Cause: The META_ACCESS_TOKEN environment variable is malformed — it may be missing characters, have extra whitespace, or have been truncated when pasting into Vercel.

Solution: Go to Vercel Dashboard → Settings → Environment Variables, delete the META_ACCESS_TOKEN entry, and re-paste the token carefully. Validate it first using the Meta Token Debugger at developers.facebook.com/tools/debug/accesstoken/.

API route returns 200 but campaigns array is empty even though campaigns exist in Meta Business Manager

Cause: The META_AD_ACCOUNT_ID is incorrect or the access token doesn't have permission to read that specific ad account. Meta silently returns an empty array for unauthorized accounts rather than an error.

Solution: Verify the ad account ID in Meta Business Manager → Business Settings → Ad Accounts. The ID should be a number without any prefix. Also check that your app (and its access token) has the ads_read permission for that specific ad account under Business Settings → Apps.

Campaign insights show null or undefined even though the campaigns fetch successfully

Cause: Campaigns with no spend in the selected date range return a campaign object without an insights key. The date preset may also be returning data outside the campaign's active period.

Solution: Add a null check when accessing insights data: const metrics = campaign.insights?.data?.[0] ?? null. Display a dash or 'No data' in the table for null metrics rather than crashing. Try switching to a date preset like 'last_30d' or 'lifetime' to confirm the campaign has any data at all.

typescript
1// Safe insights extraction
2const metrics = campaign.insights?.data?.[0] ?? null;
3const impressions = metrics ? parseInt(metrics.impressions, 10) : 0;
4const spend = metrics ? parseFloat(metrics.spend) : 0;

API returns error code 17 or error message 'User request limit reached'

Cause: Meta Marketing API rate limits are based on your app's API tier and the number of ad accounts being queried. Fetching insights for many campaigns with large date ranges is expensive and can exhaust your hourly quota.

Solution: Add response caching to your API route using Next.js caching (revalidate) or cache the response in memory/Upstash Redis. Reduce the number of fields requested and avoid fetching insights in the initial campaign list — load insights lazily when the user clicks a campaign for details. For RapidDev's team building high-volume dashboards, implementing a background sync job that pre-fetches data every hour avoids live rate limit issues entirely.

typescript
1// Add cache revalidation to reduce live API calls
2export const revalidate = 300; // Cache for 5 minutes
3
4// Or in fetch options:
5const res = await fetch(url.toString(), { next: { revalidate: 300 } });

Best practices

  • Use a Meta System User token instead of a personal access token for production — System User tokens don't expire and aren't tied to an individual employee's account
  • Never request more fields than your UI displays — Meta API calls are billed against your rate limit quota, so lean field selections keep you within limits longer
  • Cache insights responses in your API route for at least 5 minutes — Meta's API caches insights data internally, so polling faster than every few minutes returns the same data and wastes rate limit budget
  • Store only the ad account ID and access token in Vercel environment variables — never hardcode account IDs in V0-generated components where they could be exposed in client bundles
  • Use date_preset query parameters rather than custom time_range objects in your API route — presets are simpler and less error-prone than formatting date range strings
  • Handle the case where campaign.insights is undefined — campaigns with zero spend in the selected period return no insights key at all, and unguarded access will crash your dashboard
  • Separate your campaign list fetch (fast, no insights) from your insights fetch (slow) so the dashboard renders immediately and enriches progressively

Alternatives

Frequently asked questions

Can I use Facebook Ads API with V0 on Vercel's free Hobby plan?

Yes. The Meta Marketing API is a free external service — you only pay for the ads themselves, not the API. Vercel's Hobby plan supports all the Next.js API routes needed for this integration. The only limit to be aware of is Vercel's serverless function execution time (10 seconds on Hobby), which is usually sufficient for Meta API calls unless you're fetching insights for hundreds of campaigns simultaneously.

How do I get a Meta access token that doesn't expire?

Create a System User in Meta Business Manager under Business Settings → System Users. Assign the System User to your ad account with Standard or Admin access, then generate a token directly from the System User. System User tokens do not have a 60-day expiration and remain valid until you manually revoke them — they're designed specifically for server-to-server integrations like this one.

What's the difference between Facebook Ads and Google Ads for V0 integrations?

Both use a similar API route proxy pattern in Next.js. The key difference is the data model: Facebook Ads organizes campaigns → ad sets → ads with audience targeting at the ad set level, while Google Ads uses campaigns → ad groups → ads with keywords driving targeting. Facebook's API returns insights nested within campaign objects; Google Ads uses a separate reporting service with its own query language (GAQL).

Can I create and manage ads through the API, or only read performance data?

The Meta Marketing API supports full CRUD operations — you can create campaigns, ad sets, ads, and audiences programmatically. However, creating ads requires the ads_management permission scope in addition to ads_read. Ad creation also requires media (images/videos) uploaded to Meta's ad library. Most V0 dashboard use cases only need ads_read for performance reporting.

Why do my campaign insights show different numbers than Meta Business Manager?

Meta applies attribution windows to conversion data, and the default attribution window in the API may differ from your Business Manager report settings. Check the attribution_settings on your campaign and ensure your API request uses matching attribution_windows parameters. Also note that Meta's reported data can change retroactively for up to 28 days as conversions are attributed.

How do I filter the dashboard to show only active campaigns?

Add effective_status as a field in your API request and include filtering in the query: &filtering=[{"field":"effective_status","operator":"IN","value":["ACTIVE"]}]. You can also filter client-side after fetching all campaigns. The effective_status field is more reliable than status for filtering because it reflects the actual delivery state including parent-level pauses.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.