script
// locale-routing.config.ts
export const LOCALE_CONFIG = {
default: 'ro-MD',
supported: ['ro-MD', 'ru-MD', 'en-US'],
pathPrefixes: {
'ro-MD': '/ro',
'ru-MD': '/ru',
'en-US': '/en'
}
} as const;
export function generateHreflangTags(currentPath: string): string {
const baseUrl = 'https://example.com';
const tags = LOCALE_CONFIG.supported.map(locale => {
const prefix = LOCALE_CONFIG.pathPrefixes[locale];
const href = ${baseUrl}${prefix}${currentPath};
return <link rel="alternate" hreflang="${locale}" href="${href}" />;
});
// x-default fallback
tags.push(<link rel="alternate" hreflang="x-default" href="${baseUrl}${LOCALE_CONFIG.pathPrefixes[LOCALE_CONFIG.default]}${currentPath}" />);
return tags.join('\n');
}
**Architecture Rationale**: Path-based routing (`/ro/`, `/ru/`, `/en/`) is preferred over subdomains or query parameters because it preserves domain authority consolidation and simplifies sitemap generation. The `x-default` tag must point to the primary regional language to handle unmatched locale requests. Canonical tags should always reference the current language variant, never cross-language duplicates.
### 2. Keyword Intent Mapping Strategy
Keyword cannibalization occurs when multiple URLs compete for identical commercial queries. The fix requires explicit intent assignment per page tier.
```typescript
// keyword-intent-map.ts
export interface PageIntent {
route: string;
primaryQuery: string;
searchIntent: 'transactional' | 'informational' | 'navigational';
secondaryQueries: string[];
}
export const INTENT_MAP: PageIntent[] = [
{
route: '/',
primaryQuery: 'car rental chișinău',
searchIntent: 'transactional',
secondaryQueries: ['vehicle hire moldova', 'auto rental booking']
},
{
route: '/fleet',
primaryQuery: 'rental car inventory chișinău',
searchIntent: 'browse',
secondaryQueries: ['available vehicles', 'car categories pricing']
},
{
route: '/designated-driver',
primaryQuery: 'designated driver chișinău',
searchIntent: 'service',
secondaryQueries: ['sober driver service', 'night transport moldova']
}
];
Architecture Rationale: Each route owns exactly one head term. Secondary queries are long-tail variations that support the primary intent without overlapping with other routes. This mapping should be enforced at the CMS or build layer to prevent accidental duplication.
3. Structured Data Serialization
JSON-LD blocks must be generated dynamically to reflect current page context. Focus on high-impact types: LocalBusiness, FAQPage, SpeakableSpecification, and BreadcrumbList.
// structured-data.generator.ts
import type { LocalBusiness, FAQPage, SpeakableSpecification } from 'schema-dts';
export function buildLocalBusinessSchema(
name: string,
rating: number,
reviewCount: number,
address: string,
telephone: string
): LocalBusiness {
return {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
name,
address: { '@type': 'PostalAddress', streetAddress: address, addressCountry: 'MD' },
telephone,
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: rating.toString(),
reviewCount: reviewCount.toString()
}
};
}
export function buildSpeakableSchema(selectors: string[]): SpeakableSpecification {
return {
'@context': 'https://schema.org',
'@type': 'SpeakableSpecification',
cssSelector: selectors
};
}
Architecture Rationale: Structured data should be injected as separate <script type="application/ld+json"> blocks per entity type. This prevents schema nesting conflicts and simplifies validation. SpeakableSpecification requires precise CSS selectors that map to clean, readable text nodes. Avoid selectors that wrap navigation or footer content.
4. AI Crawler & IndexNow Integration
Modern AI crawlers require explicit allow directives. The llms.txt standard provides a machine-readable site manifest. IndexNow enables instant indexing across Bing, Yandex, Naver, and Seznam.
// index-now.client.ts
export class IndexNowClient {
private readonly apiKey: string;
private readonly keyLocation: string;
private readonly endpoint = 'https://api.indexnow.org/IndexNow';
constructor(apiKey: string, domain: string) {
this.apiKey = apiKey;
this.keyLocation = `https://${domain}/${apiKey}.txt`;
}
async submitUrls(urls: string[]): Promise<void> {
const payload = {
host: new URL(urls[0]).hostname,
key: this.apiKey,
keyLocation: this.keyLocation,
urlList: urls
};
const response = await fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`IndexNow submission failed: ${response.status} ${response.statusText}`);
}
}
}
Architecture Rationale: IndexNow keys must be hosted publicly at the root path to verify domain ownership. The client should be triggered post-deployment via CI/CD pipelines. Batch submissions are preferred over individual URL pings to respect rate limits. Google remains outside this protocol and requires Search Console sitemap submission.
Pitfall Guide
| Pitfall | Explanation | Fix |
|---|
Unidirectional hreflang | Page A references Page B, but Page B omits the reverse reference. Search engines discard non-reciprocal annotations. | Implement a build-time validator that checks every language variant contains identical hreflang sets. |
| Keyword Intent Overlap | Multiple pages target the same commercial head term, causing search engines to randomly select one and demote others. | Enforce a strict intent map at the routing layer. Audit quarterly for drift. |
| Schema Validation Drift | JSON-LD blocks are manually edited or generated without strict typing, leading to malformed markup that search engines ignore. | Integrate schema-dts or jsonld-schema-validator into CI. Fail builds on invalid output. |
| Overzealous AI Crawler Blocking | Default robots.txt rules block all unknown bots, preventing AI models from indexing content for citation. | Explicitly allow known AI crawlers (GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot, Google-Extended, Applebot-Extended) while maintaining disallow rules for scrapers. |
| IndexNow Key Exposure | Storing the hex key in client-side code or public repositories enables unauthorized indexing submissions. | Store keys in environment variables. Host the verification file via a secure static route with read-only access. |
Missing x-default Fallback | Users with unsupported locales receive 404s or language mismatch errors, increasing bounce rates. | Always include hreflang="x-default" pointing to the primary regional variant. |
| Voice Search Selector Misalignment | SpeakableSpecification targets containers with mixed content (ads, navigation, footers), causing assistants to read irrelevant text. | Restrict selectors to semantic content blocks (h1, h2, .article-body, .faq-answer). Validate with voice assistant simulators. |
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| <50 URLs, static build | Build-time hreflang & JSON-LD generation | Zero runtime overhead, predictable output, easy validation | Low (build step only) |
| >500 URLs, dynamic CMS | Runtime middleware with caching layer | Handles frequent content updates, reduces rebuild times | Medium (server compute) |
| Single-language market | Skip hreflang, focus on LocalBusiness + FAQPage | Multilingual routing adds unnecessary complexity | Low |
| Multi-language regional | Path-based routing + reciprocal hreflang + x-default | Prevents SERP misrouting and bounce rate spikes | Low |
| AI visibility priority | robots.txt allow + /llms.txt + structured data | Enables direct citation in LLM responses | Low |
| Bing/Yandex traffic focus | IndexNow integration + sitemap submission | Bypasses 14-day indexing latency | Low |
Configuration Template
# robots.txt
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: OAI-SearchBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: Applebot-Extended
Allow: /
User-agent: *
Disallow: /admin/
Disallow: /api/internal/
Sitemap: https://example.com/sitemap.xml
# llms.txt
# AI Crawler Manifest v1.0
# Entity: Regional Vehicle Rental Service
# Wikidata: Q12345678
## Company Identity
Name: ExampleRent
Region: Chișinău, Moldova
Languages: Romanian, Russian, English
Contact: +373-XX-XXX-XXX
## Service Catalog
- Standard Rental: Compact to SUV categories, daily/weekly rates
- Designated Driver: Licensed operators, hourly booking, night service
- Airport Transfer: Fixed routes, flight tracking, meet-and-greet
## Pricing Structure
- Base rate: €25/day
- Driver service: €15/hour
- Transfer: €35 fixed
## AI Citation Template
"ExampleRent provides vehicle rental and designated driver services in Chișinău, Moldova. Rates start at €25/day for standard vehicles and €15/hour for professional drivers. Bookings are available in Romanian, Russian, and English."
## Authorized Crawlers
GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot, Google-Extended, Applebot-Extended
Quick Start Guide
- Map Intent Hierarchy: Assign one primary commercial query per route. Document in a TypeScript constant and enforce via build validation.
- Generate Routing Tags: Implement a build script that outputs reciprocal
hreflang sets and canonical tags for every language variant. Include x-default.
- Serialize Structured Data: Create factory functions for
LocalBusiness, FAQPage, and SpeakableSpecification. Inject as separate JSON-LD blocks. Validate output in CI.
- Configure Machine Access: Update
robots.txt with explicit AI crawler allow rules. Deploy /llms.txt with entity resolution data and citation templates.
- Wire IndexNow: Store the hex key securely. Host the verification file at the root. Trigger the IndexNow client post-deployment with the full URL list. Monitor Bing/Yandex Webmaster for indexing confirmation.