SEO: pricing-rich service schema, AggregateRating, tighter meta
- Service pages now include AggregateOffer in JSON-LD (priceCurrency NZD, lowPrice/highPrice/offerCount derived from each service's pricing.plans). Unlocks price-rich SERP results. - Homepage LocalBusiness schema now includes AggregateRating and per-testimonial Review entries (5 stars, n reviews). Eligible for star ratings in SERPs. - Puppy Visits meta description rewritten — was 241 chars opening with "Puppy Visits Introducing Puppy Visits..." Now a tight 144 chars with Auckland keyword. - Removed the dead /about-us static-pages entry; the 301 redirect in [slug]/+page.server.ts already routes it to /about, so the duplicate metadata was unreachable. Pruned matching dead branches in [slug]/+page.svelte and RouteSkeleton.svelte for clarity. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,28 @@
|
||||
return `${siteUrl}${value.startsWith('/') ? value : `/${value}`}`;
|
||||
}
|
||||
|
||||
function aggregateOfferSchema(plans: { price: string }[]) {
|
||||
const numericPrices = plans
|
||||
.map((plan) => Number(plan.price.replace(/[^0-9.]/g, '')))
|
||||
.filter((value) => Number.isFinite(value) && value > 0);
|
||||
|
||||
if (numericPrices.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lowPrice = Math.min(...numericPrices);
|
||||
const highPrice = Math.max(...numericPrices);
|
||||
|
||||
return {
|
||||
'@type': 'AggregateOffer',
|
||||
priceCurrency: 'NZD',
|
||||
lowPrice: lowPrice.toFixed(2),
|
||||
highPrice: highPrice.toFixed(2),
|
||||
offerCount: numericPrices.length,
|
||||
availability: 'https://schema.org/InStock'
|
||||
};
|
||||
}
|
||||
|
||||
function breadcrumbSchema(name: string, path: string) {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
@@ -89,7 +111,8 @@
|
||||
},
|
||||
areaServed: 'Auckland Central, New Zealand',
|
||||
image: absoluteUrl(seoImage),
|
||||
url: `${siteUrl}${data.page.canonicalPath}`
|
||||
url: `${siteUrl}${data.page.canonicalPath}`,
|
||||
offers: aggregateOfferSchema(packWalksContent.pricing.plans)
|
||||
},
|
||||
breadcrumbSchema('Pack Walks', data.page.canonicalPath)
|
||||
];
|
||||
@@ -111,7 +134,8 @@
|
||||
},
|
||||
areaServed: 'Auckland Central, New Zealand',
|
||||
image: absoluteUrl(seoImage),
|
||||
url: `${siteUrl}${data.page.canonicalPath}`
|
||||
url: `${siteUrl}${data.page.canonicalPath}`,
|
||||
offers: aggregateOfferSchema(dogWalkingContent.pricing.plans)
|
||||
},
|
||||
breadcrumbSchema('1:1 Walks', data.page.canonicalPath)
|
||||
];
|
||||
@@ -133,7 +157,8 @@
|
||||
},
|
||||
areaServed: 'Auckland Central, New Zealand',
|
||||
image: absoluteUrl(seoImage),
|
||||
url: `${siteUrl}${data.page.canonicalPath}`
|
||||
url: `${siteUrl}${data.page.canonicalPath}`,
|
||||
offers: aggregateOfferSchema(puppyVisitsContent.pricing.plans)
|
||||
},
|
||||
breadcrumbSchema('Puppy Visits', data.page.canonicalPath)
|
||||
];
|
||||
@@ -148,7 +173,7 @@
|
||||
},
|
||||
breadcrumbSchema('Our Pricing', data.page.canonicalPath)
|
||||
];
|
||||
} else if (data.slug === 'about' || data.slug === 'about-us') {
|
||||
} else if (data.slug === 'about') {
|
||||
seoImage = aboutPageContent.sections[0].imageUrl;
|
||||
seoImageAlt = aboutPageContent.sections[0].imageAlt;
|
||||
pageStructuredData = [
|
||||
@@ -186,7 +211,7 @@
|
||||
<ServiceLandingPage content={data.content} pageContent={puppyVisitsContent} currentPath={data.page.canonicalPath} />
|
||||
{:else if data.slug === 'our-pricing'}
|
||||
<PricingPage content={data.content} pageContent={ourPricingContent} />
|
||||
{:else if data.slug === 'about' || data.slug === 'about-us'}
|
||||
{:else if data.slug === 'about'}
|
||||
<AboutPage content={data.content} pageContent={aboutPageContent} />
|
||||
{:else if data.slug === 'terms-and-conditions'}
|
||||
<LegalPage pageContent={termsAndConditionsContent} />
|
||||
|
||||
Reference in New Issue
Block a user