Local SEO with SERP API: Complete Guide to Local Rankings
Local SEO is crucial for businesses targeting specific geographic areas. This guide shows you how to use SERP API to track local rankings, monitor Google Maps results, and optimize your local search presence.
Why Use SERP API for Local SEO?
Local search results vary significantly by location. SERP API enables you to:
- Track local pack rankings across multiple locations
- Monitor Google Maps results for your business
- Analyze local competitors in different areas
- Optimize for “near me” searches with real data
- Scale local monitoring across hundreds of locations
- Get accurate location-specific results without VPNs
Understanding Local Search Results
Local search results typically include:
- Local Pack (Map Pack): Top 3 local businesses with map
- Organic Results: Regular search results
- Google Maps Results: Full map view results
- Local Finder: Extended local results
- Knowledge Panel: Business information sidebar
Basic Local Search with SERP API
Simple Local Search
const SERPpostClient = require('./serp-client');
const client = new SERPpostClient(process.env.SERPPOST_API_KEY);
async function searchLocal(query, location) {
const results = await client.search('google', query, {
location: location,
gl: 'us',
hl: 'en'
});
return {
localPack: results.local_results || [],
organicResults: results.organic_results || [],
searchMetadata: results.search_metadata
};
}
// Usage
const results = await searchLocal(
'pizza restaurant',
'New York, NY'
);
console.log('Local Pack Results:', results.localPack.length);
Location-Specific Parameters
async function searchWithPreciseLocation(query, locationParams) {
const results = await client.search('google', query, {
location: locationParams.city,
gl: locationParams.country,
hl: locationParams.language,
uule: locationParams.uule // Precise location encoding
});
return results;
}
// Example: Search from specific coordinates
const results = await searchWithPreciseLocation('coffee shop', {
city: 'San Francisco, CA',
country: 'us',
language: 'en',
uule: 'w+CAIQICINc2FuIGZyYW5jaXNjbw' // Encoded location
});
Building a Local Rank Tracker
Track your business rankings across multiple locations:
class LocalRankTracker {
constructor(apiKey) {
this.client = new SERPpostClient(apiKey);
}
async trackLocalRanking(businessName, keyword, locations) {
const rankings = [];
for (const location of locations) {
const results = await this.client.search('google', keyword, {
location: location,
gl: 'us',
hl: 'en'
});
const ranking = this.findBusinessRanking(
results,
businessName
);
rankings.push({
location,
keyword,
...ranking,
timestamp: new Date()
});
// Respect rate limits
await this.sleep(1000);
}
return rankings;
}
findBusinessRanking(results, businessName) {
// Check local pack
const localPack = results.local_results || [];
const localPackPosition = localPack.findIndex(result =>
result.title.toLowerCase().includes(businessName.toLowerCase())
);
if (localPackPosition !== -1) {
return {
inLocalPack: true,
localPackPosition: localPackPosition + 1,
organicPosition: null,
data: localPack[localPackPosition]
};
}
// Check organic results
const organicResults = results.organic_results || [];
const organicPosition = organicResults.findIndex(result =>
result.title.toLowerCase().includes(businessName.toLowerCase())
);
return {
inLocalPack: false,
localPackPosition: null,
organicPosition: organicPosition !== -1 ? organicPosition + 1 : null,
data: organicPosition !== -1 ? organicResults[organicPosition] : null
};
}
async trackMultipleKeywords(businessName, keywords, locations) {
const allRankings = [];
for (const keyword of keywords) {
const rankings = await this.trackLocalRanking(
businessName,
keyword,
locations
);
allRankings.push(...rankings);
}
return this.generateReport(allRankings);
}
generateReport(rankings) {
const report = {
totalSearches: rankings.length,
inLocalPack: rankings.filter(r => r.inLocalPack).length,
averageLocalPackPosition: this.calculateAverage(
rankings.filter(r => r.inLocalPack).map(r => r.localPackPosition)
),
averageOrganicPosition: this.calculateAverage(
rankings.filter(r => r.organicPosition).map(r => r.organicPosition)
),
byLocation: this.groupByLocation(rankings),
byKeyword: this.groupByKeyword(rankings)
};
return report;
}
groupByLocation(rankings) {
const grouped = {};
rankings.forEach(ranking => {
if (!grouped[ranking.location]) {
grouped[ranking.location] = [];
}
grouped[ranking.location].push(ranking);
});
return grouped;
}
groupByKeyword(rankings) {
const grouped = {};
rankings.forEach(ranking => {
if (!grouped[ranking.keyword]) {
grouped[ranking.keyword] = [];
}
grouped[ranking.keyword].push(ranking);
});
return grouped;
}
calculateAverage(numbers) {
if (numbers.length === 0) return 0;
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage
const tracker = new LocalRankTracker(process.env.SERPPOST_API_KEY);
const report = await tracker.trackMultipleKeywords(
'Joe\'s Pizza',
['pizza delivery', 'best pizza', 'pizza near me'],
['New York, NY', 'Brooklyn, NY', 'Queens, NY']
);
console.log('Local SEO Report:', report);
Monitoring Google Maps Results
Track your Google Maps presence:
class GoogleMapsMonitor {
constructor(apiKey) {
this.client = new SERPpostClient(apiKey);
}
async monitorMapsListing(businessName, location) {
const results = await this.client.search('google', businessName, {
location: location,
gl: 'us',
hl: 'en'
});
const localResults = results.local_results || [];
const knowledgeGraph = results.knowledge_graph;
const listing = localResults.find(result =>
result.title.toLowerCase() === businessName.toLowerCase()
);
if (listing) {
return {
found: true,
position: localResults.indexOf(listing) + 1,
title: listing.title,
rating: listing.rating,
reviews: listing.reviews,
address: listing.address,
phone: listing.phone,
hours: listing.hours,
type: listing.type,
thumbnail: listing.thumbnail
};
}
// Check knowledge graph
if (knowledgeGraph &&
knowledgeGraph.title.toLowerCase() === businessName.toLowerCase()) {
return {
found: true,
inKnowledgeGraph: true,
...knowledgeGraph
};
}
return {
found: false,
message: 'Business not found in local results'
};
}
async compareLocations(businessName, locations) {
const comparisons = [];
for (const location of locations) {
const listing = await this.monitorMapsListing(businessName, location);
comparisons.push({
location,
...listing
});
await this.sleep(1000);
}
return comparisons;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage
const monitor = new GoogleMapsMonitor(process.env.SERPPOST_API_KEY);
const listings = await monitor.compareLocations(
'Starbucks',
['Seattle, WA', 'Portland, OR', 'San Francisco, CA']
);
listings.forEach(listing => {
console.log(`${listing.location}: ${listing.found ? 'Found' : 'Not found'}`);
if (listing.found) {
console.log(` Rating: ${listing.rating} (${listing.reviews} reviews)`);
}
});
Local Competitor Analysis
Analyze local competitors across multiple locations:
class LocalCompetitorAnalyzer {
constructor(apiKey) {
this.client = new SERPpostClient(apiKey);
}
async analyzeCompetitors(keywords, location, limit = 10) {
const competitorData = new Map();
for (const keyword of keywords) {
const results = await this.client.search('google', keyword, {
location: location,
gl: 'us',
hl: 'en'
});
// Analyze local pack
const localResults = results.local_results || [];
localResults.slice(0, limit).forEach((result, index) => {
const name = result.title;
if (!competitorData.has(name)) {
competitorData.set(name, {
name,
appearances: 0,
keywords: [],
averagePosition: 0,
totalPosition: 0,
ratings: [],
reviews: []
});
}
const data = competitorData.get(name);
data.appearances++;
data.keywords.push(keyword);
data.totalPosition += index + 1;
data.averagePosition = data.totalPosition / data.appearances;
if (result.rating) data.ratings.push(result.rating);
if (result.reviews) data.reviews.push(result.reviews);
});
await this.sleep(1000);
}
// Convert to array and sort by appearances
const competitors = Array.from(competitorData.values())
.sort((a, b) => b.appearances - a.appearances);
return {
location,
totalKeywords: keywords.length,
competitors: competitors.map(c => ({
...c,
averageRating: c.ratings.length > 0
? c.ratings.reduce((a, b) => a + b, 0) / c.ratings.length
: null,
totalReviews: c.reviews.length > 0
? c.reviews.reduce((a, b) => a + b, 0)
: null
}))
};
}
async compareAcrossLocations(keywords, locations) {
const analyses = [];
for (const location of locations) {
const analysis = await this.analyzeCompetitors(keywords, location);
analyses.push(analysis);
}
return this.generateComparisonReport(analyses);
}
generateComparisonReport(analyses) {
const allCompetitors = new Set();
analyses.forEach(analysis => {
analysis.competitors.forEach(c => allCompetitors.add(c.name));
});
const report = {
locations: analyses.map(a => a.location),
totalCompetitors: allCompetitors.size,
byLocation: analyses,
topCompetitors: this.findTopCompetitors(analyses)
};
return report;
}
findTopCompetitors(analyses) {
const competitorScores = new Map();
analyses.forEach(analysis => {
analysis.competitors.forEach((competitor, index) => {
const name = competitor.name;
if (!competitorScores.has(name)) {
competitorScores.set(name, {
name,
totalScore: 0,
locations: []
});
}
const data = competitorScores.get(name);
// Score based on position (lower is better)
data.totalScore += (10 - index);
data.locations.push(analysis.location);
});
});
return Array.from(competitorScores.values())
.sort((a, b) => b.totalScore - a.totalScore)
.slice(0, 10);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage
const analyzer = new LocalCompetitorAnalyzer(process.env.SERPPOST_API_KEY);
const analysis = await analyzer.compareAcrossLocations(
['pizza', 'italian restaurant', 'pizza delivery'],
['New York, NY', 'Chicago, IL', 'Los Angeles, CA']
);
console.log('Top Competitors:', analysis.topCompetitors);
“Near Me” Search Optimization
Track and optimize for “near me” searches:
class NearMeOptimizer {
constructor(apiKey) {
this.client = new SERPpostClient(apiKey);
}
async analyzeNearMeSearch(baseKeyword, locations) {
const nearMeVariations = [
`${baseKeyword} near me`,
`${baseKeyword} nearby`,
`best ${baseKeyword} near me`,
`${baseKeyword} close to me`,
`${baseKeyword} around me`
];
const results = [];
for (const location of locations) {
for (const keyword of nearMeVariations) {
const searchResults = await this.client.search('google', keyword, {
location: location,
gl: 'us',
hl: 'en'
});
results.push({
keyword,
location,
localPackCount: (searchResults.local_results || []).length,
hasLocalPack: (searchResults.local_results || []).length > 0,
organicCount: (searchResults.organic_results || []).length,
features: this.extractFeatures(searchResults)
});
await this.sleep(1000);
}
}
return this.generateNearMeReport(results);
}
extractFeatures(results) {
const features = [];
if (results.local_results) features.push('local_pack');
if (results.knowledge_graph) features.push('knowledge_graph');
if (results.people_also_ask) features.push('people_also_ask');
return features;
}
generateNearMeReport(results) {
return {
totalSearches: results.length,
withLocalPack: results.filter(r => r.hasLocalPack).length,
averageLocalPackSize: this.calculateAverage(
results.map(r => r.localPackCount)
),
byKeyword: this.groupByKeyword(results),
byLocation: this.groupByLocation(results),
recommendations: this.generateRecommendations(results)
};
}
groupByKeyword(results) {
const grouped = {};
results.forEach(result => {
if (!grouped[result.keyword]) {
grouped[result.keyword] = [];
}
grouped[result.keyword].push(result);
});
return grouped;
}
groupByLocation(results) {
const grouped = {};
results.forEach(result => {
if (!grouped[result.location]) {
grouped[result.location] = [];
}
grouped[result.location].push(result);
});
return grouped;
}
calculateAverage(numbers) {
if (numbers.length === 0) return 0;
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}
generateRecommendations(results) {
const recommendations = [];
// Check local pack presence
const withoutLocalPack = results.filter(r => !r.hasLocalPack);
if (withoutLocalPack.length > 0) {
recommendations.push({
type: 'local_pack_opportunity',
message: `${withoutLocalPack.length} searches don't show local pack`,
keywords: withoutLocalPack.map(r => r.keyword)
});
}
return recommendations;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage
const optimizer = new NearMeOptimizer(process.env.SERPPOST_API_KEY);
const report = await optimizer.analyzeNearMeSearch(
'coffee shop',
['Seattle, WA', 'Portland, OR']
);
console.log('Near Me Analysis:', report);
Multi-Location Monitoring Dashboard
Build a comprehensive local SEO dashboard:
class LocalSEODashboard {
constructor(apiKey) {
this.rankTracker = new LocalRankTracker(apiKey);
this.mapsMonitor = new GoogleMapsMonitor(apiKey);
this.competitorAnalyzer = new LocalCompetitorAnalyzer(apiKey);
this.nearMeOptimizer = new NearMeOptimizer(apiKey);
}
async generateFullReport(businessName, keywords, locations) {
console.log('Generating comprehensive local SEO report...');
const [
rankings,
mapsListings,
competitors,
nearMeAnalysis
] = await Promise.all([
this.rankTracker.trackMultipleKeywords(businessName, keywords, locations),
this.mapsMonitor.compareLocations(businessName, locations),
this.competitorAnalyzer.compareAcrossLocations(keywords, locations),
this.nearMeOptimizer.analyzeNearMeSearch(keywords[0], locations)
]);
return {
businessName,
generatedAt: new Date(),
summary: {
totalLocations: locations.length,
totalKeywords: keywords.length,
inLocalPack: rankings.inLocalPack,
averagePosition: rankings.averageLocalPackPosition,
mapsListingsFound: mapsListings.filter(l => l.found).length
},
rankings,
mapsListings,
competitors,
nearMeAnalysis,
recommendations: this.generateRecommendations({
rankings,
mapsListings,
competitors
})
};
}
generateRecommendations(data) {
const recommendations = [];
// Check for missing locations
const missingListings = data.mapsListings.filter(l => !l.found);
if (missingListings.length > 0) {
recommendations.push({
priority: 'high',
type: 'missing_listings',
message: `Claim or verify listings in ${missingListings.length} locations`,
locations: missingListings.map(l => l.location)
});
}
// Check for low rankings
const lowRankings = data.rankings.byLocation;
Object.entries(lowRankings).forEach(([location, ranks]) => {
const notInPack = ranks.filter(r => !r.inLocalPack);
if (notInPack.length > ranks.length / 2) {
recommendations.push({
priority: 'medium',
type: 'improve_rankings',
message: `Improve local pack presence in ${location}`,
keywords: notInPack.map(r => r.keyword)
});
}
});
return recommendations;
}
}
// Usage
const dashboard = new LocalSEODashboard(process.env.SERPPOST_API_KEY);
const report = await dashboard.generateFullReport(
'Joe\'s Pizza',
['pizza', 'pizza delivery', 'best pizza'],
['New York, NY', 'Brooklyn, NY']
);
console.log(JSON.stringify(report, null, 2));
Best Practices for Local SEO Monitoring
- Track multiple locations: Monitor all service areas
- Use precise locations: Specify exact cities/neighborhoods
- Monitor competitors: Track top local competitors
- Check “near me” searches: Optimize for mobile searches
- Track reviews and ratings: Monitor reputation metrics
- Schedule regular checks: Daily or weekly monitoring
- Compare engines: Check both Google and Bing
Integration with Other Tools
Combine local SEO data with:
- Analytics platforms: Track correlation with traffic
- Review management: Monitor review impact on rankings
- Social media: Correlate social signals with rankings
- CRM systems: Track leads by location
- Reporting tools: Automated client reports
Cost Optimization
Keep costs low while monitoring multiple locations:
- Cache results for frequently checked locations
- Batch similar location searches
- Use appropriate check frequencies
- Focus on high-priority locations first
- Check our affordable pricing
Advanced Use Cases
Multi-Location Franchise Monitoring
async function monitorFranchise(franchiseName, locations) {
const tracker = new LocalRankTracker(process.env.SERPPOST_API_KEY);
const results = await tracker.trackMultipleKeywords(
franchiseName,
['franchise name', 'service keywords'],
locations
);
// Generate per-location reports
return results.byLocation;
}
Service Area Business Tracking
async function trackServiceArea(businessName, centerLocation, radiusMiles) {
// Generate locations within radius
const locations = generateLocationGrid(centerLocation, radiusMiles);
const tracker = new LocalRankTracker(process.env.SERPPOST_API_KEY);
return await tracker.trackLocalRanking(businessName, 'service keyword', locations);
}
Next Steps
- Learn SERP API best practices
- Explore SEO tool development
- Check Python integration
- Review Node.js implementation
Conclusion
SERP API makes local SEO monitoring scalable and reliable. Track rankings across multiple locations, monitor Google Maps presence, and analyze local competitors without the complexity of traditional scraping methods.
Start monitoring local rankings today with 100 free credits and see how SERPpost can improve your local SEO strategy.
Ready to dominate local search? Get your API key and start tracking. Need help? Check our documentation or pricing.