To integrate SEMrush with V0 by Vercel, generate an SEO reporting dashboard with V0, create Next.js API routes that fetch domain analytics and keyword data from the SEMrush API, store your API key in Vercel environment variables, and deploy. Your dashboard will display organic traffic trends, top keywords, backlink counts, and domain authority scores.
Build SEO Reporting Dashboards with SEMrush API and V0
SEMrush is the most comprehensive SEO toolkit in the market — it tracks keyword rankings for 142 geographic databases, maintains backlink data for over 43 trillion links, and runs site audits across 130 technical SEO checks. Its API makes this data programmable, enabling custom reporting dashboards that present exactly the metrics your clients or team care about without the noise of SEMrush's full interface. V0 generates the dashboard UI rapidly; the integration work is connecting that UI to SEMrush's API endpoints through secure server-side proxy routes.
A critical implementation detail: SEMrush's API does not return standard JSON by default. Most endpoints return pipe-delimited (|) CSV data where the first row is a header and subsequent rows are data. Your API routes need to parse this format into JSON before returning data to React components. Some newer SEMrush endpoints support JSON responses when requested — check the specific endpoint documentation. This parsing step is typically handled in the API route so your React components receive clean typed objects.
The SEMrush API uses a unit-based credit system. Each API request costs a number of units depending on the endpoint and the database size queried. Domain overview calls are relatively cheap (10 units); keyword data calls are more expensive (10-40 units per row returned). Monitor your unit balance in the SEMrush API usage dashboard and implement caching in your API routes to avoid burning units on repeated identical requests.
Integration method
SEMrush integrates with V0-generated Next.js apps through server-side API routes that proxy requests to the SEMrush REST API using your API key. The key is stored as a server-only Vercel environment variable and never exposed to the browser. Your V0-generated dashboard components fetch SEO data from these API routes and render domain analytics, keyword tables, and competitive metrics.
Prerequisites
- A SEMrush account with API access enabled — the API is available on Pro ($119.95/mo), Guru ($229.95/mo), and Business plans, plus dedicated API packages
- Your SEMrush API key — found at semrush.com under your Profile → Subscription Info → API → Copy Key
- Understanding of SEMrush's unit-based pricing model — each API call consumes units from your monthly allocation
- A V0 account at v0.dev to generate the dashboard UI
- A Vercel account to deploy the Next.js app and store the API key
Step-by-step guide
Generate the SEO Dashboard UI with V0
Generate the SEO Dashboard UI with V0
Open V0 at v0.dev and describe your SEO reporting dashboard. SEMrush data is dense and numeric — think carefully about which metrics matter most to your audience and prompt V0 accordingly. Effective V0 prompts for SEO dashboards specify exact metric names (domain authority, organic keywords count, estimated monthly traffic, referring domains) and visualization types (line chart for traffic trends, tables for keyword lists, bar charts for keyword distribution). V0 will generate React components with shadcn/ui's chart primitives (which use Recharts under the hood) and data table components. The generated mock data structure will show you how V0 expects chart data to be formatted — typically arrays like [{date: '2026-01', value: 12000}, ...] for line charts. Note these expected data shapes before building your API route so you can transform SEMrush's response to match. V0 will also likely create a domain search input — confirm it calls a fetch to /api/semrush/overview on submit. Push to GitHub via V0's Git panel once you're satisfied with the design.
Build an SEO analytics dashboard with a domain search bar at the top, four metric cards showing Organic Keywords (with trend arrow), Monthly Traffic, Domain Score (0-100 gauge), and Referring Domains. Include a line chart showing 6-month organic traffic trend. Below, show a 'Top Keywords' table with columns: Keyword, Position, Volume, Traffic, and CPC. Add a 'Top Competitors' section with a table showing competitor domains, their keyword count, and traffic estimate. Data from /api/semrush/overview. Clean professional design, white background, blue accents.
Paste this in V0 chat
Pro tip: Ask V0 to use server-side data fetching in a Server Component rather than client-side useEffect if your dashboard only shows static data without interactive filtering. Server Components are simpler and don't expose loading state complexity for basic report pages.
Expected result: A polished SEO analytics dashboard renders in V0's preview with metric cards, a traffic trend chart, keyword table, and competitor section — all using realistic sample data shaped to match SEMrush API output.
Create the SEMrush API Route with Response Parsing
Create the SEMrush API Route with Response Parsing
Create the API route that calls SEMrush and transforms its response. The SEMrush API base URL is https://api.semrush.com/ and requests include your API key as the key query parameter. The main domain overview endpoint is /api/semrush/overview in your Next.js app, which calls SEMrush's domain_ranks report type: https://api.semrush.com/?type=domain_ranks&key={key}&export_columns=Or,Ot,Oc,Ad,Ak&domain={domain}&database=us. The response is pipe-delimited text (not JSON). The first line is the header row with column abbreviations (Or = organic keywords, Ot = organic traffic, Oc = organic traffic cost, etc.). Your parsing function splits on newlines and then on the pipe character. SEMrush uses specific database codes for countries: 'us' for US, 'uk' for UK, 'ca' for Canada. Create a parseCSV utility that takes the raw text and returns an array of objects. Cache API responses for at least one hour since SEMrush data is updated daily at best. One API key unit consideration: the domain_ranks report costs 10 units per request. For keyword-level data, the phrase_organic report costs 10 units per 10 rows returned.
1// app/api/semrush/overview/route.ts2import { NextRequest, NextResponse } from 'next/server';34const SEMRUSH_API_BASE = 'https://api.semrush.com/';56function parseSemrushResponse(text: string): Record<string, string>[] {7 const lines = text.trim().split('\n');8 if (lines.length < 2) return [];910 const headers = lines[0].split(';');11 return lines.slice(1).map((line) => {12 const values = line.split(';');13 return headers.reduce((obj, header, i) => {14 obj[header.trim()] = (values[i] || '').trim();15 return obj;16 }, {} as Record<string, string>);17 });18}1920export async function GET(request: NextRequest) {21 const apiKey = process.env.SEMRUSH_API_KEY;2223 if (!apiKey) {24 return NextResponse.json({ error: 'SEMrush API key not configured' }, { status: 500 });25 }2627 const { searchParams } = new URL(request.url);28 const domain = searchParams.get('domain');29 const database = searchParams.get('db') || 'us';3031 if (!domain) {32 return NextResponse.json({ error: 'domain parameter is required' }, { status: 400 });33 }3435 try {36 // Fetch domain overview (10 API units)37 const overviewParams = new URLSearchParams({38 type: 'domain_ranks',39 key: apiKey,40 domain,41 database,42 export_columns: 'Dn,Rk,Or,Ot,Oc,Ad,At,Ac',43 });4445 const overviewRes = await fetch(46 `${SEMRUSH_API_BASE}?${overviewParams.toString()}`,47 { next: { revalidate: 3600 } } // Cache 1 hour48 );4950 if (!overviewRes.ok) {51 throw new Error(`SEMrush API error: ${overviewRes.status}`);52 }5354 const overviewText = await overviewRes.text();5556 // Check for SEMrush error response57 if (overviewText.startsWith('ERROR')) {58 const errorCode = overviewText.split(' ')[1];59 if (errorCode === '50') {60 return NextResponse.json(61 { error: 'SEMrush API units exhausted' },62 { status: 429 }63 );64 }65 throw new Error(`SEMrush error: ${overviewText}`);66 }6768 const overviewRows = parseSemrushResponse(overviewText);69 const overview = overviewRows[0] || {};7071 // Fetch top organic keywords (10 units per 10 rows)72 const keywordsParams = new URLSearchParams({73 type: 'domain_organic',74 key: apiKey,75 domain,76 database,77 display_limit: '20',78 export_columns: 'Ph,Po,Pp,Pd,Nq,Cp,Ur,Tr,Tl,Kd',79 display_sort: 'tr_desc',80 });8182 const keywordsRes = await fetch(83 `${SEMRUSH_API_BASE}?${keywordsParams.toString()}`,84 { next: { revalidate: 3600 } }85 );86 const keywordsText = await keywordsRes.text();87 const keywords = keywordsText.startsWith('ERROR')88 ? []89 : parseSemrushResponse(keywordsText).map((row) => ({90 keyword: row['Keyword'] || row['Ph'],91 position: parseInt(row['Position'] || row['Po'] || '0'),92 searchVolume: parseInt(row['Search Volume'] || row['Nq'] || '0'),93 traffic: parseFloat(row['Traffic'] || row['Tr'] || '0'),94 cpc: parseFloat(row['CPC'] || row['Cp'] || '0'),95 url: row['URL'] || row['Ur'],96 }));9798 return NextResponse.json({99 domain,100 summary: {101 organicKeywords: parseInt(overview['Organic Keywords'] || overview['Or'] || '0'),102 organicTraffic: parseInt(overview['Organic Traffic'] || overview['Ot'] || '0'),103 paidKeywords: parseInt(overview['Paid Keywords'] || overview['Ad'] || '0'),104 paidTraffic: parseInt(overview['Paid Traffic'] || overview['At'] || '0'),105 authorityScore: parseInt(overview['Authority Score'] || overview['Rk'] || '0'),106 },107 keywords,108 });109 } catch (error) {110 console.error('SEMrush API error:', error);111 return NextResponse.json(112 { error: 'Failed to fetch SEMrush data' },113 { status: 500 }114 );115 }116}Pro tip: SEMrush responses use semicolons (;) as the delimiter for most report types, not pipes. Always log the first few characters of the raw response text when debugging to confirm the actual delimiter your endpoint uses, as it can differ between report types.
Expected result: Calling GET /api/semrush/overview?domain=example.com returns structured JSON with domain summary stats (organic keywords, traffic, authority score) and a keywords array with position and search volume data.
Handle SEMrush Error Responses and API Unit Monitoring
Handle SEMrush Error Responses and API Unit Monitoring
SEMrush has a distinctive error format: instead of returning HTTP 4xx or 5xx status codes for most errors, it returns HTTP 200 with an error message string starting with 'ERROR' in the response body. Your API routes must check for this pattern before attempting to parse the response as CSV data. The most important error codes are: ERROR 50 (API units exhausted — your monthly balance is depleted), ERROR 40 (incorrect request format or missing parameter), ERROR 120 (nothing found for the requested query — domain has no data in SEMrush's database). Add this error detection to your parseSemrushResponse function. Additionally, implement a /api/semrush/credits route that calls SEMrush's unit balance endpoint so your dashboard can display remaining API credits. The balance endpoint is: https://api.semrush.com/?type=units_stat&key={apiKey}. This returns a simple number representing your remaining units. Display this in your dashboard's header or settings so you know when you're approaching your limit. For agency tools serving multiple clients, implement aggressive caching per domain and consider storing cached results in a database rather than relying solely on Next.js fetch caching.
Add an API credits indicator to the dashboard header showing remaining SEMrush units, fetched from /api/semrush/credits. Display it as 'SEMrush: {count} units remaining' with a warning color when below 1000 units. Also add an error banner that appears when the API returns an error, with specific messages for 'units exhausted' and 'domain not found' cases.
Paste this in V0 chat
1// app/api/semrush/credits/route.ts2import { NextResponse } from 'next/server';34export async function GET() {5 const apiKey = process.env.SEMRUSH_API_KEY;67 if (!apiKey) {8 return NextResponse.json({ error: 'API key not configured' }, { status: 500 });9 }1011 try {12 const res = await fetch(13 `https://api.semrush.com/?type=units_stat&key=${apiKey}`,14 { next: { revalidate: 300 } } // Cache 5 minutes15 );16 const text = await res.text();1718 if (text.startsWith('ERROR')) {19 return NextResponse.json({ error: 'Failed to fetch unit balance' }, { status: 500 });20 }2122 const units = parseInt(text.trim(), 10);23 return NextResponse.json({ units, low: units < 1000 });24 } catch (error) {25 console.error('SEMrush credits error:', error);26 return NextResponse.json({ error: 'Request failed' }, { status: 500 });27 }28}Pro tip: For multi-client agency dashboards, store the last successful SEMrush response for each domain in a database (Supabase, PostgreSQL via Neon) with a timestamp. Serve the cached database result to clients while refreshing in the background, so slow or failing SEMrush API calls don't break the user experience.
Expected result: GET /api/semrush/credits returns the remaining API unit balance. The dashboard displays a unit counter and shows appropriate warnings when limits approach, preventing surprise outages.
Add API Key to Vercel and Deploy
Add API Key to Vercel and Deploy
Push all changes to GitHub and configure the SEMrush API key in Vercel. Open the Vercel Dashboard, navigate to your project, click Settings, then Environment Variables. Add SEMRUSH_API_KEY with your SEMrush API key (found at semrush.com → Profile → Subscription Info → API → the key string). This variable must not have the NEXT_PUBLIC_ prefix — the API key is server-side only. Set it for Production, Preview, and Development environments. After saving, trigger a deployment from the Deployments tab or by pushing a commit. Once deployed, test the dashboard by entering your own domain and a competitor's domain. The SEMrush API's US database has the best coverage — start by testing with well-known domains (like competitor.com or a major brand) to confirm the API is returning data before testing smaller domains. Monitor your API unit consumption in your SEMrush account under Profile → API. If you're seeing unexpected unit depletion, check the Vercel Function Logs for unexpected duplicate calls and ensure your caching is working (next: { revalidate: 3600 } should prevent repeated identical requests during the same hour).
Pro tip: Test your SEMrush API key and quota separately before building the full dashboard. A quick curl command to https://api.semrush.com/?type=units_stat&key=YOUR_KEY will return your remaining unit balance and confirm the key is valid.
Expected result: Your Vercel deployment successfully builds and serves the SEMrush dashboard. Domain searches return real SEO data, API unit consumption is tracked, and credentials are never visible to users.
Common use cases
Client SEO Reporting Dashboard
A white-label SEO dashboard for agency clients showing their domain's key metrics: organic traffic trend, top 10 keywords by traffic, domain authority score, and number of referring domains. The dashboard auto-refreshes weekly and can be shared with a view-only link.
Create a client SEO report dashboard with the client's domain displayed prominently, four KPI cards (Organic Keywords, Monthly Traffic, Domain Score, Referring Domains), a line chart showing monthly organic traffic trend over 12 months, a table of top 10 keywords with rank, search volume, and traffic percentage columns, and a backlinks summary section. Data from /api/semrush/overview. Use a professional clean design with the client's primary brand color.
Copy this prompt to try it in V0
Keyword Rank Tracker
An internal tool that monitors keyword positions for a set of target keywords. Shows current position, position change from last week, search volume, and estimated traffic for each keyword. Color-codes position changes green/red for quick scanning.
Build a keyword rank tracking table with columns: Keyword, Current Position, Position Change (with green/red arrows), Search Volume, Estimated Traffic, and URL ranking. Include a filter bar to search keywords and a sort by position. Show a summary card at the top with average position and total tracked keywords. Data from /api/semrush/rankings. Professional dark theme.
Copy this prompt to try it in V0
Competitor SEO Gap Analysis
A tool that compares your domain against three competitors on the key SEO metrics: total keywords, estimated traffic, domain score, and backlinks. Highlights where competitors outperform your domain and suggests priority improvement areas.
Design a competitor comparison page with a domain input for your site and three competitor domain inputs. Show a comparison table with rows for: Total Organic Keywords, Monthly Traffic Estimate, Domain Authority, Total Backlinks. Color-code cells where competitors outperform your domain in red. Include a 'Keyword Gap' section showing top keywords competitors rank for but you don't. Fetch from /api/semrush/compare.
Copy this prompt to try it in V0
Troubleshooting
API route returns error or empty data for a domain that clearly exists
Cause: SEMrush may not have indexed the requested domain in their database, or the domain has very low search visibility. Smaller or newer domains may genuinely return 'nothing found' (ERROR 120). Also check that you're using the correct database code for your target country.
Solution: Handle ERROR 120 as a 'no data found' case rather than an error. Display 'No SEMrush data found for this domain' as a user-friendly message. Test with a well-known domain first to confirm your integration works. Check the database parameter — US data (database=us) has the best coverage.
1if (overviewText.includes('ERROR 120')) {2 return NextResponse.json({ error: 'No SEMrush data found for this domain', code: 'NO_DATA' }, { status: 404 });3}Parsed keyword data has wrong values or undefined fields
Cause: SEMrush uses column abbreviations (Ph, Po, Nq) in the response header, not full column names. The mapping between abbreviation and full name depends on the export_columns parameter you specified in the request.
Solution: Log the raw response text to see the exact column header abbreviations SEMrush returns for your specific request. Update your field mapping to match the actual abbreviations. Common ones: Ph = keyword phrase, Po = position, Nq = search volume, Cp = CPC, Tr = traffic, Ur = URL.
1// Log raw response for debugging:2console.log('SEMrush raw response:', overviewText.substring(0, 500));SEMrush API returns ERROR 50 - API units exhausted
Cause: Your SEMrush API unit balance has been depleted. Each API call consumes units, and without caching, repeated dashboard loads can quickly exhaust monthly allocations, especially during development.
Solution: Add caching to all API routes (next: { revalidate: 3600 }) to limit calls to once per hour per domain. In development, use a static mock JSON response to test UI without consuming real API units. Units reset monthly — check your billing date to know when your balance refreshes.
SEMrush API response delimiter is wrong — parsing returns single-column rows
Cause: Different SEMrush report types use different delimiters. Some use semicolons (;), others use pipes (|). The parseSemrushResponse function must use the correct delimiter for each report type.
Solution: Inspect the raw response text by logging the first line and checking what character separates columns. Update the split character in your parsing function to match. The domain_ranks report type uses semicolons; some older endpoints use pipes.
1// Auto-detect delimiter:2const firstLine = text.split('\n')[0];3const delimiter = firstLine.includes(';') ? ';' : '|';4const headers = firstLine.split(delimiter);Best practices
- Cache SEMrush API responses for at least 1 hour using Next.js fetch caching (next: { revalidate: 3600 }) — SEMrush data updates daily and aggressive caching dramatically reduces unit consumption
- Always check the response text for SEMrush's ERROR pattern before parsing as CSV — SEMrush returns HTTP 200 even for error conditions
- Use the export_columns parameter to request only the columns your dashboard needs — this doesn't reduce unit cost but reduces response size and parsing complexity
- Implement the /api/semrush/credits endpoint and display the remaining unit balance in your dashboard to prevent unexpected outages
- In development, use static mock data to build and test UI components without consuming real SEMrush API units
- Use the correct country database code for your market — the US database (database=us) has the most comprehensive coverage; smaller markets like Australia (au) have less data
- Never hardcode your SEMRUSH_API_KEY in client-side code — all SEMrush calls must go through server-side API routes using process.env
Alternatives
Use SpyFu instead of SEMrush if your primary need is competitor PPC and ad intelligence specifically — SpyFu specializes in ad spend tracking while SEMrush is a broader platform.
Choose Ahrefs over SEMrush if backlink analysis and link-building research are your primary SEO focus — Ahrefs has the largest and freshest backlink database.
Use Google Search Console instead of SEMrush if you only need your own domain's real Google search data — it's free, accurate (not estimated), and available via OAuth API.
Frequently asked questions
What is the SEMrush API unit system and how many units does a typical dashboard use?
SEMrush API calls consume units from your monthly balance. Domain overview requests cost around 10 units each; keyword data requests cost 10 units per 10 rows returned. A dashboard that loads data for one domain (overview + 20 keywords + competitor data) consumes approximately 40-60 units. With aggressive 1-hour caching, a dashboard with 100 daily active users analyzing 10 unique domains would use roughly 600 units per day. Pro plans start with 10,000 units/month.
Why does SEMrush return HTTP 200 for errors instead of 4xx status codes?
This is a legacy API design choice. SEMrush's API predates modern REST conventions and uses HTTP 200 with an 'ERROR {code} {message}' text response body for most errors. Your code must check whether the response text starts with 'ERROR' before attempting to parse it as CSV data. This affects every SEMrush API endpoint, not just specific ones.
Can I use the SEMrush API on the free plan?
No. SEMrush's API requires a paid subscription (Pro at minimum). The free trial gives limited access but not full API access. If you need API access for a limited project, SEMrush sells standalone API packages starting at $200 for 50,000 units without a full SEMrush subscription.
How do I get backlink data from the SEMrush API?
Backlink data uses a different base URL: https://api.semrush.com/analytics/v1/ with the type parameter set to 'backlinks_overview' and the 'target' parameter instead of 'domain'. The response format is the same semicolon-delimited CSV. Include export_columns=ascore,total,domains_num,urls_num to get authority score, total backlinks, referring domains, and referring URLs.
Can I build a client-facing SEO reporting tool with the SEMrush API?
Technically yes, but check SEMrush's API terms of service for redistribution restrictions. SEMrush generally allows API data in custom internal tools and client reports. Building a competing SEO tool or reselling raw SEMrush data as your product's core offering may violate terms. For white-label agency reporting dashboards, this is typically within acceptable use — but verify with SEMrush's partnership team for commercial tools.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation