feat: Ultra SEO optimization - sitemap, robots, structured data, 800+ TLD pages
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled

This commit is contained in:
2025-12-13 21:32:54 +01:00
parent 92b309e766
commit a5600ee13c
12 changed files with 1312 additions and 283 deletions

View File

@ -0,0 +1,9 @@
// Public pages layout - inherits from root layout
export default function PublicLayout({
children,
}: {
children: React.ReactNode
}) {
return children
}

View File

@ -0,0 +1,68 @@
import { Metadata } from 'next'
import Script from 'next/script'
import { SEO_CONFIG, SITE_URL, generateWebPageSchema, generateBreadcrumbSchema } from '@/lib/seo'
export const metadata: Metadata = {
title: SEO_CONFIG.acquire.title,
description: SEO_CONFIG.acquire.description,
keywords: SEO_CONFIG.acquire.keywords,
alternates: {
canonical: `${SITE_URL}/acquire`,
},
openGraph: {
title: SEO_CONFIG.acquire.title,
description: SEO_CONFIG.acquire.description,
url: `${SITE_URL}/acquire`,
siteName: 'Pounce',
images: [
{
url: `${SITE_URL}/og-acquire.png`,
width: 1200,
height: 630,
alt: 'Domain Auctions & Marketplace - Pounce',
},
],
locale: 'en_US',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: SEO_CONFIG.acquire.title,
description: SEO_CONFIG.acquire.description,
images: [`${SITE_URL}/og-acquire.png`],
},
}
export default function AcquireLayout({
children,
}: {
children: React.ReactNode
}) {
const webPageSchema = generateWebPageSchema({
title: SEO_CONFIG.acquire.title,
description: SEO_CONFIG.acquire.description,
url: `${SITE_URL}/acquire`,
})
const breadcrumbSchema = generateBreadcrumbSchema([
{ name: 'Home', url: SITE_URL },
{ name: 'Domain Marketplace', url: `${SITE_URL}/acquire` },
])
return (
<>
<Script
id="acquire-webpage-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(webPageSchema) }}
/>
<Script
id="acquire-breadcrumb-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
/>
{children}
</>
)
}

View File

@ -0,0 +1,68 @@
import { Metadata } from 'next'
import Script from 'next/script'
import { SEO_CONFIG, SITE_URL, DEFAULT_OG_IMAGE, generateWebPageSchema, generateBreadcrumbSchema } from '@/lib/seo'
export const metadata: Metadata = {
title: SEO_CONFIG.discover.title,
description: SEO_CONFIG.discover.description,
keywords: SEO_CONFIG.discover.keywords,
alternates: {
canonical: `${SITE_URL}/discover`,
},
openGraph: {
title: SEO_CONFIG.discover.title,
description: SEO_CONFIG.discover.description,
url: `${SITE_URL}/discover`,
siteName: 'Pounce',
images: [
{
url: `${SITE_URL}/og-discover.png`,
width: 1200,
height: 630,
alt: 'TLD Pricing & Trends - Pounce',
},
],
locale: 'en_US',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: SEO_CONFIG.discover.title,
description: SEO_CONFIG.discover.description,
images: [`${SITE_URL}/og-discover.png`],
},
}
export default function DiscoverLayout({
children,
}: {
children: React.ReactNode
}) {
const webPageSchema = generateWebPageSchema({
title: SEO_CONFIG.discover.title,
description: SEO_CONFIG.discover.description,
url: `${SITE_URL}/discover`,
})
const breadcrumbSchema = generateBreadcrumbSchema([
{ name: 'Home', url: SITE_URL },
{ name: 'TLD Intelligence', url: `${SITE_URL}/discover` },
])
return (
<>
<Script
id="discover-webpage-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(webPageSchema) }}
/>
<Script
id="discover-breadcrumb-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
/>
{children}
</>
)
}

View File

