To integrate SEMrush with Lovable, create a Supabase Edge Function that proxies requests to the SEMrush API using your API key stored in Cloud Secrets. The Edge Function fetches domain analytics, keyword overview data, and organic research results. SEMrush uses a credit-based API model, so the Edge Function should validate parameters and cache responses to minimize credit consumption.
Build a Keyword Research and Competitive Analysis Dashboard with SEMrush and Lovable
SEMrush is the most widely used SEO platform for keyword research and competitive analysis. Its database covers 25+ billion keywords across 142 countries, tracking not just organic rankings but also paid search (PPC) data, making it uniquely useful for teams running both SEO and Google Ads campaigns simultaneously. Building a custom Lovable dashboard powered by SEMrush data lets you surface competitive intelligence in exactly the format your team needs — without navigating SEMrush's own interface or relying on limited CSV exports.
The SEMrush API uses a credit-based billing model. Each API call to different endpoint types costs a different number of credits per row returned. Domain analytics calls (like domain overview) tend to be lower cost, while comprehensive organic research reports cost more. Understanding the credit costs of each endpoint before building your dashboard is essential for staying within your plan's allocation. The free SEMrush plan includes 10 API units per day — enough for testing, but production dashboards require a paid plan with API access.
The integration pattern for Lovable is the standard edge function proxy: your React frontend calls a Supabase Edge Function, which adds the API key from Cloud Secrets and forwards the request to SEMrush's API. This architecture is required because the SEMrush API key must never appear in browser requests — any user could inspect network traffic and steal it. Lovable's security infrastructure blocks approximately 1,200 hardcoded API keys per day, and properly storing credentials in Cloud Secrets is the correct path.
Integration method
SEMrush does not have a native Lovable connector. All API calls must be proxied through a Supabase Edge Function that adds your API key from Cloud Secrets. The Edge Function calls SEMrush's REST API endpoints for domain analytics, keyword data, and competitive research, then returns structured results to your Lovable React frontend.
Prerequisites
- A Lovable project with Lovable Cloud enabled
- A SEMrush account with API access (available on Pro plan and above; limited free trial available)
- Your SEMrush API key from semrush.com/api-analytics
- Understanding of SEMrush's credit-based API pricing before building production dashboards
- A target domain or set of keywords to test the integration
Step-by-step guide
Get your SEMrush API key
Get your SEMrush API key
Log in to your SEMrush account at semrush.com and navigate to your account profile settings. Look for the API section or go directly to semrush.com/api-analytics. Your API key is displayed on this page as a long alphanumeric string. If you are on the free plan, you will have access to 10 API units per day, which is enough for testing. For a production dashboard, you will need a Pro plan ($129.95/month) or higher, which includes a larger API unit allocation. Copy your API key and do not paste it into Lovable's chat interface — instead you will store it in Cloud Secrets in the next step. SEMrush also displays your current API unit balance on this page, which you should monitor when building your dashboard to avoid hitting limits unexpectedly. The API key is a 32-character hex string unique to your account.
Pro tip: SEMrush API units are shared across your whole account. If multiple team members use the SEMrush interface while your Lovable dashboard is also making API calls, you may exhaust units faster than expected.
Expected result: You have your SEMrush API key copied and ready to store securely.
Store your API key in Cloud Secrets
Store your API key in Cloud Secrets
In your Lovable project, click the plus (+) icon next to the preview window to open the side panels. Select the Cloud tab. Scroll down to the Secrets section and click Add Secret. In the Name field, type SEMRUSH_API_KEY exactly — this is case-sensitive and must match what your Edge Function will use. Paste your SEMrush API key into the Value field. Click Save. The key is now encrypted and stored in the server-side environment. It will only be accessible from Edge Functions via Deno.env.get('SEMRUSH_API_KEY') — it will never appear in browser network requests, JavaScript bundles, or any client-side code. This is Lovable's secure secrets architecture, which also prevents the key from being committed to your GitHub repository if you have GitHub sync enabled.
Pro tip: If you accidentally paste the API key somewhere visible (like the chat or a code file), rotate the key immediately in your SEMrush account settings and update the secret in Cloud Secrets.
Expected result: SEMRUSH_API_KEY appears in the Cloud Secrets panel with its value masked.
Create the SEMrush proxy Edge Function
Create the SEMrush proxy Edge Function
Prompt Lovable to create an Edge Function that accepts requests from your frontend, reads the SEMRUSH_API_KEY from secrets, and proxies calls to the SEMrush API. The SEMrush API is a query-string-based REST API. Most endpoints accept parameters like type (the report type), domain or phrase, database (the regional database, e.g. 'us' for United States), export_columns (which data columns to return), and display_limit (max rows). The function constructs the SEMrush API URL at https://api.semrush.com/ with all parameters including the key, makes the fetch request, and returns the CSV or JSON response (SEMrush returns CSV by default; you can request JSON in some endpoints). The Edge Function should parse the CSV response into JSON for easier consumption by the frontend. Include CORS headers for all responses.
Create a Supabase Edge Function at supabase/functions/semrush-proxy/index.ts that accepts GET requests with query parameters: type (the SEMrush report type like 'domain_organic'), target (domain or phrase), database (default 'us'), and optionally display_limit and export_columns. Proxy the request to https://api.semrush.com/ adding the SEMRUSH_API_KEY secret as the 'key' parameter. Parse the CSV response into JSON and return it with CORS headers.
Paste this in Lovable chat
1import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'23const corsHeaders = {4 'Access-Control-Allow-Origin': '*',5 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',6}78function csvToJson(csv: string): Record<string, string>[] {9 const lines = csv.trim().split('\n')10 if (lines.length < 2) return []11 const headers = lines[0].split(';')12 return lines.slice(1).map(line => {13 const values = line.split(';')14 return Object.fromEntries(headers.map((h, i) => [h.trim(), values[i]?.trim() ?? '']))15 })16}1718serve(async (req) => {19 if (req.method === 'OPTIONS') {20 return new Response('ok', { headers: corsHeaders })21 }2223 try {24 const apiKey = Deno.env.get('SEMRUSH_API_KEY')25 if (!apiKey) {26 return new Response(27 JSON.stringify({ error: 'SEMRUSH_API_KEY not configured' }),28 { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }29 )30 }3132 const url = new URL(req.url)33 const type = url.searchParams.get('type')34 const target = url.searchParams.get('target')35 const database = url.searchParams.get('database') || 'us'36 const displayLimit = url.searchParams.get('display_limit') || '10'37 const exportColumns = url.searchParams.get('export_columns') || ''3839 if (!type || !target) {40 return new Response(41 JSON.stringify({ error: 'type and target parameters are required' }),42 { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }43 )44 }4546 const semrushUrl = new URL('https://api.semrush.com/')47 semrushUrl.searchParams.set('type', type)48 semrushUrl.searchParams.set('key', apiKey)49 semrushUrl.searchParams.set('database', database)50 semrushUrl.searchParams.set('display_limit', displayLimit)51 // domain vs phrase endpoint uses different param name52 if (type.startsWith('phrase_')) {53 semrushUrl.searchParams.set('phrase', target)54 } else {55 semrushUrl.searchParams.set('domain', target)56 }57 if (exportColumns) semrushUrl.searchParams.set('export_columns', exportColumns)5859 const response = await fetch(semrushUrl.toString())60 const text = await response.text()6162 // Check for SEMrush error responses63 if (text.startsWith('ERROR') || text.includes('ERROR 50')) {64 return new Response(65 JSON.stringify({ error: 'SEMrush API error', details: text }),66 { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }67 )68 }6970 const jsonData = csvToJson(text)7172 return new Response(73 JSON.stringify({ rows: jsonData, total: jsonData.length }),74 { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }75 )76 } catch (error) {77 return new Response(78 JSON.stringify({ error: error.message }),79 { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }80 )81 }82})Pro tip: SEMrush error responses are plain text strings like 'ERROR 50 :: NOTHING FOUND' — always check if the response text starts with 'ERROR' before attempting to parse it as CSV.
Expected result: The semrush-proxy Edge Function is deployed and can be tested at /functions/v1/semrush-proxy?type=domain_organic&target=example.com&display_limit=5, returning a JSON array of organic keyword rows.
Build the keyword research dashboard
Build the keyword research dashboard
With the Edge Function working, build the keyword research dashboard in Lovable. The most valuable report for a first dashboard is the keyword overview (type=phrase_this) combined with a domain organic report (type=domain_organic). Ask Lovable to create a page with two sections: a keyword search section where you enter a phrase and get back volume, difficulty, CPC, and competitive density; and a domain section where you enter a domain and see its top organic keywords with their positions, traffic estimates, and keyword difficulty. Use shadcn/ui Table, Card, and Badge components for the layout. The difficulty score (0–100) should be visualized as a color-coded badge: green (0–39 Easy), yellow (40–69 Medium), red (70–100 Hard). Add a database selector dropdown so users can switch between US, UK, and other SEMrush regional databases.
Build a keyword research dashboard with two tabs: Keyword Overview and Domain Keywords. In Keyword Overview tab, show a search input for a keyword phrase that calls our semrush-proxy Edge Function with type=phrase_this and displays volume, keyword difficulty (with Easy/Medium/Hard badge), CPC, and competitive density. In Domain Keywords tab, show a domain input that calls the Edge Function with type=domain_organic and displays a table of top keywords with position, search volume, and traffic percentage.
Paste this in Lovable chat
Pro tip: SEMrush regional databases are identified by country codes: 'us' (United States), 'uk' (United Kingdom), 'ca' (Canada), 'au' (Australia). Always expose a database selector in your UI so users can query the correct regional data.
Expected result: A working keyword research dashboard showing phrase overview data and domain organic rankings, with color-coded difficulty badges and regional database selection.
Implement API credit management with caching
Implement API credit management with caching
SEMrush API calls consume credits that do not regenerate immediately. To prevent your dashboard from exhausting your daily credit allocation, implement response caching in Supabase. Create a Supabase table called semrush_cache with columns: cache_key (text, primary key), data (jsonb), and created_at (timestamptz). The cache key is a hash of the type + target + database parameters. Before calling the SEMrush API, the Edge Function checks if a non-expired cache entry exists for this cache key. Use a cache TTL of 12 hours — SEMrush updates its data roughly once per day for most metrics. On a cache miss, call SEMrush, store the result, and return it. On a cache hit, return the cached result with a note in the response indicating it is cached. This approach can reduce SEMrush API credit consumption by over 90% for dashboards that display the same domains and keywords throughout the day.
Update the semrush-proxy Edge Function to cache API responses in a Supabase table called semrush_cache with columns cache_key (text primary key), data (jsonb), created_at (timestamptz). Use a 12-hour cache TTL. The cache key should be a combination of type + target + database. Check cache before calling SEMrush and return cached data when available.
Paste this in Lovable chat
Pro tip: Add a _cached: true flag to the cached response so your frontend can show a 'Data from cache' indicator with the cache timestamp, helping users understand when data was last refreshed.
Expected result: Repeated requests for the same domain or keyword return cached results instantly without consuming SEMrush API credits. The Edge Function logs show cache hits and misses.
Common use cases
Domain overview comparison tool
Enter two or more domains and compare their organic traffic estimates, keyword counts, backlink counts, and authority scores side by side. This gives sales teams a quick competitive context for prospects and helps content teams benchmark their site against competitors.
Create a domain comparison tool where I can enter up to three domains and see them side by side. Call our /functions/v1/semrush-proxy Edge Function with the domain overview endpoint for each. Show a comparison table with organic keywords count, organic traffic estimate, paid keywords, and domain authority score for each domain.
Copy this prompt to try it in Lovable
Keyword difficulty and volume research tool
Enter a seed keyword and retrieve volume, keyword difficulty, CPC, and competitive density. Display related keywords with their metrics so content writers can identify which terms to target in new content. Include a difficulty filter to surface only achievable keywords.
Build a keyword research page where I enter a keyword and our SEMrush Edge Function returns the keyword overview data including search volume, keyword difficulty (0-100), CPC, and top 5 competing domains. Show the data in a card with a difficulty indicator badge (Easy/Medium/Hard based on KD score) and a related keywords table.
Copy this prompt to try it in Lovable
Organic keyword gap analysis
Compare your site's organic keyword rankings against a competitor to find keywords the competitor ranks for in the top 10 that you do not rank for at all. These gap keywords represent content opportunities with proven search demand.
Create a keyword gap analysis page that calls our SEMrush Edge Function with two domains. Use the phrase_organic endpoint to get keywords for each domain, then display keywords the competitor ranks for in positions 1-10 that our domain doesn't rank for. Sort by monthly search volume descending.
Copy this prompt to try it in Lovable
Troubleshooting
Edge Function returns 'ERROR 50 :: NOTHING FOUND' from SEMrush
Cause: SEMrush has no data for the queried domain or keyword in the selected regional database. This is common for small or new sites, or for regional databases that do not track the target domain.
Solution: Try querying the 'us' database first — it has the most comprehensive coverage. For domains that primarily operate in non-English markets, try the appropriate regional database. Newly registered domains with no organic rankings will return no data in any database.
SEMrush API returns 'ERROR 10 :: WRONG KEY'
Cause: The SEMRUSH_API_KEY secret is incorrect, has expired, or was pasted with extra whitespace characters.
Solution: Go to Cloud → Secrets in your Lovable project, delete the SEMRUSH_API_KEY secret, and re-add it by copying the key fresh from semrush.com/api-analytics. Make sure there are no leading or trailing spaces in the value field.
API calls succeed but return fewer rows than expected
Cause: The display_limit parameter defaults to 10 if not specified. For reports with many rows, you need to explicitly set a higher limit.
Solution: Add the display_limit parameter to your frontend request when you need more than 10 rows. The maximum varies by endpoint type but is typically 1,000–10,000. Note that higher row counts consume more API credits proportionally.
1// Pass display_limit explicitly from the frontend2const response = await fetch(3 `/functions/v1/semrush-proxy?type=domain_organic&target=${domain}&display_limit=100`4)The CSV parsing returns empty objects or misaligned columns
Cause: Some SEMrush report types use different column separators or return data in different formats depending on the endpoint.
Solution: Log the raw response text from SEMrush before parsing to inspect the actual format. Most SEMrush reports use semicolons (;) as separators, but some newer endpoints may use commas. Adjust the csvToJson function's split character accordingly.
1// Log raw response for debugging2console.log('SEMrush raw response:', text.substring(0, 200))3// Try comma separator if semicolon gives empty results4const separator = text.includes(';') ? ';' : ','Best practices
- Always store the SEMrush API key in Cloud → Secrets as SEMRUSH_API_KEY — never include it in frontend code or paste it into Lovable's chat
- Implement 12-hour response caching in Supabase to prevent credit exhaustion; SEMrush data updates at most once daily
- Always include a display_limit parameter — SEMrush defaults to 10 rows, and some endpoints allow up to 10,000, so be intentional about how many rows you fetch
- Add the display_offset parameter to implement server-side pagination for large datasets rather than fetching all rows at once
- Use export_columns to request only the specific columns your UI needs, which reduces response size and in some cases reduces credit cost
- Monitor your API credit balance via the semrush.com/api-analytics dashboard and implement logging in your Edge Function so you can see which queries are most expensive
- Test new dashboard features using SEMrush's low-cost domain_overview endpoint before building more expensive organic research reports
- The 'us' database is the most comprehensive for testing; add a database selector to your UI so users can query their target market's regional data
Alternatives
Ahrefs has the stronger backlink index and is preferred by link builders, while SEMrush excels at keyword research, PPC competitive data, and multi-channel marketing analytics.
Google Search Console provides free and exact organic performance data for sites you own, while SEMrush provides estimated data for any site including competitors.
Moz is known for its Domain Authority metric and has a simpler API, while SEMrush offers a more comprehensive marketing data suite including PPC and social media analytics.
Frequently asked questions
How many API credits does a typical SEMrush dashboard query consume?
Credit costs vary by endpoint. The domain_organic report costs 10 units per row returned — so fetching 100 keywords for one domain costs 1,000 units. The phrase_this (keyword overview) endpoint costs 1 unit per row. Free accounts get 10 units per day, which limits you to 1 domain_organic row or 10 phrase_this rows. Pro plans include 3,000 units per day.
Can SEMrush data be called directly from the React frontend without an Edge Function?
No. The SEMrush API requires your API key as a query parameter, which means any direct frontend call would expose the key in browser network requests. Anyone could view this in DevTools and use your key to drain your credits. Always proxy SEMrush calls through an Edge Function with the key stored in Cloud Secrets.
Which SEMrush API endpoints are available without a paid plan?
The SEMrush API is not available on the free plan for most report types. You need at minimum the Pro plan ($129.95/month) to access domain_organic, phrase_this, and domain_ranks reports. Some limited API access is available via a free API key but with a 10 unit/day cap, which is only sufficient for testing single queries.
How accurate is SEMrush's organic traffic data?
SEMrush estimates organic traffic based on keyword rankings and average click-through rates for each position. It is an estimate, not exact data. Actual traffic can vary significantly from SEMrush estimates depending on brand searches, navigational queries, and other factors. For your own site, Google Search Console provides exact data; SEMrush estimates are most useful for competitor analysis where you have no access to actual data.
Can I use the SEMrush API to track keyword rankings over time?
The SEMrush API's domain_organic endpoint returns current rankings, not historical trends. To track rankings over time in your Lovable app, you need to schedule regular API calls (for example, daily via a scheduled Edge Function) and store the results in Supabase with timestamps. Your dashboard then queries Supabase for the historical data rather than hitting the SEMrush API for every view.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation