1.2.0: local SEO structured data (LocalBusiness, Service, FAQPage), noindex per page
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
type MediaLike = { url?: null | string } | null | undefined
|
||||
|
||||
type Address = {
|
||||
city?: string
|
||||
country?: string // ISO code, e.g. 'PL'
|
||||
postalCode?: string
|
||||
region?: string
|
||||
street?: string
|
||||
}
|
||||
|
||||
type LocalBusinessJsonLdArgs = {
|
||||
address?: Address
|
||||
/** Geo coordinates for maps/local search. */
|
||||
geo?: { latitude: number; longitude: number }
|
||||
image?: MediaLike
|
||||
logo?: MediaLike
|
||||
name: string
|
||||
/** Opening hours, e.g. ['Mo-Fr 08:00-18:00', 'Sa 09:00-14:00']. */
|
||||
openingHours?: string[]
|
||||
priceRange?: string // e.g. '$$'
|
||||
sameAs?: string[]
|
||||
/** Business phone, e.g. '+48 123 456 789'. */
|
||||
telephone?: string
|
||||
url: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds LocalBusiness JSON-LD (schema.org) — the key structured data for LOCAL
|
||||
* SEO. Helps Google show the business in local results / map pack with address,
|
||||
* hours, phone. Strong signal for "usługa + miasto" queries.
|
||||
*
|
||||
* All data from the panel (company global) — nothing hardcoded. Emit once in the
|
||||
* root layout (business is site-wide):
|
||||
*
|
||||
* import { buildLocalBusinessJsonLd } from '@intecion/ipal-kit'
|
||||
* const jsonLd = buildLocalBusinessJsonLd({
|
||||
* name: company.name, url: baseUrl, telephone: company.phone,
|
||||
* address: company.address, openingHours: company.hours,
|
||||
* })
|
||||
*
|
||||
* For a more specific type (e.g. 'Dentist', 'Plumber'), override @type in the
|
||||
* returned object — schema.org has many LocalBusiness subtypes.
|
||||
*/
|
||||
export function buildLocalBusinessJsonLd({
|
||||
name,
|
||||
address,
|
||||
geo,
|
||||
image,
|
||||
logo,
|
||||
openingHours,
|
||||
priceRange,
|
||||
sameAs,
|
||||
telephone,
|
||||
url,
|
||||
}: LocalBusinessJsonLdArgs) {
|
||||
const logoUrl = logo?.url
|
||||
const imageUrl = image?.url ?? logoUrl
|
||||
|
||||
return {
|
||||
name,
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'LocalBusiness',
|
||||
url,
|
||||
...(telephone ? { telephone } : {}),
|
||||
...(imageUrl ? { image: imageUrl.startsWith('http') ? imageUrl : `${url}${imageUrl}` } : {}),
|
||||
...(logoUrl ? { logo: logoUrl.startsWith('http') ? logoUrl : `${url}${logoUrl}` } : {}),
|
||||
...(address
|
||||
? {
|
||||
address: {
|
||||
'@type': 'PostalAddress',
|
||||
...(address.street ? { streetAddress: address.street } : {}),
|
||||
...(address.city ? { addressLocality: address.city } : {}),
|
||||
...(address.postalCode ? { postalCode: address.postalCode } : {}),
|
||||
...(address.region ? { addressRegion: address.region } : {}),
|
||||
...(address.country ? { addressCountry: address.country } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(geo
|
||||
? { geo: { '@type': 'GeoCoordinates', latitude: geo.latitude, longitude: geo.longitude } }
|
||||
: {}),
|
||||
...(openingHours && openingHours.length > 0 ? { openingHours } : {}),
|
||||
...(priceRange ? { priceRange } : {}),
|
||||
...(sameAs && sameAs.length > 0 ? { sameAs } : {}),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user