Skip to content

Commit 17cda42

Browse files
committed
feat: enhance conference SEO metadata and event structure
- Introduced new functions to build event location and offers, improving the structured data for SEO. - Updated the schedule event description logic for better clarity and consistency. - Refactored the buildScheduleSubEvents function to include new parameters for event URL, images, and venue details. - Enhanced the overall JSON-LD structure for conference events, ensuring accurate representation of event details and accessibility features.
1 parent 5e697c7 commit 17cda42

2 files changed

Lines changed: 133 additions & 51 deletions

File tree

‎src/lib/seo/conferenceSeo.ts‎

Lines changed: 132 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,94 @@ const buildSpeakerPerformers = (conference: ConferenceProfile) =>
222222
...(speaker.linkedin ? { sameAs: [speaker.linkedin] } : {}),
223223
})) ?? [];
224224

225+
const buildEventLocation = (
226+
conference: ConferenceProfile,
227+
locality: string,
228+
region?: string,
229+
venue?: string
230+
) => ({
231+
'@type': 'Place',
232+
name: venue ?? conference.locationName,
233+
...(venue
234+
? {
235+
containedInPlace: {
236+
'@type': 'City',
237+
name: locality,
238+
...(region
239+
? {
240+
containedInPlace: {
241+
'@type': 'AdministrativeArea',
242+
name: region,
243+
},
244+
}
245+
: {}),
246+
},
247+
}
248+
: {}),
249+
address: {
250+
'@type': 'PostalAddress',
251+
addressLocality: locality,
252+
...(region ? { addressRegion: region } : {}),
253+
addressCountry: 'IR',
254+
},
255+
});
256+
257+
const buildEventOffers = (
258+
url: string,
259+
startDate: string,
260+
endDate: string | undefined,
261+
isPastEvent: boolean
262+
) => ({
263+
'@type': 'Offer',
264+
url,
265+
price: '0',
266+
priceCurrency: 'IRR',
267+
availability: isPastEvent
268+
? 'https://schema.org/SoldOut'
269+
: 'https://schema.org/InStock',
270+
validFrom: startDate,
271+
...(endDate ? { validThrough: endDate } : {}),
272+
});
273+
274+
const buildScheduleEventDescription = (
275+
event: ScheduleEvent,
276+
conferenceLabel: string
277+
) => {
278+
if (event.description) {
279+
return plainifySync(event.description);
280+
}
281+
282+
const title = event.subtitle
283+
? `${event.title} — ${event.subtitle}`
284+
: event.title;
285+
const parts = [`${title}، بخشی از ${conferenceLabel}`];
286+
287+
if (event.speaker) {
288+
parts.push(`با ${event.speaker}`);
289+
}
290+
291+
return plainifySync(parts.join(' — '));
292+
};
293+
225294
const buildScheduleSubEvents = (
226-
conference: ConferenceProfile
295+
conference: ConferenceProfile,
296+
{
297+
eventUrl,
298+
conferenceLabel,
299+
images,
300+
isPastEvent,
301+
locality,
302+
region,
303+
venue,
304+
}: {
305+
eventUrl: string;
306+
conferenceLabel: string;
307+
images: string[];
308+
isPastEvent: boolean;
309+
locality: string;
310+
region?: string;
311+
venue?: string;
312+
}
227313
): Record<string, unknown>[] => {
228314
if (!conference.schedule?.length) return [];
229315

@@ -234,35 +320,53 @@ const buildScheduleSubEvents = (
234320
.map((event: ScheduleEvent, index) => {
235321
const eventDate = event.date ?? conference.startDate;
236322
const startDate = scheduleTimeToIso(eventDate, event.time);
323+
const endDate = event.endTime
324+
? scheduleTimeToIso(eventDate, event.endTime)
325+
: undefined;
326+
const sessionUrl = `${eventUrl}#session-${index + 1}`;
327+
const sessionStartDate = startDate ?? conference.startDate;
237328

238329
return {
239330
'@type': 'Event',
240-
'@id': `${SITE_URL}${conferencePath(conference.slug)}#session-${index + 1}`,
331+
'@id': sessionUrl,
332+
url: sessionUrl,
241333
name: event.subtitle
242334
? `${event.title} — ${event.subtitle}`
243335
: event.title,
244-
...(event.description
245-
? { description: plainifySync(event.description) }
246-
: {}),
336+
description: buildScheduleEventDescription(event, conferenceLabel),
247337
...(startDate ? { startDate } : {}),
338+
...(endDate ? { endDate } : {}),
248339
eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode',
249340
eventStatus: 'https://schema.org/EventScheduled',
250-
location: {
251-
'@type': 'Place',
252-
name:
253-
parseConferenceLocation(conference.locationName).venue ??
254-
conference.locationName,
341+
isAccessibleForFree: true,
342+
organizer: {
343+
'@id': organizationId,
255344
},
345+
image: images,
346+
location: buildEventLocation(conference, locality, region, venue),
347+
offers: buildEventOffers(
348+
sessionUrl,
349+
sessionStartDate,
350+
endDate,
351+
isPastEvent
352+
),
256353
...(event.speaker
257354
? {
258355
performer: {
259356
'@type': 'Person',
260357
name: event.speaker,
261358
},
262359
}
263-
: {}),
360+
: event.type === 'panel' && conference.speakers?.list.length
361+
? {
362+
performer: conference.speakers.list.map((speaker) => ({
363+
'@type': 'Person',
364+
name: speaker.name,
365+
})),
366+
}
367+
: {}),
264368
superEvent: {
265-
'@id': `${SITE_URL}${conferencePath(conference.slug)}#event`,
369+
'@id': `${eventUrl}#event`,
266370
},
267371
};
268372
});
@@ -278,10 +382,18 @@ export const buildConferenceJsonLd = (conference: ConferenceProfile) => {
278382
const attendeeCount = getAttendeeCount(conference.stats);
279383
const keywords = buildConferenceKeywords(conference);
280384
const performers = buildSpeakerPerformers(conference);
281-
const subEvents = buildScheduleSubEvents(conference);
282385
const pageName = plainifySync(
283386
formatConferenceLabel(conference.title, conference.year)
284387
);
388+
const subEvents = buildScheduleSubEvents(conference, {
389+
eventUrl,
390+
conferenceLabel: pageName,
391+
images,
392+
isPastEvent,
393+
locality,
394+
region,
395+
venue,
396+
});
285397

286398
const graph: Record<string, unknown>[] = [
287399
{
@@ -347,45 +459,15 @@ export const buildConferenceJsonLd = (conference: ConferenceProfile) => {
347459
'@id': organizationId,
348460
},
349461
image: images,
350-
location: {
351-
'@type': 'Place',
352-
name: venue ?? conference.locationName,
353-
...(venue
354-
? {
355-
containedInPlace: {
356-
'@type': 'City',
357-
name: locality,
358-
...(region
359-
? {
360-
containedInPlace: {
361-
'@type': 'AdministrativeArea',
362-
name: region,
363-
},
364-
}
365-
: {}),
366-
},
367-
}
368-
: {}),
369-
address: {
370-
'@type': 'PostalAddress',
371-
addressLocality: locality,
372-
...(region ? { addressRegion: region } : {}),
373-
addressCountry: 'IR',
374-
},
375-
},
462+
location: buildEventLocation(conference, locality, region, venue),
376463
...(performers.length ? { performer: performers } : {}),
377464
...(attendeeCount ? { maximumAttendeeCapacity: attendeeCount } : {}),
378-
offers: {
379-
'@type': 'Offer',
380-
url: eventUrl,
381-
price: '0',
382-
priceCurrency: 'IRR',
383-
availability: isPastEvent
384-
? 'https://schema.org/SoldOut'
385-
: 'https://schema.org/InStock',
386-
validFrom: conference.startDate,
387-
...(conference.endDate ? { validThrough: conference.endDate } : {}),
388-
},
465+
offers: buildEventOffers(
466+
eventUrl,
467+
conference.startDate,
468+
conference.endDate,
469+
isPastEvent
470+
),
389471
...(subEvents.length ? { subEvent: subEvents } : {}),
390472
},
391473
];

‎tsconfig.tsbuildinfo‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)