# How to Integrate Semrush with V0

- Tool: V0
- Difficulty: Intermediate
- Time required: 35 minutes
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

**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.

### 2. 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.

```
// app/api/semrush/overview/route.ts
import { NextRequest, NextResponse } from 'next/server';

const SEMRUSH_API_BASE = 'https://api.semrush.com/';

function parseSemrushResponse(text: string): Record<string, string>[] {
  const lines = text.trim().split('\n');
  if (lines.length < 2) return [];

  const headers = lines[0].split(';');
  return lines.slice(1).map((line) => {
    const values = line.split(';');
    return headers.reduce((obj, header, i) => {
      obj[header.trim()] = (values[i] || '').trim();
      return obj;
    }, {} as Record<string, string>);
  });
}

export async function GET(request: NextRequest) {
  const apiKey = process.env.SEMRUSH_API_KEY;

  if (!apiKey) {
    return NextResponse.json({ error: 'SEMrush API key not configured' }, { status: 500 });
  }

  const { searchParams } = new URL(request.url);
  const domain = searchParams.get('domain');
  const database = searchParams.get('db') || 'us';

  if (!domain) {
    return NextResponse.json({ error: 'domain parameter is required' }, { status: 400 });
  }

  try {
    // Fetch domain overview (10 API units)
    const overviewParams = new URLSearchParams({
      type: 'domain_ranks',
      key: apiKey,
      domain,
      database,
      export_columns: 'Dn,Rk,Or,Ot,Oc,Ad,At,Ac',
    });

    const overviewRes = await fetch(
      `${SEMRUSH_API_BASE}?${overviewParams.toString()}`,
      { next: { revalidate: 3600 } } // Cache 1 hour
    );

    if (!overviewRes.ok) {
      throw new Error(`SEMrush API error: ${overviewRes.status}`);
    }

    const overviewText = await overviewRes.text();

    // Check for SEMrush error response
    if (overviewText.startsWith('ERROR')) {
      const errorCode = overviewText.split(' ')[1];
      if (errorCode === '50') {
        return NextResponse.json(
          { error: 'SEMrush API units exhausted' },
          { status: 429 }
        );
      }
      throw new Error(`SEMrush error: ${overviewText}`);
    }

    const overviewRows = parseSemrushResponse(overviewText);
    const overview = overviewRows[0] || {};

    // Fetch top organic keywords (10 units per 10 rows)
    const keywordsParams = new URLSearchParams({
      type: 'domain_organic',
      key: apiKey,
      domain,
      database,
      display_limit: '20',
      export_columns: 'Ph,Po,Pp,Pd,Nq,Cp,Ur,Tr,Tl,Kd',
      display_sort: 'tr_desc',
    });

    const keywordsRes = await fetch(
      `${SEMRUSH_API_BASE}?${keywordsParams.toString()}`,
      { next: { revalidate: 3600 } }
    );
    const keywordsText = await keywordsRes.text();
    const keywords = keywordsText.startsWith('ERROR')
      ? []
      : parseSemrushResponse(keywordsText).map((row) => ({
          keyword: row['Keyword'] || row['Ph'],
          position: parseInt(row['Position'] || row['Po'] || '0'),
          searchVolume: parseInt(row['Search Volume'] || row['Nq'] || '0'),
          traffic: parseFloat(row['Traffic'] || row['Tr'] || '0'),
          cpc: parseFloat(row['CPC'] || row['Cp'] || '0'),
          url: row['URL'] || row['Ur'],
        }));

    return NextResponse.json({
      domain,
      summary: {
        organicKeywords: parseInt(overview['Organic Keywords'] || overview['Or'] || '0'),
        organicTraffic: parseInt(overview['Organic Traffic'] || overview['Ot'] || '0'),
        paidKeywords: parseInt(overview['Paid Keywords'] || overview['Ad'] || '0'),
        paidTraffic: parseInt(overview['Paid Traffic'] || overview['At'] || '0'),
        authorityScore: parseInt(overview['Authority Score'] || overview['Rk'] || '0'),
      },
      keywords,
    });
  } catch (error) {
    console.error('SEMrush API error:', error);
    return NextResponse.json(
      { error: 'Failed to fetch SEMrush data' },
      { status: 500 }
    );
  }
}
```

**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.

### 3. 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.

```
// app/api/semrush/credits/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  const apiKey = process.env.SEMRUSH_API_KEY;

  if (!apiKey) {
    return NextResponse.json({ error: 'API key not configured' }, { status: 500 });
  }

  try {
    const res = await fetch(
      `https://api.semrush.com/?type=units_stat&key=${apiKey}`,
      { next: { revalidate: 300 } } // Cache 5 minutes
    );
    const text = await res.text();

    if (text.startsWith('ERROR')) {
      return NextResponse.json({ error: 'Failed to fetch unit balance' }, { status: 500 });
    }

    const units = parseInt(text.trim(), 10);
    return NextResponse.json({ units, low: units < 1000 });
  } catch (error) {
    console.error('SEMrush credits error:', error);
    return NextResponse.json({ error: 'Request failed' }, { status: 500 });
  }
}
```

**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.

### 4. 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).

**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.

## 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

## 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.

Prompt example:

```
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.
```

### 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.

Prompt example:

```
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.
```

### 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.

Prompt example:

```
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.
```

## 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.

```
if (overviewText.includes('ERROR 120')) {
  return NextResponse.json({ error: 'No SEMrush data found for this domain', code: 'NO_DATA' }, { status: 404 });
}
```

### 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.

```
// Log raw response for debugging:
console.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.

```
// Auto-detect delimiter:
const firstLine = text.split('\n')[0];
const delimiter = firstLine.includes(';') ? ';' : '|';
const headers = firstLine.split(delimiter);
```

## 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.

---

Source: https://www.rapidevelopers.com/v0-integrations/semrush
© RapidDev — https://www.rapidevelopers.com/v0-integrations/semrush
