1.2.0: local SEO structured data (LocalBusiness, Service, FAQPage), noindex per page

This commit is contained in:
2026-09-08 12:49:49 +02:00
parent e39e2a361a
commit 068415849f
34 changed files with 1030 additions and 104 deletions
@@ -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 } : {}),
}
}