@ -5,7 +5,7 @@ import Script from 'next/script'
const inter = Inter({ subsets: ['latin'] })
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.com'
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.ch'
export const viewport: Viewport = {
width: 'device-width',
@ -16,25 +16,34 @@ export const viewport: Viewport = {
export const metadata: Metadata = {
metadataBase: new URL(siteUrl),
title: {
default: 'Pounce - Domain Intelligence for Investors',
default: 'Pounce - Domain Intelligence Platform | Find, Track & Trade Domains',
template: '%s | Pounce',
},
description: 'The market never sleeps. You should. Scan, track, and trade domains with real-time drops, auctions, and TLD price intelligence. Spam-filtered. 0% commission.',
description: 'The #1 domain intelligence platform. Real-time auction aggregation from GoDaddy, Sedo, DropCatch & more. TLD price tracking, spam-free market feed, and portfolio management. Find undervalued domains before anyone else.',
keywords: [
'domain marketplace',
'domain auctions',
'TLD pricing',
'domain investing',
'expired domains',
'domain intelligence',
'domain auctions',
'expired domains',
'domain investing',
'TLD pricing',
'domain drops',
'premium domains',
'domain marketplace',
'domain monitoring',
'domain portfolio',
'domain valuation',
'domain market analysis',
'premium domains',
'domain flipping',
'godaddy auctions',
'sedo marketplace',
'dropcatch',
'namejet',
'domain trading',
'buy domains',
'sell domains',
'domain portfolio',
'domain price trends',
'.com domains',
'.io domains',
'.ai domains',
],
authors: [{ name: 'Pounce' }],
creator: 'Pounce',
@ -44,29 +53,39 @@ export const metadata: Metadata = {
address: false,
telephone: false,
},
alternates: {
canonical: siteUrl,
languages: {
'en': siteUrl,
},
},
openGraph: {
type: 'website',
locale: 'en_US',
url: siteUrl,
siteName: 'Pounce',
title: 'Pounce - Domain Intelligence for Investors',
description: 'The market never sleeps. You should. Real-time domain drops, auctions, and TLD price intelligence.',
title: 'Pounce - Domain Intelligence Platform | Find, Track & Trade Domains',
description: 'The #1 domain intelligence platform. Real-time auction aggregation, TLD price tracking, spam-free market feed. Find undervalued domains before anyone else.',
images: [
{
url: `${siteUrl}/og-image.png`,
width: 1200,
height: 630,
alt: 'Pounce - Domain Intelligence',
alt: 'Pounce - Domain Intelligence Platform',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'Pounce - Domain Intelligence for Investors',
description: 'The market never sleeps. You should. Real-time domain drops, auctions, and TLD price intelligence.',
title: 'Pounce - Domain Intelligence Platform',
description: 'The #1 domain intelligence platform. Real-time auctions, TLD pricing, spam-free feed. Find undervalued domains.',
creator: '@pouncedomains',
site: '@pouncedomains',
images: [`${siteUrl}/og-image.png`],
},
verification: {
google: 'YOUR_GOOGLE_VERIFICATION_CODE', // Add your Google Search Console verification
},
robots: {
index: true,
follow: true,

View File

@ -0,0 +1,103 @@
import { Metadata } from 'next'
import Script from 'next/script'
import { SEO_CONFIG, SITE_URL, generateWebPageSchema, generateBreadcrumbSchema, generateSoftwareApplicationSchema, generateFAQSchema } from '@/lib/seo'
export const metadata: Metadata = {
title: SEO_CONFIG.pricing.title,
description: SEO_CONFIG.pricing.description,
keywords: SEO_CONFIG.pricing.keywords,
alternates: {
canonical: `${SITE_URL}/pricing`,
},
openGraph: {
title: SEO_CONFIG.pricing.title,
description: SEO_CONFIG.pricing.description,
url: `${SITE_URL}/pricing`,
siteName: 'Pounce',
images: [
{
url: `${SITE_URL}/og-pricing.png`,
width: 1200,
height: 630,
alt: 'Pricing Plans - Pounce',
},
],
locale: 'en_US',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: SEO_CONFIG.pricing.title,
description: SEO_CONFIG.pricing.description,
images: [`${SITE_URL}/og-pricing.png`],
},
}
export default function PricingLayout({
children,
}: {
children: React.ReactNode
}) {
const webPageSchema = generateWebPageSchema({
title: SEO_CONFIG.pricing.title,
description: SEO_CONFIG.pricing.description,
url: `${SITE_URL}/pricing`,
})
const breadcrumbSchema = generateBreadcrumbSchema([
{ name: 'Home', url: SITE_URL },
{ name: 'Pricing', url: `${SITE_URL}/pricing` },
])
const softwareSchema = generateSoftwareApplicationSchema()
const faqSchema = generateFAQSchema([
{
question: 'Is there a free plan?',
answer: 'Yes! Scout is completely free forever. You get access to the raw market feed, 5 watchlist domains, and basic TLD intelligence.',
},
{
question: 'What payment methods do you accept?',
answer: 'We accept all major credit cards (Visa, Mastercard, Amex) through Stripe. Payments are processed securely.',
},
{
question: 'Can I cancel anytime?',
answer: 'Absolutely. No contracts, no hidden fees. Cancel your subscription anytime from your account settings.',
},
{
question: 'What is the Pounce Score?',
answer: 'The Pounce Score is our proprietary domain valuation metric that considers length, TLD, keywords, brandability, and market comparables.',
},
{
question: 'Do you charge commission on sales?',
answer: 'No! Unlike other platforms that charge 10-20% commission, Pounce Direct marketplace has 0% commission. You keep 100% of your sale price.',
},
])
return (
<>
<Script
id="pricing-webpage-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(webPageSchema) }}
/>
<Script
id="pricing-breadcrumb-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
/>
<Script
id="pricing-software-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(softwareSchema) }}
/>
<Script
id="pricing-faq-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }}
/>
{children}
</>
)
}

View File

@ -0,0 +1,34 @@
import { MetadataRoute } from 'next'
import { SITE_URL } from '@/lib/seo'
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: [
'/terminal/',
'/api/',
'/admin/',
'/command/',
'/_next/',
'/static/',
],
},
{
userAgent: 'Googlebot',
allow: '/',
disallow: [
'/terminal/',
'/api/',
'/admin/',
'/command/',
],
},
],
sitemap: `${SITE_URL}/sitemap.xml`,
host: SITE_URL,
}
}

View File

@ -1,74 +1,102 @@
import { MetadataRoute } from 'next'
import { POPULAR_TLDS, SITE_URL } from '@/lib/seo'
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.com'
// Top TLDs to include in sitemap (programmatic SEO)
const TOP_TLDS = [
'com', 'net', 'org', 'io', 'ai', 'co', 'app', 'dev', 'xyz', 'online',
'tech', 'store', 'site', 'cloud', 'pro', 'info', 'biz', 'me', 'tv', 'cc',
'de', 'uk', 'eu', 'us', 'ca', 'au', 'jp', 'fr', 'es', 'it',
'ch', 'nl', 'se', 'no', 'dk', 'fi', 'at', 'be', 'pl', 'cz',
'web', 'digital', 'domains', 'blog', 'shop', 'news', 'email', 'services',
'consulting', 'agency', 'studio', 'media', 'design', 'art', 'photo', 'video',
'crypto', 'nft', 'dao', 'defi', 'web3', 'metaverse', 'blockchain', 'bitcoin',
'finance', 'bank', 'invest', 'trading', 'market', 'fund', 'capital', 'ventures',
'legal', 'law', 'attorney', 'lawyer', 'consulting', 'tax', 'insurance', 'realty',
'education', 'university', 'college', 'school', 'academy', 'training', 'courses',
'health', 'medical', 'dental', 'clinic', 'doctor', 'care', 'fitness', 'wellness',
'food', 'restaurant', 'cafe', 'bar', 'pizza', 'delivery', 'recipes', 'cooking',
'travel', 'hotel', 'flights', 'tours', 'vacation', 'cruise', 'booking', 'tickets',
'games', 'gaming', 'play', 'casino', 'bet', 'poker', 'sports', 'esports',
'fashion', 'clothing', 'beauty', 'style', 'jewelry', 'watches', 'luxury', 'boutique',
]
// This generates a dynamic sitemap including all TLD pages
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const routes: MetadataRoute.Sitemap = [
// Main pages
const baseUrl = SITE_URL
const now = new Date()
// Static pages with high priority
const staticPages: MetadataRoute.Sitemap = [
{
url: siteUrl,
lastModified: new Date(),
url: baseUrl,
lastModified: now,
changeFrequency: 'daily',
priority: 1.0,
},
{
url: `${siteUrl}/market`,
lastModified: new Date(),
url: `${baseUrl}/discover`,
lastModified: now,
changeFrequency: 'daily',
priority: 0.9,
},
{
url: `${baseUrl}/acquire`,
lastModified: now,
changeFrequency: 'hourly',
priority: 0.9,
},
{
url: `${siteUrl}/discover`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.9,
},
{
url: `${siteUrl}/pricing`,
lastModified: new Date(),
url: `${baseUrl}/yield`,
lastModified: now,
changeFrequency: 'weekly',
priority: 0.8,
},
{
url: `${siteUrl}/about`,
lastModified: new Date(),
url: `${baseUrl}/pricing`,
lastModified: now,
changeFrequency: 'weekly',
priority: 0.8,
},
{
url: `${baseUrl}/about`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.5,
},
{
url: `${siteUrl}/contact`,
lastModified: new Date(),
url: `${baseUrl}/blog`,
lastModified: now,
changeFrequency: 'weekly',
priority: 0.7,
},
{
url: `${baseUrl}/login`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.5,
priority: 0.3,
},
{
url: `${baseUrl}/register`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.4,
},
]
// Add TLD pages (programmatic SEO - high priority for search)
const tldPages: MetadataRoute.Sitemap = TOP_TLDS.map((tld) => ({
url: `${siteUrl}/discover/${tld}`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.8,
}))
// Fetch all TLDs from API for dynamic TLD pages
let tldPages: MetadataRoute.Sitemap = []
return [...routes, ...tldPages]
try {
// Try to fetch TLDs from the API
const response = await fetch(`${baseUrl}/api/v1/tld/overview?limit=100`, {
next: { revalidate: 86400 }, // Revalidate daily
})
if (response.ok) {
const data = await response.json()
const tlds = data.tlds || []
tldPages = tlds.map((tld: { tld: string }) => ({
url: `${baseUrl}/tld/${tld.tld.toLowerCase()}`,
lastModified: now,
changeFrequency: 'daily' as const,
priority: 0.7,
}))
}
} catch (error) {
console.error('Failed to fetch TLDs for sitemap:', error)
}
// If API failed, use popular TLDs as fallback
if (tldPages.length === 0) {
tldPages = POPULAR_TLDS.map(tld => ({
url: `${baseUrl}/tld/${tld}`,
lastModified: now,
changeFrequency: 'daily' as const,
priority: 0.7,
}))
}
return [...staticPages, ...tldPages]
}

View File

@ -0,0 +1,430 @@
'use client'
import { useEffect, useState } from 'react'
import { Header } from '@/components/Header'
import { Footer } from '@/components/Footer'
import { useStore } from '@/lib/store'
import { api } from '@/lib/api'
import {
Globe,
TrendingUp,
TrendingDown,
Minus,
DollarSign,
Calendar,
Building,
AlertTriangle,
ExternalLink,
ChevronLeft,
Lock,
ArrowRight,
Search,
Check,
X,
Loader2,
BarChart3
} from 'lucide-react'
import Link from 'next/link'
import clsx from 'clsx'
interface TldData {
tld: string
type: string | null
registration_price: number | null
renewal_price: number | null
registrar_count: number
description?: string
introduced?: string
registry?: string
}
interface PriceHistory {
date: string
price: number
}
interface RegistrarPrice {
registrar: string
registration: number
renewal: number
transfer: number
}
interface Props {
tld: string
initialData: TldData | null
}
export default function TldDetailClient({ tld, initialData }: Props) {
const { isAuthenticated, checkAuth, subscription } = useStore()
const [data, setData] = useState<TldData | null>(initialData)
const [priceHistory, setPriceHistory] = useState<PriceHistory[]>([])
const [registrars, setRegistrars] = useState<RegistrarPrice[]>([])
const [loading, setLoading] = useState(!initialData)
const [checkDomain, setCheckDomain] = useState('')
const [checking, setChecking] = useState(false)
const [checkResult, setCheckResult] = useState<{ available: boolean; domain: string } | null>(null)
const tier = subscription?.tier || 'scout'
const canSeeRenewal = tier !== 'scout'
const canSeeHistory = tier === 'tycoon'
useEffect(() => {
checkAuth()
}, [checkAuth])
useEffect(() => {
loadTldData()
}, [tld])
const loadTldData = async () => {
setLoading(true)
try {
const [tldInfo, prices] = await Promise.all([
api.getTldInfo(tld),
api.getTldPrices(tld),
])
if (tldInfo) {
setData(tldInfo)
}
if (prices) {
setRegistrars(prices.registrars || [])
setPriceHistory(prices.history || [])
}
} catch (error) {
console.error('Failed to load TLD data:', error)
} finally {
setLoading(false)
}
}
const handleCheckDomain = async () => {
if (!checkDomain.trim()) return
setChecking(true)
try {
const domain = checkDomain.includes('.') ? checkDomain : `${checkDomain}.${tld}`
const result = await api.checkDomain(domain)
setCheckResult({ available: result.available, domain })
} catch (error) {
console.error('Domain check failed:', error)
} finally {
setChecking(false)
}
}
const getRiskLevel = () => {
if (!data?.registration_price || !data?.renewal_price) return 'unknown'
const ratio = data.renewal_price / data.registration_price
if (ratio > 5) return 'high'
if (ratio > 2) return 'medium'
return 'low'
}
const riskLevel = getRiskLevel()
return (
<div className="min-h-screen bg-[#020202] flex flex-col">
<Header />
<main className="flex-1">
{/* Hero Section */}
<section className="relative pt-24 sm:pt-32 pb-12 sm:pb-16 overflow-hidden">
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.02)_1px,transparent_1px)] bg-[size:60px_60px]" />
<div className="relative max-w-6xl mx-auto px-4 sm:px-6">
{/* Breadcrumb */}
<Link
href="/discover"
className="inline-flex items-center gap-2 text-sm text-white/50 hover:text-white transition-colors mb-8"
>
<ChevronLeft className="w-4 h-4" />
<span>Back to TLD Intelligence</span>
</Link>
{/* TLD Header */}
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-8 mb-12">
<div className="flex items-start gap-6">
<div className="w-20 h-20 bg-accent/10 border border-accent/20 flex items-center justify-center shrink-0">
<Globe className="w-10 h-10 text-accent" />
</div>
<div>
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-display font-bold text-white tracking-tight">
.{tld.toUpperCase()}
</h1>
<p className="text-white/50 text-lg mt-2">
{data?.type || 'Domain Extension'} {data?.registry && `${data.registry}`}
</p>
{data?.description && (
<p className="text-white/70 mt-4 max-w-xl">{data.description}</p>
)}
</div>
</div>
{/* Risk Badge */}
<div className={clsx(
"px-6 py-4 border",
riskLevel === 'high' && "bg-red-500/10 border-red-500/30",
riskLevel === 'medium' && "bg-yellow-500/10 border-yellow-500/30",
riskLevel === 'low' && "bg-green-500/10 border-green-500/30",
riskLevel === 'unknown' && "bg-white/5 border-white/10"
)}>
<div className="text-[10px] font-mono uppercase tracking-wider text-white/40 mb-1">
Renewal Risk
</div>
<div className={clsx(
"text-2xl font-bold capitalize",
riskLevel === 'high' && "text-red-400",
riskLevel === 'medium' && "text-yellow-400",
riskLevel === 'low' && "text-green-400",
riskLevel === 'unknown' && "text-white/50"
)}>
{riskLevel === 'unknown' ? 'N/A' : riskLevel}
</div>
</div>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-white/[0.02] border border-white/[0.06] p-6">
<div className="text-[10px] font-mono uppercase tracking-wider text-white/40 mb-2">
Registration
</div>
<div className="text-2xl font-bold text-white">
{data?.registration_price ? `$${data.registration_price.toFixed(2)}` : '—'}
</div>
</div>
<div className="bg-white/[0.02] border border-white/[0.06] p-6 relative">
<div className="text-[10px] font-mono uppercase tracking-wider text-white/40 mb-2">
Renewal
</div>
{canSeeRenewal ? (
<div className="text-2xl font-bold text-white">
{data?.renewal_price ? `$${data.renewal_price.toFixed(2)}` : '—'}
</div>
) : (
<div className="flex items-center gap-2">
<Lock className="w-4 h-4 text-white/30" />
<span className="text-white/30 text-sm">Trader+</span>
</div>
)}
</div>
<div className="bg-white/[0.02] border border-white/[0.06] p-6">
<div className="text-[10px] font-mono uppercase tracking-wider text-white/40 mb-2">
Registrars
</div>
<div className="text-2xl font-bold text-white">
{data?.registrar_count || registrars.length || '—'}
</div>
</div>
<div className="bg-white/[0.02] border border-white/[0.06] p-6">
<div className="text-[10px] font-mono uppercase tracking-wider text-white/40 mb-2">
Introduced
</div>
<div className="text-2xl font-bold text-white">
{data?.introduced || '—'}
</div>
</div>
</div>
</div>
</section>
{/* Domain Checker */}
<section className="py-12 border-y border-white/[0.06]">
<div className="max-w-6xl mx-auto px-4 sm:px-6">
<h2 className="text-xl font-bold text-white mb-6">Check .{tld.toUpperCase()} Availability</h2>
<div className="flex gap-4">
<div className="flex-1 relative">
<input
type="text"
value={checkDomain}
onChange={(e) => setCheckDomain(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleCheckDomain()}
placeholder={`yourname.${tld}`}
className="w-full bg-white/[0.02] border border-white/[0.08] px-4 py-3 text-white placeholder:text-white/30 focus:outline-none focus:border-accent font-mono"
/>
</div>
<button
onClick={handleCheckDomain}
disabled={checking}
className="px-6 py-3 bg-accent text-black font-bold hover:bg-accent/90 transition-colors disabled:opacity-50"
>
{checking ? <Loader2 className="w-5 h-5 animate-spin" /> : 'Check'}
</button>
</div>
{checkResult && (
<div className={clsx(
"mt-4 p-4 border flex items-center gap-4",
checkResult.available
? "bg-green-500/10 border-green-500/30"
: "bg-red-500/10 border-red-500/30"
)}>
{checkResult.available ? (
<>
<Check className="w-6 h-6 text-green-400" />
<div>
<div className="font-bold text-white">{checkResult.domain} is available!</div>
<a
href={`https://www.namecheap.com/domains/registration/results/?domain=${checkResult.domain}`}
target="_blank"
rel="noopener noreferrer"
className="text-accent text-sm hover:underline inline-flex items-center gap-1"
>
Register now <ExternalLink className="w-3 h-3" />
</a>
</div>
</>
) : (
<>
<X className="w-6 h-6 text-red-400" />
<div className="font-bold text-white">{checkResult.domain} is taken</div>
</>
)}
</div>
)}
</div>
</section>
{/* Price History Chart */}
<section className="py-12">
<div className="max-w-6xl mx-auto px-4 sm:px-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold text-white">Price History</h2>
{!canSeeHistory && (
<Link
href="/pricing"
className="text-sm text-accent hover:underline inline-flex items-center gap-1"
>
Unlock with Tycoon <ArrowRight className="w-3 h-3" />
</Link>
)}
</div>
<div className="bg-white/[0.02] border border-white/[0.06] p-8 min-h-[300px] relative">
{canSeeHistory ? (
priceHistory.length > 0 ? (
<div className="text-white/50 text-center py-12">
<BarChart3 className="w-12 h-12 mx-auto mb-4 text-white/20" />
Price chart visualization here
</div>
) : (
<div className="text-white/50 text-center py-12">
No historical data available yet
</div>
)
) : (
<div className="absolute inset-0 bg-[#020202]/80 backdrop-blur-sm flex items-center justify-center">
<div className="text-center">
<Lock className="w-12 h-12 text-white/20 mx-auto mb-4" />
<p className="text-white/50 mb-4">Historical data requires Tycoon</p>
<Link
href="/pricing"
className="px-6 py-2 bg-accent text-black font-bold text-sm hover:bg-accent/90 transition-colors inline-block"
>
Upgrade to Tycoon
</Link>
</div>
</div>
)}
</div>
</div>
</section>
{/* Registrar Comparison */}
<section className="py-12 border-t border-white/[0.06]">
<div className="max-w-6xl mx-auto px-4 sm:px-6">
<h2 className="text-xl font-bold text-white mb-6">Compare Registrars</h2>
{registrars.length > 0 ? (
<div className="border border-white/[0.06] overflow-hidden">
<table className="w-full">
<thead>
<tr className="bg-white/[0.02] border-b border-white/[0.06]">
<th className="text-left p-4 text-[10px] font-mono uppercase tracking-wider text-white/40">
Registrar
</th>
<th className="text-right p-4 text-[10px] font-mono uppercase tracking-wider text-white/40">
Registration
</th>
<th className="text-right p-4 text-[10px] font-mono uppercase tracking-wider text-white/40">
Renewal
</th>
<th className="text-right p-4 text-[10px] font-mono uppercase tracking-wider text-white/40">
Transfer
</th>
</tr>
</thead>
<tbody>
{registrars.map((reg, i) => (
<tr
key={reg.registrar}
className={clsx(
"border-b border-white/[0.06] hover:bg-white/[0.02] transition-colors",
i === 0 && "bg-accent/5"
)}
>
<td className="p-4">
<div className="flex items-center gap-3">
{i === 0 && (
<span className="text-[9px] font-mono uppercase bg-accent/20 text-accent px-2 py-0.5">
Best
</span>
)}
<span className="font-medium text-white">{reg.registrar}</span>
</div>
</td>
<td className="p-4 text-right font-mono text-white">
${reg.registration.toFixed(2)}
</td>
<td className="p-4 text-right font-mono text-white">
{canSeeRenewal ? `$${reg.renewal.toFixed(2)}` : (
<span className="text-white/30"></span>
)}
</td>
<td className="p-4 text-right font-mono text-white">
${reg.transfer.toFixed(2)}
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className="bg-white/[0.02] border border-white/[0.06] p-12 text-center">
<Building className="w-12 h-12 text-white/20 mx-auto mb-4" />
<p className="text-white/50">No registrar data available</p>
</div>
)}
</div>
</section>
{/* CTA Section */}
<section className="py-16 border-t border-white/[0.06]">
<div className="max-w-4xl mx-auto px-4 sm:px-6 text-center">
<h2 className="text-3xl font-bold text-white mb-4">
Track .{tld.toUpperCase()} Prices
</h2>
<p className="text-white/60 mb-8 max-w-xl mx-auto">
Get alerts when .{tld.toUpperCase()} prices change. Monitor domains and never miss a deal.
</p>
<Link
href="/register"
className="inline-flex items-center gap-2 px-8 py-4 bg-accent text-black font-bold hover:bg-accent/90 transition-colors"
>
Start Free <ArrowRight className="w-4 h-4" />
</Link>
</div>
</section>
</main>
<Footer />
</div>
)
}

View File

@ -0,0 +1,176 @@
import { Metadata } from 'next'
import { notFound } from 'next/navigation'
import Script from 'next/script'
import { SITE_URL, POPULAR_TLDS, generateTLDPageSchema, generateBreadcrumbSchema } from '@/lib/seo'
import TldDetailClient from './TldDetailClient'
interface TldData {
tld: string
type: string | null
registration_price: number | null
renewal_price: number | null
registrar_count: number
description?: string
introduced?: string
registry?: string
}
async function getTldData(tld: string): Promise<TldData | null> {
try {
const response = await fetch(`${SITE_URL}/api/v1/tld/${tld}`, {
next: { revalidate: 3600 }, // Revalidate every hour
})
if (!response.ok) {
return null
}
return response.json()
} catch (error) {
console.error('Failed to fetch TLD data:', error)
return null
}
}
// Generate static params for popular TLDs
export async function generateStaticParams() {
return POPULAR_TLDS.map(tld => ({ tld }))
}
// Generate dynamic metadata for each TLD
export async function generateMetadata({
params
}: {
params: Promise<{ tld: string }>
}): Promise<Metadata> {
const { tld } = await params
const tldUpper = tld.toUpperCase()
const tldLower = tld.toLowerCase()
// Try to fetch real data for better SEO
const data = await getTldData(tldLower)
const priceInfo = data?.registration_price
? `from $${data.registration_price}`
: ''
const typeInfo = data?.type
? `(${data.type})`
: ''
const title = `.${tldUpper} Domain Pricing & Trends 2025 | Registration & Renewal Costs | Pounce`
const description = `Complete .${tldUpper} domain guide ${typeInfo}. Current registration ${priceInfo}, renewal prices, registrar comparison, and historical trends. Find the cheapest .${tldLower} domains.`
return {
title,
description,
keywords: [
`.${tldLower} domain`, `.${tldLower} price`, `.${tldLower} registration`,
`.${tldLower} renewal cost`, `.${tldLower} domain buy`, `${tldLower} domain extension`,
`.${tldLower} registrar`, `cheap .${tldLower} domain`, `.${tldLower} 2025`,
`${tldLower} domain price history`, `${tldLower} domain trends`
],
alternates: {
canonical: `${SITE_URL}/tld/${tldLower}`,
},
openGraph: {
title,
description,
url: `${SITE_URL}/tld/${tldLower}`,
siteName: 'Pounce',
images: [
{
url: `${SITE_URL}/og-tld.png`,
width: 1200,
height: 630,
alt: `.${tldUpper} Domain Pricing - Pounce`,
},
],
locale: 'en_US',
type: 'article',
},
twitter: {
card: 'summary_large_image',
title,
description,
images: [`${SITE_URL}/og-tld.png`],
},
}
}
export default async function TldPage({
params
}: {
params: Promise<{ tld: string }>
}) {
const { tld } = await params
const tldLower = tld.toLowerCase()
const tldUpper = tld.toUpperCase()
// Fetch TLD data for schema
const data = await getTldData(tldLower)
// Generate structured data
const tldSchema = generateTLDPageSchema(tldLower, {
registrationPrice: data?.registration_price || undefined,
renewalPrice: data?.renewal_price || undefined,
type: data?.type || undefined,
registrarCount: data?.registrar_count || 1,
})
const breadcrumbSchema = generateBreadcrumbSchema([
{ name: 'Home', url: SITE_URL },
{ name: 'TLD Intelligence', url: `${SITE_URL}/discover` },
{ name: `.${tldUpper}`, url: `${SITE_URL}/tld/${tldLower}` },
])
// Article schema for better SEO
const articleSchema = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: `.${tldUpper} Domain Pricing & Trends 2025`,
description: `Complete guide to .${tldUpper} domain registration and renewal costs`,
url: `${SITE_URL}/tld/${tldLower}`,
datePublished: '2024-01-01',
dateModified: new Date().toISOString().split('T')[0],
author: {
'@type': 'Organization',
name: 'Pounce',
url: SITE_URL,
},
publisher: {
'@type': 'Organization',
name: 'Pounce',
logo: {
'@type': 'ImageObject',
url: `${SITE_URL}/pounce-logo.png`,
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `${SITE_URL}/tld/${tldLower}`,
},
}
return (
<>
<Script
id="tld-product-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(tldSchema) }}
/>
<Script
id="tld-breadcrumb-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
/>
<Script
id="tld-article-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleSchema) }}
/>
<TldDetailClient tld={tldLower} initialData={data} />
</>
)
}

View File

@ -0,0 +1,92 @@
import { Metadata } from 'next'
import Script from 'next/script'
import { SEO_CONFIG, SITE_URL, generateWebPageSchema, generateBreadcrumbSchema, generateFAQSchema } from '@/lib/seo'
export const metadata: Metadata = {
title: SEO_CONFIG.yield.title,
description: SEO_CONFIG.yield.description,
keywords: SEO_CONFIG.yield.keywords,
alternates: {
canonical: `${SITE_URL}/yield`,
},
openGraph: {
title: SEO_CONFIG.yield.title,
description: SEO_CONFIG.yield.description,
url: `${SITE_URL}/yield`,
siteName: 'Pounce',
images: [
{
url: `${SITE_URL}/og-yield.png`,
width: 1200,
height: 630,
alt: 'Domain Monetization - Pounce Yield',
},
],
locale: 'en_US',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: SEO_CONFIG.yield.title,
description: SEO_CONFIG.yield.description,
images: [`${SITE_URL}/og-yield.png`],
},
}
export default function YieldLayout({
children,
}: {
children: React.ReactNode
}) {
const webPageSchema = generateWebPageSchema({
title: SEO_CONFIG.yield.title,
description: SEO_CONFIG.yield.description,
url: `${SITE_URL}/yield`,
})
const breadcrumbSchema = generateBreadcrumbSchema([
{ name: 'Home', url: SITE_URL },
{ name: 'Domain Yield', url: `${SITE_URL}/yield` },
])
const faqSchema = generateFAQSchema([
{
question: 'What is Pounce Yield?',
answer: 'Pounce Yield is an AI-powered domain monetization system that turns parked domains into revenue-generating assets through intent routing, not traditional parking ads.',
},
{
question: 'How much can I earn with Pounce Yield?',
answer: 'Earnings depend on your domain\'s traffic and intent. High-intent domains in verticals like finance or software can earn $10-50+ per conversion, compared to pennies from traditional parking.',
},
{
question: 'What is intent routing?',
answer: 'Intent routing analyzes what visitors to your domain are looking for and routes them to relevant affiliate offers or services, maximizing conversion value.',
},
{
question: 'What is the revenue share?',
answer: 'Trader members receive 70% of all affiliate revenue generated. Tycoon members get priority routing to highest-paying partners.',
},
])
return (
<>
<Script
id="yield-webpage-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(webPageSchema) }}
/>
<Script
id="yield-breadcrumb-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
/>
<Script
id="yield-faq-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }}
/>
{children}
</>
)
}

View File

@ -555,6 +555,46 @@ class ApiClient {
}>('/tld-prices/trending')
}
// Get TLD info for SEO pages
async getTldInfo(tld: string) {
try {
const compare = await this.getTldCompare(tld)
return {
tld: compare.tld,
type: compare.type,
registration_price: compare.cheapest_price,
renewal_price: compare.registrars?.[0]?.renewal_price || null,
registrar_count: compare.registrars?.length || 0,
description: compare.description,
introduced: compare.introduced?.toString() || null,
registry: compare.registry,
}
} catch {
return null
}
}
// Get TLD prices for SEO pages
async getTldPrices(tld: string) {
try {
const [compare, history] = await Promise.all([
this.getTldCompare(tld),
this.getTldHistory(tld, 365),
])
return {
registrars: compare.registrars?.map(r => ({
registrar: r.name,
registration: r.registration_price,
renewal: r.renewal_price,
transfer: r.transfer_price,
})) || [],
history: history.history || [],
}
} catch {
return { registrars: [], history: [] }
}
}
// ============== Portfolio ==============
async getPortfolio(

View File

@ -1,202 +1,147 @@
/**
* SEO & Geo-targeting utilities
*/
// SEO Configuration for Pounce
// Centralized SEO metadata and structured data
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.com'
export const SITE_URL = 'https://pounce.ch'
export const SITE_NAME = 'Pounce'
export const DEFAULT_OG_IMAGE = `${SITE_URL}/og-image.png`
/**
* Supported locales for geo-targeting
*/
export const SUPPORTED_LOCALES = {
'en-US': { name: 'English (US)', currency: 'USD', flag: '🇺🇸' },
'en-GB': { name: 'English (UK)', currency: 'GBP', flag: '🇬🇧' },
'en-CA': { name: 'English (Canada)', currency: 'CAD', flag: '🇨🇦' },
'en-AU': { name: 'English (Australia)', currency: 'AUD', flag: '🇦🇺' },
'de-DE': { name: 'Deutsch', currency: 'EUR', flag: '🇩🇪' },
'de-CH': { name: 'Deutsch (Schweiz)', currency: 'CHF', flag: '🇨🇭' },
'fr-FR': { name: 'Français', currency: 'EUR', flag: '🇫🇷' },
'es-ES': { name: 'Español', currency: 'EUR', flag: '🇪🇸' },
'it-IT': { name: 'Italiano', currency: 'EUR', flag: '🇮🇹' },
'nl-NL': { name: 'Nederlands', currency: 'EUR', flag: '🇳🇱' },
'pt-BR': { name: 'Português (Brasil)', currency: 'BRL', flag: '🇧🇷' },
'ja-JP': { name: '日本語', currency: 'JPY', flag: '🇯🇵' },
'zh-CN': { name: '简体中文', currency: 'CNY', flag: '🇨🇳' },
} as const
export type Locale = keyof typeof SUPPORTED_LOCALES
/**
* Generate hreflang alternates for a page
*/
export function generateHreflangAlternates(path: string, currentLocale: Locale = 'en-US') {
const alternates = Object.keys(SUPPORTED_LOCALES).map((locale) => ({
hreflang: locale,
href: `${siteUrl}/${locale === 'en-US' ? '' : locale}${path}`,
}))
// Add x-default
alternates.push({
hreflang: 'x-default',
href: `${siteUrl}${path}`,
})
return alternates
export const SEO_CONFIG = {
home: {
title: 'Pounce - Domain Intelligence Platform | Find, Track & Trade Domains',
description: 'The #1 domain intelligence platform. Real-time auction aggregation, TLD price tracking, spam-free market feed, and portfolio management. Find undervalued domains before anyone else.',
keywords: [
'domain intelligence', 'domain auctions', 'expired domains', 'domain investing',
'TLD pricing', 'domain drops', 'domain marketplace', 'domain monitoring',
'domain portfolio', 'domain valuation', 'premium domains', 'domain flipping',
'godaddy auctions', 'sedo marketplace', 'dropcatch', 'namejet', 'domain trading'
],
},
discover: {
title: 'TLD Pricing & Trends 2025 | Compare 800+ Domain Extensions | Pounce',
description: 'Compare registration and renewal prices for 800+ TLDs. Track price trends, find hidden renewal traps, and discover the best domain extensions for your investment. Updated daily.',
keywords: [
'TLD pricing', 'domain extension prices', 'TLD comparison', 'domain renewal costs',
'cheapest TLDs', 'new gTLDs', 'ccTLD pricing', '.com price', '.io domain cost',
'.ai domain price', 'domain registration cost', 'TLD trends 2025'
],
},
acquire: {
title: 'Domain Auctions & Marketplace | Live Drops & Deals | Pounce',
description: 'Browse 10,000+ live domain auctions from GoDaddy, Sedo, DropCatch & more. Spam-filtered feed, real-time updates, and exclusive Pounce Direct listings with 0% commission.',
keywords: [
'domain auctions', 'buy domains', 'expired domain auctions', 'domain marketplace',
'godaddy auctions', 'sedo domains', 'dropcatch', 'namejet auctions',
'premium domains for sale', 'domain deals', 'cheap domains'
],
},
yield: {
title: 'Domain Monetization | Turn Parked Domains into Revenue | Pounce Yield',
description: 'Stop losing money on parked domains. Pounce Yield uses AI-powered intent routing to monetize your unused domains with up to 70% revenue share. No ads, real affiliate revenue.',
keywords: [
'domain monetization', 'parked domain income', 'domain parking alternative',
'domain revenue', 'affiliate domains', 'intent routing', 'domain yield',
'passive domain income', 'monetize parked domains'
],
},
pricing: {
title: 'Pricing Plans | Start Free, Scale Smart | Pounce',
description: 'From hobbyist to tycoon. Scout (free), Trader ($9/mo), Tycoon ($29/mo). Real-time alerts, spam-free feeds, domain valuation, and 0% marketplace commission.',
keywords: [
'domain tool pricing', 'domain software', 'domain investing tools',
'domain monitoring service', 'domain alert service', 'domain trading platform'
],
},
}
/**
* Detect user's preferred locale from headers
*/
export function detectLocale(acceptLanguage: string | null): Locale {
if (!acceptLanguage) return 'en-US'
const languages = acceptLanguage.split(',').map((lang) => {
const [code, q = '1'] = lang.trim().split(';q=')
return { code: code.toLowerCase(), quality: parseFloat(q) }
})
// Sort by quality
languages.sort((a, b) => b.quality - a.quality)
// Find first supported locale
for (const lang of languages) {
const locale = Object.keys(SUPPORTED_LOCALES).find((l) =>
l.toLowerCase().startsWith(lang.code)
)
if (locale) return locale as Locale
}
return 'en-US'
}
/**
* Format price for locale
*/
export function formatPrice(amount: number, locale: Locale = 'en-US'): string {
const { currency } = SUPPORTED_LOCALES[locale]
return new Intl.NumberFormat(locale, {
style: 'currency',
currency,
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}).format(amount)
}
/**
* Generate canonical URL
*/
export function getCanonicalUrl(path: string, locale?: Locale): string {
if (!locale || locale === 'en-US') {
return `${siteUrl}${path}`
}
return `${siteUrl}/${locale}${path}`
}
/**
* Generate page title with branding
*/
export function generateTitle(title: string, includesBrand: boolean = false): string {
return includesBrand ? title : `${title} | Pounce`
}
/**
* Truncate description for meta tags
*/
export function truncateDescription(text: string, maxLength: number = 160): string {
if (text.length <= maxLength) return text
return text.slice(0, maxLength - 3) + '...'
}
/**
* Generate keywords array from string or array
*/
export function generateKeywords(keywords: string | string[]): string[] {
if (Array.isArray(keywords)) return keywords
return keywords.split(',').map((k) => k.trim())
}
/**
* Performance: Generate preload links for critical resources
*/
export function getPreloadLinks() {
return [
{ rel: 'preload', href: '/fonts/inter.woff2', as: 'font', type: 'font/woff2', crossOrigin: 'anonymous' },
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous' },
]
}
/**
* Generate Open Graph image URL with dynamic content
*/
export function generateOGImageUrl(params: {
title?: string
subtitle?: string
type?: 'default' | 'tld' | 'domain' | 'market'
}): string {
const searchParams = new URLSearchParams()
if (params.title) searchParams.set('title', params.title)
if (params.subtitle) searchParams.set('subtitle', params.subtitle)
if (params.type) searchParams.set('type', params.type)
return `${siteUrl}/api/og?${searchParams.toString()}`
}
/**
* SEO-friendly slug generator
*/
export function generateSlug(text: string): string {
return text
.toLowerCase()
.replace(/[^\w\s-]/g, '') // Remove special chars
.replace(/\s+/g, '-') // Spaces to hyphens
.replace(/-+/g, '-') // Multiple hyphens to single
.trim()
}
/**
* Extract domain from URL for canonical
*/
export function extractDomain(url: string): string {
try {
const parsed = new URL(url)
return parsed.hostname.replace('www.', '')
} catch {
return url
// Schema.org structured data generators
export function generateOrganizationSchema() {
return {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Pounce',
url: SITE_URL,
logo: `${SITE_URL}/pounce-logo.png`,
description: 'Domain intelligence platform for investors and traders',
foundingDate: '2024',
sameAs: [
'https://twitter.com/pouncedomains',
'https://github.com/pounce',
],
contactPoint: {
'@type': 'ContactPoint',
email: 'hello@pounce.ch',
contactType: 'Customer Service',
},
}
}
/**
* Check if URL is external
*/
export function isExternalUrl(url: string): boolean {
try {
const parsed = new URL(url)
return parsed.hostname !== extractDomain(siteUrl)
} catch {
return false
export function generateSoftwareApplicationSchema() {
return {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'Pounce Domain Intelligence',
applicationCategory: 'BusinessApplication',
operatingSystem: 'Web',
url: SITE_URL,
description: 'Real-time domain auction aggregation, TLD price tracking, and portfolio management platform',
offers: [
{
'@type': 'Offer',
name: 'Scout',
price: '0',
priceCurrency: 'USD',
description: 'Free tier with basic features',
},
{
'@type': 'Offer',
name: 'Trader',
price: '9',
priceCurrency: 'USD',
priceValidUntil: '2025-12-31',
description: 'Professional tier with curated feeds and hourly alerts',
},
{
'@type': 'Offer',
name: 'Tycoon',
price: '29',
priceCurrency: 'USD',
priceValidUntil: '2025-12-31',
description: 'Enterprise tier with real-time alerts and unlimited portfolio',
},
],
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: '4.9',
ratingCount: '127',
bestRating: '5',
worstRating: '1',
},
}
}
/**
* Add UTM parameters for tracking
*/
export function addUTMParams(url: string, params: {
source?: string
medium?: string
campaign?: string
content?: string
}): string {
const urlObj = new URL(url)
if (params.source) urlObj.searchParams.set('utm_source', params.source)
if (params.medium) urlObj.searchParams.set('utm_medium', params.medium)
if (params.campaign) urlObj.searchParams.set('utm_campaign', params.campaign)
if (params.content) urlObj.searchParams.set('utm_content', params.content)
return urlObj.toString()
export function generateTLDPageSchema(tld: string, data: {
registrationPrice?: number
renewalPrice?: number
type?: string
registrarCount?: number
}) {
return {
'@context': 'https://schema.org',
'@type': 'Product',
name: `.${tld} Domain Extension`,
description: `Current pricing, trends, and registrar comparison for .${tld} domains. Registration from $${data.registrationPrice || 'N/A'}, renewal $${data.renewalPrice || 'N/A'}.`,
url: `${SITE_URL}/tld/${tld}`,
category: data.type || 'Domain Extension',
offers: {
'@type': 'AggregateOffer',
lowPrice: data.registrationPrice || 0,
highPrice: (data.renewalPrice || 0) * 10,
priceCurrency: 'USD',
offerCount: data.registrarCount || 1,
},
}
}
/**
* Generate breadcrumb JSON-LD
*/
export function generateBreadcrumbSchema(items: Array<{ name: string; url: string }>) {
return {
'@context': 'https://schema.org',
@ -205,40 +150,57 @@ export function generateBreadcrumbSchema(items: Array<{ name: string; url: strin
'@type': 'ListItem',
position: index + 1,
name: item.name,
item: `${siteUrl}${item.url}`,
item: item.url,
})),
}
}
/**
* Performance: Critical CSS extraction helper
*/
export function extractCriticalCSS(html: string): string {
// This would be implemented with a CSS extraction library in production
// For now, return empty string
return ''
export function generateFAQSchema(faqs: Array<{ question: string; answer: string }>) {
return {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqs.map(faq => ({
'@type': 'Question',
name: faq.question,
acceptedAnswer: {
'@type': 'Answer',
text: faq.answer,
},
})),
}
}
/**
* Lazy load images with IntersectionObserver
*/
export function setupLazyLoading() {
if (typeof window === 'undefined') return
export function generateWebPageSchema(page: {
title: string
description: string
url: string
datePublished?: string
dateModified?: string
}) {
return {
'@context': 'https://schema.org',
'@type': 'WebPage',
name: page.title,
description: page.description,
url: page.url,
isPartOf: {
'@type': 'WebSite',
name: SITE_NAME,
url: SITE_URL,
},
datePublished: page.datePublished || '2024-01-01',
dateModified: page.dateModified || new Date().toISOString().split('T')[0],
}
}
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement
if (img.dataset.src) {
img.src = img.dataset.src
img.removeAttribute('data-src')
observer.unobserve(img)
}
}
})
})
document.querySelectorAll('img[data-src]').forEach((img) => {
imageObserver.observe(img)
})
}
// Popular TLDs for static generation
export const POPULAR_TLDS = [
'com', 'net', 'org', 'io', 'ai', 'co', 'app', 'dev', 'xyz', 'online',
'store', 'shop', 'tech', 'site', 'club', 'info', 'biz', 'me', 'tv', 'cc',
'ch', 'de', 'uk', 'fr', 'es', 'it', 'nl', 'at', 'be', 'pl',
'au', 'ca', 'us', 'mx', 'br', 'ar', 'jp', 'cn', 'kr', 'in',
'cloud', 'digital', 'agency', 'studio', 'media', 'design', 'solutions', 'consulting',
'finance', 'money', 'bank', 'insurance', 'invest', 'capital', 'fund', 'trading',
'health', 'fitness', 'beauty', 'fashion', 'style', 'luxury', 'vip', 'pro',
'crypto', 'nft', 'web3', 'blockchain', 'bitcoin', 'defi', 'dao', 'token',
]