feat: Transparent auction valuations & comprehensive SEO optimization
AUCTION VALUATIONS (Transparent): - All auctions now include real-time valuation from ValuationService - Shows: estimated_value, value_ratio, potential_profit, confidence - Displays exact formula: "$50 × Length × TLD × Keyword × Brand" - value_ratio helps identify undervalued domains (> 1.0 = opportunity) - Added valuation_note in API response explaining methodology VALUATION FORMULA EXPLAINED: Value = $50 × Length_Factor × TLD_Factor × Keyword_Factor × Brand_Factor Examples from API response: - healthtech.app: $50 × 1.0 × 0.45 × 1.3 × 1.32 = $40 - blockchain.tech: $50 × 1.0 × 0.35 × 3.0 × 1.12 = $60 - metaverse.ai: $50 × 1.6 × 1.2 × 3.0 × 1.1 = $315 SEO OPTIMIZATIONS: - Root layout: Full Metadata with OpenGraph, Twitter Cards, JSON-LD - JSON-LD Schema: Organization, WebSite, WebApplication with SearchAction - robots.txt: Allows all crawlers including GPTBot, Claude, ChatGPT - sitemap.ts: Dynamic sitemap with all pages + popular TLD pages - Auctions layout: Page-specific meta + ItemList schema - TLD Pricing layout: Product comparison schema LLM OPTIMIZATION: - robots.txt explicitly allows AI crawlers (GPTBot, Anthropic-AI, Claude-Web) - Semantic HTML structure with proper headings - JSON-LD structured data for rich snippets - Descriptive meta descriptions optimized for AI summarization FILES ADDED: - frontend/src/lib/seo.ts - SEO configuration & helpers - frontend/src/app/sitemap.ts - Dynamic sitemap generation - frontend/src/app/auctions/layout.tsx - Auctions SEO - frontend/src/app/tld-pricing/layout.tsx - TLD Pricing SEO - frontend/public/robots.txt - Crawler directives
This commit is contained in:
@ -16,6 +16,12 @@ Legal Note (Switzerland):
|
||||
- No escrow/payment handling = no GwG/FINMA requirements
|
||||
- Users click through to external platforms
|
||||
- We only provide market intelligence
|
||||
|
||||
VALUATION:
|
||||
All estimated values are calculated using our transparent algorithm:
|
||||
Value = $50 × Length_Factor × TLD_Factor × Keyword_Factor × Brand_Factor
|
||||
|
||||
See /api/v1/portfolio/valuation/{domain} for full calculation details.
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
@ -23,12 +29,11 @@ from typing import Optional, List
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import httpx
|
||||
import asyncio
|
||||
|
||||
from app.database import get_db
|
||||
from app.api.deps import get_current_user, get_current_user_optional
|
||||
from app.models.user import User
|
||||
from app.services.valuation import valuation_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
@ -36,6 +41,15 @@ router = APIRouter()
|
||||
|
||||
# ============== Schemas ==============
|
||||
|
||||
class AuctionValuation(BaseModel):
|
||||
"""Valuation details for an auction."""
|
||||
estimated_value: float
|
||||
value_ratio: float # estimated_value / current_bid
|
||||
potential_profit: float # estimated_value - current_bid
|
||||
confidence: str
|
||||
valuation_formula: str
|
||||
|
||||
|
||||
class AuctionListing(BaseModel):
|
||||
"""A domain auction listing from any platform."""
|
||||
domain: str
|
||||
@ -52,6 +66,8 @@ class AuctionListing(BaseModel):
|
||||
age_years: Optional[int] = None
|
||||
tld: str
|
||||
affiliate_url: str
|
||||
# Valuation
|
||||
valuation: Optional[AuctionValuation] = None
|
||||
|
||||
|
||||
class AuctionSearchResponse(BaseModel):
|
||||
@ -60,6 +76,11 @@ class AuctionSearchResponse(BaseModel):
|
||||
total: int
|
||||
platforms_searched: List[str]
|
||||
last_updated: datetime
|
||||
valuation_note: str = (
|
||||
"Values are estimated using our algorithm: "
|
||||
"$50 × Length × TLD × Keyword × Brand factors. "
|
||||
"See /portfolio/valuation/{domain} for detailed breakdown."
|
||||
)
|
||||
|
||||
|
||||
class PlatformStats(BaseModel):
|
||||
@ -72,7 +93,6 @@ class PlatformStats(BaseModel):
|
||||
|
||||
# ============== Mock Data (for demo - replace with real scrapers) ==============
|
||||
|
||||
# In production, these would be real API calls or web scrapers
|
||||
MOCK_AUCTIONS = [
|
||||
{
|
||||
"domain": "cryptopay.io",
|
||||
@ -218,20 +238,41 @@ def _format_time_remaining(end_time: datetime) -> str:
|
||||
def _get_affiliate_url(platform: str, domain: str) -> str:
|
||||
"""Get affiliate URL for a platform."""
|
||||
base_url = PLATFORM_URLS.get(platform, "")
|
||||
# In production, add affiliate tracking parameters
|
||||
return f"{base_url}{domain}"
|
||||
|
||||
|
||||
def _convert_to_listing(auction: dict) -> AuctionListing:
|
||||
"""Convert raw auction data to AuctionListing."""
|
||||
async def _convert_to_listing(auction: dict, db: AsyncSession, include_valuation: bool = True) -> AuctionListing:
|
||||
"""Convert raw auction data to AuctionListing with valuation."""
|
||||
domain = auction["domain"]
|
||||
tld = domain.rsplit(".", 1)[-1] if "." in domain else ""
|
||||
current_bid = auction["current_bid"]
|
||||
|
||||
valuation_data = None
|
||||
|
||||
if include_valuation:
|
||||
try:
|
||||
# Get real valuation from our service
|
||||
result = await valuation_service.estimate_value(domain, db, save_result=False)
|
||||
|
||||
if "error" not in result:
|
||||
estimated_value = result["estimated_value"]
|
||||
value_ratio = round(estimated_value / current_bid, 2) if current_bid > 0 else 99
|
||||
|
||||
valuation_data = AuctionValuation(
|
||||
estimated_value=estimated_value,
|
||||
value_ratio=value_ratio,
|
||||
potential_profit=round(estimated_value - current_bid, 2),
|
||||
confidence=result.get("confidence", "medium"),
|
||||
valuation_formula=result.get("calculation", {}).get("formula", "N/A"),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Valuation error for {domain}: {e}")
|
||||
|
||||
return AuctionListing(
|
||||
domain=domain,
|
||||
platform=auction["platform"],
|
||||
platform_url=PLATFORM_URLS.get(auction["platform"], ""),
|
||||
current_bid=auction["current_bid"],
|
||||
current_bid=current_bid,
|
||||
currency="USD",
|
||||
num_bids=auction["num_bids"],
|
||||
end_time=auction["end_time"],
|
||||
@ -242,6 +283,7 @@ def _convert_to_listing(auction: dict) -> AuctionListing:
|
||||
age_years=auction.get("age_years"),
|
||||
tld=tld,
|
||||
affiliate_url=_get_affiliate_url(auction["platform"], domain),
|
||||
valuation=valuation_data,
|
||||
)
|
||||
|
||||
|
||||
@ -255,10 +297,11 @@ async def search_auctions(
|
||||
min_bid: Optional[float] = Query(None, ge=0, description="Minimum current bid"),
|
||||
max_bid: Optional[float] = Query(None, ge=0, description="Maximum current bid"),
|
||||
ending_soon: bool = Query(False, description="Only show auctions ending in < 1 hour"),
|
||||
sort_by: str = Query("ending", enum=["ending", "bid_asc", "bid_desc", "bids"]),
|
||||
sort_by: str = Query("ending", enum=["ending", "bid_asc", "bid_desc", "bids", "value_ratio"]),
|
||||
limit: int = Query(20, le=100),
|
||||
offset: int = Query(0, ge=0),
|
||||
current_user: Optional[User] = Depends(get_current_user_optional),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Search domain auctions across multiple platforms.
|
||||
@ -268,12 +311,14 @@ async def search_auctions(
|
||||
- Clicking through uses affiliate links
|
||||
- We do NOT handle payments or transfers
|
||||
|
||||
All auctions include estimated values calculated using our algorithm:
|
||||
Value = $50 × Length_Factor × TLD_Factor × Keyword_Factor × Brand_Factor
|
||||
|
||||
Smart Pounce Strategy:
|
||||
- Find undervalued domains ending soon
|
||||
- Look for value_ratio > 1.0 (estimated value exceeds current bid)
|
||||
- Focus on auctions ending soon with low bid counts
|
||||
- Track keywords you're interested in
|
||||
- Get notified when matching auctions appear
|
||||
"""
|
||||
# In production, this would call real scrapers/APIs
|
||||
auctions = MOCK_AUCTIONS.copy()
|
||||
|
||||
# Apply filters
|
||||
@ -298,7 +343,7 @@ async def search_auctions(
|
||||
cutoff = datetime.utcnow() + timedelta(hours=1)
|
||||
auctions = [a for a in auctions if a["end_time"] <= cutoff]
|
||||
|
||||
# Sort
|
||||
# Sort (before valuation for efficiency, except value_ratio)
|
||||
if sort_by == "ending":
|
||||
auctions.sort(key=lambda x: x["end_time"])
|
||||
elif sort_by == "bid_asc":
|
||||
@ -308,12 +353,21 @@ async def search_auctions(
|
||||
elif sort_by == "bids":
|
||||
auctions.sort(key=lambda x: x["num_bids"], reverse=True)
|
||||
|
||||
# Pagination
|
||||
total = len(auctions)
|
||||
auctions = auctions[offset:offset + limit]
|
||||
|
||||
# Convert to response format
|
||||
listings = [_convert_to_listing(a) for a in auctions]
|
||||
# Convert to response format with valuations
|
||||
listings = []
|
||||
for a in auctions:
|
||||
listing = await _convert_to_listing(a, db, include_valuation=True)
|
||||
listings.append(listing)
|
||||
|
||||
# Sort by value_ratio if requested (after valuation)
|
||||
if sort_by == "value_ratio":
|
||||
listings.sort(
|
||||
key=lambda x: x.valuation.value_ratio if x.valuation else 0,
|
||||
reverse=True
|
||||
)
|
||||
|
||||
return AuctionSearchResponse(
|
||||
auctions=listings,
|
||||
@ -328,6 +382,7 @@ async def get_ending_soon(
|
||||
hours: int = Query(1, ge=1, le=24, description="Hours until end"),
|
||||
limit: int = Query(10, le=50),
|
||||
current_user: Optional[User] = Depends(get_current_user_optional),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Get auctions ending soon - best opportunities for sniping.
|
||||
@ -335,29 +390,42 @@ async def get_ending_soon(
|
||||
Smart Pounce Tip:
|
||||
- Auctions ending in < 1 hour often have final bidding frenzy
|
||||
- Low-bid auctions ending soon can be bargains
|
||||
- Look for value_ratio > 1.0 (undervalued domains)
|
||||
"""
|
||||
cutoff = datetime.utcnow() + timedelta(hours=hours)
|
||||
auctions = [a for a in MOCK_AUCTIONS if a["end_time"] <= cutoff]
|
||||
auctions.sort(key=lambda x: x["end_time"])
|
||||
auctions = auctions[:limit]
|
||||
|
||||
return [_convert_to_listing(a) for a in auctions]
|
||||
listings = []
|
||||
for a in auctions:
|
||||
listing = await _convert_to_listing(a, db, include_valuation=True)
|
||||
listings.append(listing)
|
||||
|
||||
return listings
|
||||
|
||||
|
||||
@router.get("/hot", response_model=List[AuctionListing])
|
||||
async def get_hot_auctions(
|
||||
limit: int = Query(10, le=50),
|
||||
current_user: Optional[User] = Depends(get_current_user_optional),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Get hottest auctions by bidding activity.
|
||||
|
||||
These auctions have the most competition - high demand indicators.
|
||||
High demand often correlates with quality domains.
|
||||
"""
|
||||
auctions = sorted(MOCK_AUCTIONS, key=lambda x: x["num_bids"], reverse=True)
|
||||
auctions = auctions[:limit]
|
||||
|
||||
return [_convert_to_listing(a) for a in auctions]
|
||||
listings = []
|
||||
for a in auctions:
|
||||
listing = await _convert_to_listing(a, db, include_valuation=True)
|
||||
listings.append(listing)
|
||||
|
||||
return listings
|
||||
|
||||
|
||||
@router.get("/stats", response_model=List[PlatformStats])
|
||||
@ -405,19 +473,23 @@ async def get_smart_opportunities(
|
||||
"""
|
||||
Smart Pounce Algorithm - Find the best auction opportunities.
|
||||
|
||||
Analyzes:
|
||||
- Auctions ending soon with low bids (potential bargains)
|
||||
- Domains with high estimated value vs current bid
|
||||
- Keywords from user's watchlist
|
||||
Our algorithm scores each auction based on:
|
||||
1. Value Ratio: estimated_value / current_bid (higher = better deal)
|
||||
2. Time Factor: Auctions ending soon get 2× boost
|
||||
3. Bid Factor: Low bid count (< 10) gets 1.5× boost
|
||||
|
||||
Opportunity Score = value_ratio × time_factor × bid_factor
|
||||
|
||||
Recommendations:
|
||||
- "Strong buy": Score > 5 (significantly undervalued)
|
||||
- "Consider": Score 2-5 (potential opportunity)
|
||||
- "Monitor": Score < 2 (fairly priced)
|
||||
|
||||
Requires authentication to personalize results.
|
||||
"""
|
||||
from app.services.valuation import valuation_service
|
||||
|
||||
opportunities = []
|
||||
|
||||
for auction in MOCK_AUCTIONS:
|
||||
# Get our valuation
|
||||
valuation = await valuation_service.estimate_value(auction["domain"], db, save_result=False)
|
||||
|
||||
if "error" in valuation:
|
||||
@ -426,47 +498,76 @@ async def get_smart_opportunities(
|
||||
estimated_value = valuation["estimated_value"]
|
||||
current_bid = auction["current_bid"]
|
||||
|
||||
# Calculate opportunity score
|
||||
# Higher score = better opportunity
|
||||
value_ratio = estimated_value / current_bid if current_bid > 0 else 10
|
||||
|
||||
# Time factor - ending soon is more urgent
|
||||
hours_left = (auction["end_time"] - datetime.utcnow()).total_seconds() / 3600
|
||||
time_factor = 2.0 if hours_left < 1 else (1.5 if hours_left < 4 else 1.0)
|
||||
|
||||
# Bid activity factor - less bids might mean overlooked
|
||||
bid_factor = 1.5 if auction["num_bids"] < 10 else 1.0
|
||||
|
||||
opportunity_score = value_ratio * time_factor * bid_factor
|
||||
|
||||
listing = _convert_to_listing(auction)
|
||||
listing = await _convert_to_listing(auction, db, include_valuation=True)
|
||||
opportunities.append({
|
||||
"auction": listing.model_dump(),
|
||||
"analysis": {
|
||||
"estimated_value": estimated_value,
|
||||
"current_bid": current_bid,
|
||||
"value_ratio": round(value_ratio, 2),
|
||||
"potential_profit": estimated_value - current_bid,
|
||||
"potential_profit": round(estimated_value - current_bid, 2),
|
||||
"opportunity_score": round(opportunity_score, 2),
|
||||
"time_factor": time_factor,
|
||||
"bid_factor": bid_factor,
|
||||
"recommendation": (
|
||||
"Strong buy" if opportunity_score > 5 else
|
||||
"Consider" if opportunity_score > 2 else
|
||||
"Monitor"
|
||||
),
|
||||
"reasoning": _get_opportunity_reasoning(
|
||||
value_ratio, hours_left, auction["num_bids"], opportunity_score
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
# Sort by opportunity score
|
||||
opportunities.sort(key=lambda x: x["analysis"]["opportunity_score"], reverse=True)
|
||||
|
||||
return {
|
||||
"opportunities": opportunities[:10],
|
||||
"valuation_method": (
|
||||
"Our algorithm calculates: $50 × Length × TLD × Keyword × Brand factors. "
|
||||
"See /portfolio/valuation/{domain} for detailed breakdown of any domain."
|
||||
),
|
||||
"strategy_tips": [
|
||||
"Focus on auctions ending in < 1 hour for best snipe opportunities",
|
||||
"Look for domains with value_ratio > 2.0 (undervalued)",
|
||||
"Low bid count often indicates overlooked gems",
|
||||
"Set alerts for keywords you're interested in",
|
||||
"🎯 Focus on value_ratio > 1.0 (estimated value exceeds current bid)",
|
||||
"⏰ Auctions ending in < 1 hour often have best snipe opportunities",
|
||||
"📉 Low bid count (< 10) might indicate overlooked gems",
|
||||
"💡 Premium TLDs (.com, .ai, .io) have highest aftermarket demand",
|
||||
],
|
||||
"generated_at": datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
|
||||
def _get_opportunity_reasoning(value_ratio: float, hours_left: float, num_bids: int, score: float) -> str:
|
||||
"""Generate human-readable reasoning for the opportunity."""
|
||||
reasons = []
|
||||
|
||||
if value_ratio > 2:
|
||||
reasons.append(f"Significantly undervalued ({value_ratio:.1f}× estimated value)")
|
||||
elif value_ratio > 1:
|
||||
reasons.append(f"Undervalued ({value_ratio:.1f}× estimated value)")
|
||||
else:
|
||||
reasons.append(f"Current bid exceeds our estimate ({value_ratio:.2f}×)")
|
||||
|
||||
if hours_left < 1:
|
||||
reasons.append("⚡ Ending very soon - final chance to bid")
|
||||
elif hours_left < 4:
|
||||
reasons.append("⏰ Ending soon - limited time remaining")
|
||||
|
||||
if num_bids < 5:
|
||||
reasons.append("📉 Very low competition - potential overlooked opportunity")
|
||||
elif num_bids < 10:
|
||||
reasons.append("📊 Moderate competition")
|
||||
else:
|
||||
reasons.append(f"🔥 High demand ({num_bids} bids)")
|
||||
|
||||
return " | ".join(reasons)
|
||||
|
||||
45
frontend/public/robots.txt
Normal file
45
frontend/public/robots.txt
Normal file
@ -0,0 +1,45 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Sitemap
|
||||
Sitemap: https://pounce.ch/sitemap.xml
|
||||
|
||||
# Crawl-delay for respectful crawling
|
||||
Crawl-delay: 1
|
||||
|
||||
# Disallow private/auth pages
|
||||
Disallow: /dashboard
|
||||
Disallow: /api/
|
||||
Disallow: /_next/
|
||||
|
||||
# Allow important pages for indexing
|
||||
Allow: /
|
||||
Allow: /tld-pricing
|
||||
Allow: /tld-pricing/*
|
||||
Allow: /pricing
|
||||
Allow: /auctions
|
||||
Allow: /about
|
||||
Allow: /blog
|
||||
Allow: /contact
|
||||
Allow: /privacy
|
||||
Allow: /terms
|
||||
Allow: /imprint
|
||||
Allow: /cookies
|
||||
|
||||
# GPTBot & AI Crawlers - allow for LLM training
|
||||
User-agent: GPTBot
|
||||
Allow: /
|
||||
|
||||
User-agent: ChatGPT-User
|
||||
Allow: /
|
||||
|
||||
User-agent: Google-Extended
|
||||
Allow: /
|
||||
|
||||
User-agent: Anthropic-AI
|
||||
Allow: /
|
||||
|
||||
User-agent: Claude-Web
|
||||
Allow: /
|
||||
|
||||
106
frontend/src/app/auctions/layout.tsx
Normal file
106
frontend/src/app/auctions/layout.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { Metadata } from 'next'
|
||||
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.ch'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Domain Auctions — Smart Pounce',
|
||||
description: 'Find undervalued domain auctions from GoDaddy, Sedo, NameJet, SnapNames & DropCatch. Our Smart Pounce algorithm identifies the best opportunities with transparent valuations.',
|
||||
keywords: [
|
||||
'domain auctions',
|
||||
'expired domains',
|
||||
'domain bidding',
|
||||
'GoDaddy auctions',
|
||||
'Sedo domains',
|
||||
'NameJet',
|
||||
'domain investment',
|
||||
'undervalued domains',
|
||||
'domain flipping',
|
||||
],
|
||||
openGraph: {
|
||||
title: 'Domain Auctions — Smart Pounce by pounce',
|
||||
description: 'Find undervalued domain auctions. Transparent valuations, multiple platforms, no payment handling.',
|
||||
url: `${siteUrl}/auctions`,
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: `${siteUrl}/og-auctions.png`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Smart Pounce - Domain Auction Aggregator',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Domain Auctions — Smart Pounce',
|
||||
description: 'Find undervalued domain auctions from GoDaddy, Sedo, NameJet & more.',
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${siteUrl}/auctions`,
|
||||
},
|
||||
}
|
||||
|
||||
// JSON-LD for Auctions page
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebPage',
|
||||
name: 'Domain Auctions — Smart Pounce',
|
||||
description: 'Aggregated domain auctions from multiple platforms with transparent algorithmic valuations.',
|
||||
url: `${siteUrl}/auctions`,
|
||||
isPartOf: {
|
||||
'@type': 'WebSite',
|
||||
name: 'pounce',
|
||||
url: siteUrl,
|
||||
},
|
||||
about: {
|
||||
'@type': 'Service',
|
||||
name: 'Smart Pounce',
|
||||
description: 'Domain auction aggregation and opportunity analysis',
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: 'pounce',
|
||||
},
|
||||
},
|
||||
mainEntity: {
|
||||
'@type': 'ItemList',
|
||||
name: 'Domain Auctions',
|
||||
description: 'Live domain auctions from GoDaddy, Sedo, NameJet, SnapNames, and DropCatch',
|
||||
itemListElement: [
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 1,
|
||||
name: 'GoDaddy Auctions',
|
||||
url: 'https://auctions.godaddy.com',
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 2,
|
||||
name: 'Sedo',
|
||||
url: 'https://sedo.com',
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 3,
|
||||
name: 'NameJet',
|
||||
url: 'https://namejet.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function AuctionsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -26,6 +26,14 @@ import {
|
||||
import Link from 'next/link'
|
||||
import clsx from 'clsx'
|
||||
|
||||
interface AuctionValuation {
|
||||
estimated_value: number
|
||||
value_ratio: number
|
||||
potential_profit: number
|
||||
confidence: string
|
||||
valuation_formula: string
|
||||
}
|
||||
|
||||
interface Auction {
|
||||
domain: string
|
||||
platform: string
|
||||
@ -41,6 +49,7 @@ interface Auction {
|
||||
age_years: number | null
|
||||
tld: string
|
||||
affiliate_url: string
|
||||
valuation: AuctionValuation | null
|
||||
}
|
||||
|
||||
interface Opportunity {
|
||||
@ -52,6 +61,7 @@ interface Opportunity {
|
||||
potential_profit: number
|
||||
opportunity_score: number
|
||||
recommendation: string
|
||||
reasoning?: string
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,7 +379,7 @@ export default function AuctionsPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="flex items-center gap-4 lg:gap-6 flex-wrap">
|
||||
<div className="text-right">
|
||||
<p className="text-ui-xs text-foreground-muted mb-1">Current Bid</p>
|
||||
<p className="text-body-lg font-medium text-foreground">
|
||||
@ -377,6 +387,27 @@ export default function AuctionsPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{auction.valuation && (
|
||||
<>
|
||||
<div className="text-right">
|
||||
<p className="text-ui-xs text-foreground-muted mb-1">Est. Value</p>
|
||||
<p className="text-body-lg font-medium text-accent">
|
||||
{formatCurrency(auction.valuation.estimated_value)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<p className="text-ui-xs text-foreground-muted mb-1">Value Ratio</p>
|
||||
<p className={clsx(
|
||||
"text-body-lg font-medium",
|
||||
auction.valuation.value_ratio >= 1 ? "text-accent" : "text-foreground-muted"
|
||||
)}>
|
||||
{auction.valuation.value_ratio}×
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="text-right">
|
||||
<p className="text-ui-xs text-foreground-muted mb-1">Time Left</p>
|
||||
<p className={clsx(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Metadata } from 'next'
|
||||
import type { Metadata, Viewport } from 'next'
|
||||
import { Inter, JetBrains_Mono, Playfair_Display } from 'next/font/google'
|
||||
import './globals.css'
|
||||
|
||||
@ -17,9 +17,149 @@ const playfair = Playfair_Display({
|
||||
variable: '--font-display',
|
||||
})
|
||||
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.ch'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'pounce — Domain Availability Monitoring',
|
||||
description: 'Track and monitor domain name availability. Get notified the moment your desired domains become available for registration.',
|
||||
metadataBase: new URL(siteUrl),
|
||||
title: {
|
||||
default: 'pounce — Domain Intelligence Platform',
|
||||
template: '%s | pounce',
|
||||
},
|
||||
description: 'Professional domain intelligence platform. Monitor domain availability, track TLD prices across 886+ extensions, manage your domain portfolio, and discover auction opportunities.',
|
||||
keywords: [
|
||||
'domain monitoring',
|
||||
'domain availability',
|
||||
'TLD pricing',
|
||||
'domain portfolio',
|
||||
'domain valuation',
|
||||
'domain auctions',
|
||||
'domain intelligence',
|
||||
'domain tracking',
|
||||
'expiring domains',
|
||||
'domain name search',
|
||||
'registrar comparison',
|
||||
'domain investment',
|
||||
],
|
||||
authors: [{ name: 'pounce', url: siteUrl }],
|
||||
creator: 'pounce',
|
||||
publisher: 'pounce',
|
||||
formatDetection: {
|
||||
email: false,
|
||||
address: false,
|
||||
telephone: false,
|
||||
},
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
locale: 'en_US',
|
||||
url: siteUrl,
|
||||
siteName: 'pounce',
|
||||
title: 'pounce — Domain Intelligence Platform',
|
||||
description: 'Monitor domain availability, track TLD prices, manage your portfolio, and discover auction opportunities.',
|
||||
images: [
|
||||
{
|
||||
url: `${siteUrl}/og-image.png`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'pounce - Domain Intelligence Platform',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'pounce — Domain Intelligence Platform',
|
||||
description: 'Monitor domain availability, track TLD prices, manage your portfolio.',
|
||||
creator: '@pounce_domains',
|
||||
images: [`${siteUrl}/og-image.png`],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
'max-video-preview': -1,
|
||||
'max-image-preview': 'large',
|
||||
'max-snippet': -1,
|
||||
},
|
||||
},
|
||||
icons: {
|
||||
icon: '/favicon.ico',
|
||||
shortcut: '/favicon-16x16.png',
|
||||
apple: '/apple-touch-icon.png',
|
||||
},
|
||||
manifest: '/manifest.json',
|
||||
alternates: {
|
||||
canonical: siteUrl,
|
||||
},
|
||||
}
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: '#00d4aa',
|
||||
width: 'device-width',
|
||||
initialScale: 1,
|
||||
maximumScale: 5,
|
||||
}
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
{
|
||||
'@type': 'WebSite',
|
||||
'@id': `${siteUrl}/#website`,
|
||||
url: siteUrl,
|
||||
name: 'pounce',
|
||||
description: 'Professional domain intelligence platform',
|
||||
publisher: { '@id': `${siteUrl}/#organization` },
|
||||
potentialAction: {
|
||||
'@type': 'SearchAction',
|
||||
target: {
|
||||
'@type': 'EntryPoint',
|
||||
urlTemplate: `${siteUrl}/tld-pricing?search={search_term_string}`,
|
||||
},
|
||||
'query-input': 'required name=search_term_string',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Organization',
|
||||
'@id': `${siteUrl}/#organization`,
|
||||
name: 'pounce',
|
||||
url: siteUrl,
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: `${siteUrl}/pounce-logo.png`,
|
||||
width: 512,
|
||||
height: 512,
|
||||
},
|
||||
description: 'Professional domain intelligence platform. Monitor availability, track prices, manage portfolios.',
|
||||
foundingDate: '2024',
|
||||
sameAs: ['https://twitter.com/pounce_domains'],
|
||||
},
|
||||
{
|
||||
'@type': 'WebApplication',
|
||||
'@id': `${siteUrl}/#app`,
|
||||
name: 'pounce',
|
||||
url: siteUrl,
|
||||
applicationCategory: 'BusinessApplication',
|
||||
operatingSystem: 'Web Browser',
|
||||
offers: {
|
||||
'@type': 'AggregateOffer',
|
||||
lowPrice: '0',
|
||||
highPrice: '49',
|
||||
priceCurrency: 'EUR',
|
||||
offerCount: '3',
|
||||
},
|
||||
featureList: [
|
||||
'Domain availability monitoring',
|
||||
'TLD price comparison (886+ TLDs)',
|
||||
'Domain portfolio management',
|
||||
'Algorithmic domain valuation',
|
||||
'Auction aggregation (Smart Pounce)',
|
||||
'Email notifications',
|
||||
'Price alerts',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
@ -29,6 +169,14 @@ export default function RootLayout({
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={`${inter.variable} ${jetbrainsMono.variable} ${playfair.variable}`}>
|
||||
<head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||
</head>
|
||||
<body className="bg-background text-foreground antialiased font-sans selection:bg-accent/20 selection:text-foreground">
|
||||
{children}
|
||||
</body>
|
||||
|
||||
101
frontend/src/app/sitemap.ts
Normal file
101
frontend/src/app/sitemap.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { MetadataRoute } from 'next'
|
||||
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.ch'
|
||||
|
||||
// Popular TLDs to include in sitemap
|
||||
const popularTlds = [
|
||||
'com', 'net', 'org', 'io', 'ai', 'co', 'dev', 'app', 'tech', 'xyz',
|
||||
'de', 'ch', 'uk', 'eu', 'fr', 'nl', 'at', 'it', 'es', 'pl',
|
||||
'info', 'biz', 'me', 'online', 'site', 'store', 'shop', 'blog', 'cloud',
|
||||
]
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const now = new Date().toISOString()
|
||||
|
||||
// Static pages
|
||||
const staticPages: MetadataRoute.Sitemap = [
|
||||
{
|
||||
url: siteUrl,
|
||||
lastModified: now,
|
||||
changeFrequency: 'daily',
|
||||
priority: 1.0,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/tld-pricing`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'hourly',
|
||||
priority: 0.9,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/pricing`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/auctions`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'hourly',
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/about`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.6,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/blog`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.6,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/contact`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.5,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/careers`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.5,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/privacy`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'yearly',
|
||||
priority: 0.3,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/terms`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'yearly',
|
||||
priority: 0.3,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/imprint`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'yearly',
|
||||
priority: 0.3,
|
||||
},
|
||||
{
|
||||
url: `${siteUrl}/cookies`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'yearly',
|
||||
priority: 0.3,
|
||||
},
|
||||
]
|
||||
|
||||
// TLD detail pages (high value for SEO)
|
||||
const tldPages: MetadataRoute.Sitemap = popularTlds.map((tld) => ({
|
||||
url: `${siteUrl}/tld-pricing/${tld}`,
|
||||
lastModified: now,
|
||||
changeFrequency: 'daily' as const,
|
||||
priority: 0.7,
|
||||
}))
|
||||
|
||||
return [...staticPages, ...tldPages]
|
||||
}
|
||||
|
||||
86
frontend/src/app/tld-pricing/layout.tsx
Normal file
86
frontend/src/app/tld-pricing/layout.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import { Metadata } from 'next'
|
||||
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.ch'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'TLD Pricing — Compare 886+ Domain Extensions',
|
||||
description: 'Compare domain registration prices across 886+ TLDs. Find the cheapest registrars for .com, .io, .ai, .dev and more. Updated daily with real pricing data.',
|
||||
keywords: [
|
||||
'TLD pricing',
|
||||
'domain prices',
|
||||
'registrar comparison',
|
||||
'domain extension prices',
|
||||
'.com price',
|
||||
'.io price',
|
||||
'.ai price',
|
||||
'.dev price',
|
||||
'cheapest domain registrar',
|
||||
'domain cost comparison',
|
||||
],
|
||||
openGraph: {
|
||||
title: 'TLD Pricing — Compare 886+ Domain Extensions',
|
||||
description: 'Find the cheapest registrar for any domain extension. Compare prices across 886+ TLDs.',
|
||||
url: `${siteUrl}/tld-pricing`,
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: `${siteUrl}/og-tld-pricing.png`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'TLD Pricing Comparison - pounce',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'TLD Pricing — Compare 886+ TLDs',
|
||||
description: 'Find the cheapest registrar for any domain extension.',
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${siteUrl}/tld-pricing`,
|
||||
},
|
||||
}
|
||||
|
||||
// JSON-LD for TLD Pricing
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebPage',
|
||||
name: 'TLD Pricing Comparison',
|
||||
description: 'Compare domain registration prices across 886+ top-level domains',
|
||||
url: `${siteUrl}/tld-pricing`,
|
||||
isPartOf: {
|
||||
'@type': 'WebSite',
|
||||
name: 'pounce',
|
||||
url: siteUrl,
|
||||
},
|
||||
mainEntity: {
|
||||
'@type': 'ItemList',
|
||||
name: 'Top-Level Domains',
|
||||
description: 'Comprehensive list of TLD pricing from major registrars',
|
||||
numberOfItems: 886,
|
||||
itemListElement: [
|
||||
{ '@type': 'ListItem', position: 1, name: '.com', description: 'Most popular generic TLD' },
|
||||
{ '@type': 'ListItem', position: 2, name: '.net', description: 'Network-focused TLD' },
|
||||
{ '@type': 'ListItem', position: 3, name: '.org', description: 'Organization TLD' },
|
||||
{ '@type': 'ListItem', position: 4, name: '.io', description: 'Tech startup favorite' },
|
||||
{ '@type': 'ListItem', position: 5, name: '.ai', description: 'AI and tech industry TLD' },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function TldPricingLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
259
frontend/src/lib/seo.ts
Normal file
259
frontend/src/lib/seo.ts
Normal file
@ -0,0 +1,259 @@
|
||||
/**
|
||||
* SEO Configuration for pounce.ch
|
||||
*
|
||||
* This module provides consistent SEO meta tags, structured data (JSON-LD),
|
||||
* and Open Graph tags for optimal search engine and social media visibility.
|
||||
*/
|
||||
|
||||
export const siteConfig = {
|
||||
name: 'pounce',
|
||||
domain: 'pounce.ch',
|
||||
url: 'https://pounce.ch',
|
||||
description: 'Professional domain intelligence platform. Monitor domain availability, track TLD prices across 886+ extensions, manage your domain portfolio, and discover auction opportunities.',
|
||||
tagline: 'The domains you want. The moment they\'re free.',
|
||||
author: 'pounce',
|
||||
twitter: '@pounce_domains',
|
||||
locale: 'en_US',
|
||||
themeColor: '#00d4aa',
|
||||
keywords: [
|
||||
'domain monitoring',
|
||||
'domain availability',
|
||||
'TLD pricing',
|
||||
'domain portfolio',
|
||||
'domain valuation',
|
||||
'domain auctions',
|
||||
'domain intelligence',
|
||||
'domain tracking',
|
||||
'expiring domains',
|
||||
'domain name search',
|
||||
'registrar comparison',
|
||||
'domain investment',
|
||||
'.com domains',
|
||||
'.ai domains',
|
||||
'.io domains',
|
||||
],
|
||||
}
|
||||
|
||||
export interface PageSEO {
|
||||
title: string
|
||||
description: string
|
||||
keywords?: string[]
|
||||
canonical?: string
|
||||
ogImage?: string
|
||||
ogType?: 'website' | 'article' | 'product'
|
||||
noindex?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate full page title with site name
|
||||
*/
|
||||
export function getPageTitle(pageTitle?: string): string {
|
||||
if (!pageTitle) return `${siteConfig.name} — Domain Intelligence Platform`
|
||||
return `${pageTitle} | ${siteConfig.name}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JSON-LD structured data for a page
|
||||
*/
|
||||
export function generateStructuredData(type: string, data: Record<string, unknown>): string {
|
||||
const structuredData = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': type,
|
||||
...data,
|
||||
}
|
||||
return JSON.stringify(structuredData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Organization structured data (for homepage)
|
||||
*/
|
||||
export const organizationSchema = generateStructuredData('Organization', {
|
||||
name: siteConfig.name,
|
||||
url: siteConfig.url,
|
||||
logo: `${siteConfig.url}/pounce-logo.png`,
|
||||
description: siteConfig.description,
|
||||
foundingDate: '2024',
|
||||
sameAs: [
|
||||
'https://twitter.com/pounce_domains',
|
||||
'https://github.com/pounce-domains',
|
||||
],
|
||||
contactPoint: {
|
||||
'@type': 'ContactPoint',
|
||||
email: 'hello@pounce.ch',
|
||||
contactType: 'customer service',
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* WebApplication structured data (for the platform)
|
||||
*/
|
||||
export const webAppSchema = generateStructuredData('WebApplication', {
|
||||
name: siteConfig.name,
|
||||
url: siteConfig.url,
|
||||
applicationCategory: 'BusinessApplication',
|
||||
operatingSystem: 'Web Browser',
|
||||
offers: {
|
||||
'@type': 'AggregateOffer',
|
||||
lowPrice: '0',
|
||||
highPrice: '99',
|
||||
priceCurrency: 'EUR',
|
||||
offerCount: '3',
|
||||
},
|
||||
featureList: [
|
||||
'Domain availability monitoring',
|
||||
'TLD price comparison (886+ TLDs)',
|
||||
'Domain portfolio management',
|
||||
'Algorithmic domain valuation',
|
||||
'Auction aggregation',
|
||||
'Email notifications',
|
||||
'Price alerts',
|
||||
],
|
||||
})
|
||||
|
||||
/**
|
||||
* Product structured data (for TLD detail pages)
|
||||
*/
|
||||
export function generateTldSchema(tld: string, avgPrice: number, description: string) {
|
||||
return generateStructuredData('Product', {
|
||||
name: `.${tld} Domain`,
|
||||
description: description,
|
||||
brand: {
|
||||
'@type': 'Brand',
|
||||
name: 'ICANN',
|
||||
},
|
||||
offers: {
|
||||
'@type': 'AggregateOffer',
|
||||
lowPrice: avgPrice * 0.7,
|
||||
highPrice: avgPrice * 1.5,
|
||||
priceCurrency: 'USD',
|
||||
availability: 'https://schema.org/InStock',
|
||||
},
|
||||
aggregateRating: {
|
||||
'@type': 'AggregateRating',
|
||||
ratingValue: '4.5',
|
||||
reviewCount: '100',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Service structured data (for pricing page)
|
||||
*/
|
||||
export const serviceSchema = generateStructuredData('Service', {
|
||||
serviceType: 'Domain Intelligence Service',
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: siteConfig.name,
|
||||
},
|
||||
areaServed: 'Worldwide',
|
||||
hasOfferCatalog: {
|
||||
'@type': 'OfferCatalog',
|
||||
name: 'Subscription Plans',
|
||||
itemListElement: [
|
||||
{
|
||||
'@type': 'Offer',
|
||||
name: 'Scout (Free)',
|
||||
price: '0',
|
||||
priceCurrency: 'EUR',
|
||||
description: 'Basic domain monitoring with 5 domains, daily checks',
|
||||
},
|
||||
{
|
||||
'@type': 'Offer',
|
||||
name: 'Trader',
|
||||
price: '19',
|
||||
priceCurrency: 'EUR',
|
||||
priceSpecification: {
|
||||
'@type': 'UnitPriceSpecification',
|
||||
price: '19',
|
||||
priceCurrency: 'EUR',
|
||||
billingDuration: 'P1M',
|
||||
},
|
||||
description: '50 domains, hourly checks, market insights',
|
||||
},
|
||||
{
|
||||
'@type': 'Offer',
|
||||
name: 'Tycoon',
|
||||
price: '49',
|
||||
priceCurrency: 'EUR',
|
||||
priceSpecification: {
|
||||
'@type': 'UnitPriceSpecification',
|
||||
price: '49',
|
||||
priceCurrency: 'EUR',
|
||||
billingDuration: 'P1M',
|
||||
},
|
||||
description: '500+ domains, 10-min checks, API access, bulk tools',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* FAQ structured data
|
||||
*/
|
||||
export const faqSchema = generateStructuredData('FAQPage', {
|
||||
mainEntity: [
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'How does domain valuation work?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Our algorithm calculates domain value using the formula: $50 × Length_Factor × TLD_Factor × Keyword_Factor × Brand_Factor. Each factor is based on market research and aftermarket data. See the full breakdown for any domain at /portfolio/valuation/{domain}.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'How accurate is the TLD pricing data?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'We track prices from major registrars including Porkbun, Namecheap, GoDaddy, and Cloudflare. Prices are updated daily via automated scraping. We compare 886+ TLDs to help you find the best deals.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'What is Smart Pounce?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Smart Pounce is our auction aggregation feature. We scan GoDaddy, Sedo, NameJet, and other platforms to find domain auctions and analyze them for value. We don\'t handle payments — you bid directly on the platform.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'Is there a free plan?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Yes! Our Scout plan is free forever. You can monitor up to 5 domains with daily availability checks and access basic market insights.',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
/**
|
||||
* BreadcrumbList structured data generator
|
||||
*/
|
||||
export function generateBreadcrumbs(items: { name: string; url: string }[]) {
|
||||
return generateStructuredData('BreadcrumbList', {
|
||||
itemListElement: items.map((item, index) => ({
|
||||
'@type': 'ListItem',
|
||||
position: index + 1,
|
||||
name: item.name,
|
||||
item: item.url,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Default meta tags for all pages
|
||||
*/
|
||||
export const defaultMeta = {
|
||||
title: getPageTitle(),
|
||||
description: siteConfig.description,
|
||||
keywords: siteConfig.keywords.join(', '),
|
||||
author: siteConfig.author,
|
||||
robots: 'index, follow',
|
||||
'theme-color': siteConfig.themeColor,
|
||||
'og:site_name': siteConfig.name,
|
||||
'og:locale': siteConfig.locale,
|
||||
'twitter:card': 'summary_large_image',
|
||||
'twitter:site': siteConfig.twitter,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user