Location-Specific Content Optimization
[To Build] ·
topic-researcher,keyword-researcher,content-brief-writer,blog-writeragents ·targetLocationfield on Activity
Adds a targetLocation field to content activities so agents can produce content specific to a city, region, or country — with local keywords, local competitor references, and location-appropriate tone.
Related: Keyword Researcher · Content Brief Writer · Blog Writer · Topic Researcher · RAG Integration · Content Toolkit Overview
Overview
| Function | Generate content targeting a specific city, region, or country |
| Type | Platform Enhancement — SEO + Content Agents |
| Status | To Build |
| Priority | P3 — Growth |
| Affected Agents | topic-researcher · keyword-researcher · content-brief-writer · blog-writer |
| Credits | No credit change |
| Plan | Free+ |
Why This Is Needed
Most Leadmetrics tenants are local service businesses — marketing agencies, medical clinics, law firms, home services companies. Their content goal is not to rank globally but to rank for “[service] in [city]” queries. Without location targeting, the content pipeline produces generic content that competes against national brands and large publications rather than local competitors.
Location context also enables more authentic content: referencing local landmarks, local events, local regulations, and local competitors makes the content feel native rather than syndicated.
targetLocation Field
A targetLocation field is added to Activity input metadata. It is an optional structured object:
interface TargetLocation {
city?: string; // e.g. "Dubai", "Melbourne", "Toronto"
region?: string; // e.g. "UAE", "Victoria", "Ontario"
country: string; // ISO 3166-1 alpha-2 — e.g. "AE", "AU", "CA"
locale?: string; // BCP 47 locale — e.g. "en-AE", "en-AU"; used for keyword volume data
}country is always required when targetLocation is set. city is the primary targeting signal for local SEO.
How Each Agent Uses Location
topic-researcher
When targetLocation is set:
- Appends location to topic titles: “Best Plumbers in Dubai — What to Look for in 2026” rather than “Best Plumbers — What to Look for”
- Prioritises angles relevant to the local market (local regulations, local seasons, local events)
- Avoids topics that are too broad to rank locally (“National Housing Market Trends”)
Prompt injection:
if (input.targetLocation?.city) {
locationBlock = `
## Location Context
Target audience is in ${input.targetLocation.city}, ${input.targetLocation.country}.
Ensure topic titles and angles are specific to this location.
Avoid generic national or global angles where a local angle exists.
`;
}keyword-researcher
When targetLocation is set:
- If the tool integration layer supports geo-filtered keyword data (DataForSEO or SEMrush with
gl+location_codeparams), pass location as a filter - Returns location-modified keyword variants:
plumber dubai,emergency plumber dubai marina,24 hour plumber jumeirah - Flags volume/difficulty as
locale-specificin output metadata
content-brief-writer
When targetLocation is set:
- Recommends a location-modified H1: “Emergency Plumber in Dubai — 24/7 Service in All Areas”
- Injects local competitor URLs: queries
competitor_contentRAG dataset filtered by location tags - Recommends a local “About the Area” or “Serving [City]” section in the outline
- Internal link suggestions include any existing location pages on the client’s site (from
website_contentRAG dataset)
blog-writer
When targetLocation is set:
- Adds a location context block to the system prompt:
const locationBlock = [
`## Location Context`,
``,
`This post targets readers in ${city}, ${country}.`,
`- Mention ${city} specifically in the intro, at least one body section, and the conclusion`,
`- Reference local landmarks, suburbs, or neighbourhoods where relevant to the topic`,
`- Use local terminology (e.g. "${country}" spelling conventions, local unit of measurement)`,
`- If referencing local competitors, use their real names if present in the context file`,
`- Include a brief "Serving ${city}" mention near the CTA`,
].join('\n');RAG Geo-Context Injection
Documents in the RAG knowledge base can be tagged with a location metadata field. When targetLocation is set in a job, the RAG query adds a location filter:
Datasets affected:
| Dataset | Location filter use |
|---|---|
competitor_content | Return competitors whose pages target the same city/region |
website_content | Return existing location-specific pages (e.g. “Serving Dubai” page) for internal link suggestions |
client_docs | Return any location-specific service area documents uploaded by the DM |
RAG query example:
const ragResults = await ragSearch({
tenantId,
datasets: ['competitor_content', 'website_content'],
query: `${primaryKeyword} ${city}`,
locationFilter: { city, country }, // applied as metadata filter in Qdrant
limit: 5,
});Location tags are set when documents are ingested. The client-researcher agent, which crawls the client’s website on onboarding, will be extended to extract and tag location data from service area pages.
Location Selector in Dashboard
Content Brief / Blog Activity creation form:
- “Target Location” field: country dropdown (required) + city text input (optional)
- Country dropdown uses ISO 3166-1 data; pre-populated based on tenant’s billing region (AED → UAE, INR → India)
- City input: free text with autocomplete (Google Places API or a static city list)
Inheritance: If a location is set on a brief, the corresponding blog activity inherits the same targetLocation. Repurposed social posts and emails also inherit location.
Keyword Data with Location
The current keyword-researcher agent uses Claude’s web search tool to estimate keyword metrics. For location-specific volume/difficulty:
- If
@leadmetrics/provider-dataforseois connected (future integration), passlocation_codeto DataForSEO’s Keywords Data endpoint - If not connected, the agent estimates local volume as a fraction of national volume and flags this:
{
"keyword": "plumber dubai",
"volume": 1200,
"volumeNote": "estimated — local volume based on city population fraction of national total",
"difficulty": 28,
"intent": "transactional"
}Key Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Optional location field | targetLocation is optional; no change to existing workflows | Existing tenants are not affected; location targeting is an opt-in enhancement |
| Structured object, not string | { city, region, country, locale } not "Dubai, UAE" | Enables precise geo-filtering in RAG and keyword API calls |
| Inherit from brief to blog | Blog activity inherits targetLocation from its parent brief | Ensures location context is consistent across the entire content pipeline |
| Free for all plans | No plan restriction | Local service businesses are on Starter; restricting to Pro would exclude the most relevant users |
| Country always required | country is required when targetLocation is set | Needed for locale-specific spelling, keyword data geo-filter, and RAG metadata filtering |
Implementation Phases
Phase 1 — Foundation + Blog Writer
- Add
TargetLocationtype topackages/common/src/types.ts - Add
targetLocationtoActivityJobDatainpackages/queue/src/types.ts - Implement
buildLocationBlock()utility inpackages/agents/src/utils/ - Update
blog-writer.worker.tsto inject location block whentargetLocationis set - Location selector in blog activity creation form
Phase 2 — SEO Chain
- Update
keyword-researcher.worker.ts: pass location to keyword tool calls; return location-modified variants - Update
content-brief-writer.worker.ts: location-informed outline + H1 + competitor query - Update
topic-researcher.worker.ts: location-filtered topic ideas
Phase 3 — RAG Location Filtering
- Extend RAG ingestion to extract and tag
locationmetadata from documents duringclient-researchercrawl - Update RAG query calls to include
locationFilterwhentargetLocationis set in the job - Test with a UAE tenant: verify Dubai-specific results surface in RAG queries
Phase 4 — Keyword Data Integration
- If
provider-dataforseois integrated: wirelocation_codeto keyword research calls - Volume/difficulty
locationNotefield in keyword output — shown in content brief UI