Skip to content

Commit 6577c9e

Browse files
authored
Merge pull request #17706 from ethereum/hub-schema
Add structured data for community hubs
2 parents dc7e853 + b7eb0a3 commit 6577c9e

File tree

3 files changed

+259
-1
lines changed

3 files changed

+259
-1
lines changed

app/[locale]/community/events/page-jsonld.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,93 @@ import { getLocaleYear } from "@/lib/utils/date"
88
import {
99
ethereumCommunityOrganization,
1010
ethereumFoundationOrganization,
11+
ethereumFoundationReference,
1112
} from "@/lib/utils/jsonld"
1213
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
1314

15+
import { communityHubSchemas } from "@/data/community-hub-schemas"
16+
import communityHubs from "@/data/community-hubs"
17+
18+
function buildHubSchemaNodes(
19+
hub: (typeof communityHubs)[number],
20+
description: string
21+
) {
22+
const schema = communityHubSchemas[hub.id]
23+
if (!schema) return []
24+
25+
const placeId = `#hub-location-${hub.id}`
26+
const seriesId = `#coworking-series-${hub.id}`
27+
28+
const serviceNode = {
29+
"@type": "Service" as const,
30+
name: "Ethereum Community Coworking and Events",
31+
description,
32+
provider: ethereumFoundationReference,
33+
areaServed: {
34+
"@type": "City" as const,
35+
name: hub.location,
36+
},
37+
}
38+
39+
const placeNode: Record<string, unknown> = {
40+
"@type": "Place" as const,
41+
"@id": placeId,
42+
name: schema.hubName ?? `Ethereum Community Hub (${hub.location})`,
43+
}
44+
45+
if (schema.address) {
46+
placeNode.address = {
47+
"@type": "PostalAddress" as const,
48+
streetAddress: schema.address.streetAddress,
49+
addressLocality: schema.address.addressLocality,
50+
...(schema.address.postalCode && {
51+
postalCode: schema.address.postalCode,
52+
}),
53+
addressCountry: schema.address.addressCountry,
54+
}
55+
}
56+
57+
if (schema.containedInPlace) {
58+
placeNode.containedInPlace = {
59+
"@type": "LocalBusiness" as const,
60+
name: schema.containedInPlace.name,
61+
...(schema.containedInPlace.url && {
62+
url: schema.containedInPlace.url,
63+
}),
64+
}
65+
}
66+
67+
const eventNode = {
68+
"@type": ["EventSeries", "Event"] as const,
69+
"@id": seriesId,
70+
name: schema.eventSeriesName ?? "Open Ethereum Coworking Hours",
71+
description: schema.eventDescription,
72+
isAccessibleForFree: true,
73+
url: hub.coworkingSignupUrl,
74+
eventStatus: "https://schema.org/EventScheduled",
75+
eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode",
76+
organizer: ethereumFoundationReference,
77+
location: { "@id": placeId },
78+
eventSchedule: {
79+
"@type": "Schedule" as const,
80+
...(schema.schedule.startDate && {
81+
startDate: schema.schedule.startDate,
82+
}),
83+
...(schema.schedule.startTime && {
84+
startTime: schema.schedule.startTime,
85+
}),
86+
...(schema.schedule.endTime && {
87+
endTime: schema.schedule.endTime,
88+
}),
89+
repeatFrequency: schema.schedule.repeatFrequency,
90+
byDay: schema.schedule.byDay,
91+
scheduleTimezone: schema.schedule.scheduleTimezone,
92+
},
93+
}
94+
95+
return [serviceNode, placeNode, eventNode]
96+
}
97+
1498
export default async function EventsJsonLD({
1599
locale,
16100
contributors,
@@ -30,6 +114,10 @@ export default async function EventsJsonLD({
30114
url: contributor.html_url,
31115
}))
32116

117+
const hubSchemaNodes = communityHubs.flatMap((hub) =>
118+
buildHubSchemaNodes(hub, t(hub.descriptionKey))
119+
)
120+
33121
const jsonLd = {
34122
"@context": "https://schema.org",
35123
"@graph": [
@@ -118,6 +206,7 @@ export default async function EventsJsonLD({
118206
publisher: ethereumFoundationOrganization,
119207
reviewedBy: ethereumFoundationOrganization,
120208
},
209+
...hubSchemaNodes,
121210
],
122211
}
123212

src/data/community-hub-schemas.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
const WEEKDAYS = [
2+
"https://schema.org/Monday",
3+
"https://schema.org/Tuesday",
4+
"https://schema.org/Wednesday",
5+
"https://schema.org/Thursday",
6+
"https://schema.org/Friday",
7+
] as const
8+
9+
export type CommunityHubSchemaData = {
10+
/** Override for Place name. Default: "Ethereum Community Hub ({location})" */
11+
hubName?: string
12+
/** Override for EventSeries name. Default: "Open Ethereum Coworking Hours" */
13+
eventSeriesName?: string
14+
eventDescription: string
15+
address?: {
16+
streetAddress: string
17+
addressLocality: string
18+
postalCode?: string
19+
addressCountry: string
20+
}
21+
containedInPlace?: {
22+
name: string
23+
url?: string
24+
}
25+
schedule: {
26+
startDate?: string
27+
startTime?: string
28+
endTime?: string
29+
repeatFrequency: string
30+
byDay: string | readonly string[]
31+
scheduleTimezone: string
32+
}
33+
}
34+
35+
/**
36+
* Structured data metadata for community hubs.
37+
* Used to generate JSON-LD Service + Place + EventSeries schemas
38+
* for regional search discovery.
39+
*
40+
* Keys must match hub IDs in community-hubs.ts.
41+
* hub.location is used as areaServed city name (no need to duplicate here).
42+
* hubName defaults to "Ethereum Community Hub ({location})" if omitted.
43+
* eventSeriesName defaults to "Open Ethereum Coworking Hours" if omitted.
44+
*/
45+
export const communityHubSchemas: Record<string, CommunityHubSchemaData> = {
46+
london: {
47+
eventDescription:
48+
"Open community coworking for Ethereum builders at Encode Hub in London.",
49+
address: {
50+
streetAddress: "41 Pitfield St",
51+
addressLocality: "London",
52+
postalCode: "N1 6DA",
53+
addressCountry: "GB",
54+
},
55+
containedInPlace: {
56+
name: "Encode Hub",
57+
url: "https://hub.encode.club/",
58+
},
59+
schedule: {
60+
repeatFrequency: "P1D",
61+
byDay: WEEKDAYS,
62+
scheduleTimezone: "Europe/London",
63+
},
64+
},
65+
66+
berlin: {
67+
hubName: "Ethereum Foundation Office (Berlin)",
68+
eventSeriesName: "Ethereum Community Hub Berlin -- Co-working Wednesdays",
69+
eventDescription:
70+
"Every Wednesday the Ethereum Foundation office opens for builders, researchers, creators, students, and explorers to co-work, connect, and collaborate.",
71+
schedule: {
72+
startTime: "10:00",
73+
endTime: "20:00",
74+
repeatFrequency: "P1W",
75+
byDay: "https://schema.org/Wednesday",
76+
scheduleTimezone: "Europe/Berlin",
77+
},
78+
},
79+
80+
"hong-kong": {
81+
eventDescription:
82+
"Open community coworking for Ethereum builders at DoBe Hub in Hong Kong.",
83+
address: {
84+
streetAddress: "83 King Lam St",
85+
addressLocality: "Cheung Sha Wan",
86+
addressCountry: "HK",
87+
},
88+
containedInPlace: {
89+
name: "DoBe Hub",
90+
},
91+
schedule: {
92+
repeatFrequency: "P1D",
93+
byDay: WEEKDAYS,
94+
scheduleTimezone: "Asia/Hong_Kong",
95+
},
96+
},
97+
98+
rome: {
99+
eventDescription: "Open community coworking for Ethereum builders in Rome.",
100+
address: {
101+
streetAddress: "Largo Dino Frisullo",
102+
addressLocality: "Rome",
103+
addressCountry: "IT",
104+
},
105+
schedule: {
106+
repeatFrequency: "P1D",
107+
byDay: WEEKDAYS,
108+
scheduleTimezone: "Europe/Rome",
109+
},
110+
},
111+
112+
dubai: {
113+
eventDescription:
114+
"Open community coworking for Ethereum builders at Hadron Founders Club in Dubai.",
115+
address: {
116+
streetAddress: "Warehouse 21-22, Al Qouz Industrial Third, Al Quoz",
117+
addressLocality: "Dubai",
118+
addressCountry: "AE",
119+
},
120+
containedInPlace: {
121+
name: "Hadron Founders Club",
122+
url: "https://luma.com/HadronFC",
123+
},
124+
schedule: {
125+
repeatFrequency: "P1D",
126+
byDay: WEEKDAYS,
127+
scheduleTimezone: "Asia/Dubai",
128+
},
129+
},
130+
131+
lagos: {
132+
eventDescription:
133+
"Open community coworking for Ethereum builders at Web3Bridge in Lagos.",
134+
address: {
135+
streetAddress: "25 Talabi Ademola Street, Abadek Avenue, Ogunlewe St",
136+
addressLocality: "Igbogbo Ikorodu",
137+
postalCode: "104102",
138+
addressCountry: "NG",
139+
},
140+
containedInPlace: {
141+
name: "Web3Bridge",
142+
url: "https://www.web3bridge.com/",
143+
},
144+
schedule: {
145+
repeatFrequency: "P1D",
146+
byDay: WEEKDAYS,
147+
scheduleTimezone: "Africa/Lagos",
148+
},
149+
},
150+
151+
sf: {
152+
eventDescription:
153+
"Open community coworking for Ethereum builders at Frontier Tower in San Francisco.",
154+
address: {
155+
streetAddress: "995 Market St",
156+
addressLocality: "San Francisco",
157+
postalCode: "94103",
158+
addressCountry: "US",
159+
},
160+
containedInPlace: {
161+
name: "Frontier Tower",
162+
url: "https://frontiertower.io/",
163+
},
164+
schedule: {
165+
repeatFrequency: "P1D",
166+
byDay: WEEKDAYS,
167+
scheduleTimezone: "America/Los_Angeles",
168+
},
169+
},
170+
}

src/data/community-hubs.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ const communityHubs: CommunityHub[] = [
2828
coworkingSignupUrl: "https://forms.gle/bm78vRjZqvu45tsz5",
2929
meetupUrl: "https://luma.com/user/usr-ut3JGCXXuokkPdK",
3030
banner: HongKongHubBanner,
31-
// TODO: Update
3231
brandColor:
3332
"bg-gradient-to-b from-[#A4FCF5]/5 to-[#A4FCF5]/10 dark:from-[#A4FCF5]/20 dark:to-[#A4FCF5]/10 border-[#A4FCF5]/20",
3433
},

0 commit comments

Comments
 (0)