Skip to content

Commit c8a85cf

Browse files
Slashgearclaude
andcommitted
feat(seo): add images and videos to sitemap
Index Meetup event photos and YouTube talk videos in the sitemap using Next.js built-in image/video sitemap support for better search engine discoverability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 480a682 commit c8a85cf

3 files changed

Lines changed: 51 additions & 6 deletions

File tree

app/sitemap.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ import { MetadataRoute } from 'next';
22
import { fetchYearsWithMeetups } from '../modules/meetup/queries/years-with-meetups';
33
import { fetchPastEvents } from '../modules/meetup/queries/past-events.api';
44
import { slugEventTitle } from '../modules/event/eventSlug';
5+
import { overrideEvent } from '../modules/event/overrideEvent';
56

67
const BASE_URL = 'https://www.lyonjs.org';
78

9+
function youtubeVideoId(embedUrl: string): string | null {
10+
const match = embedUrl.match(/\/embed\/([^/?]+)/);
11+
return match?.[1] ?? null;
12+
}
13+
814
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
915
const years = await fetchYearsWithMeetups();
1016
const pastEvents = await fetchPastEvents();
@@ -46,14 +52,40 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
4652
priority: year === currentYear ? 0.8 : 0.4,
4753
};
4854
}),
49-
...pastEvents.map((event) => {
55+
...pastEvents.map((rawEvent) => {
56+
const event = overrideEvent(rawEvent);
5057
const eventYear = new Date(event.dateTime).getFullYear().toString();
51-
return {
58+
59+
const images: string[] = [];
60+
if (event.featuredEventPhoto?.highResUrl) {
61+
images.push(event.featuredEventPhoto.highResUrl);
62+
}
63+
64+
const videos: MetadataRoute.Sitemap[number]['videos'] = [];
65+
if (event.talks) {
66+
for (const talk of event.talks) {
67+
if (!talk.videoLink) continue;
68+
const videoId = youtubeVideoId(talk.videoLink);
69+
if (!videoId) continue;
70+
videos.push({
71+
title: talk.title,
72+
thumbnail_loc: `https://img.youtube.com/vi/${videoId}/0.jpg`,
73+
description: `${talk.title} - ${talk.speakers?.map((s) => s.name).join(', ') ?? ''} @ ${event.title}`,
74+
});
75+
}
76+
}
77+
78+
const entry: MetadataRoute.Sitemap[number] = {
5279
url: `${BASE_URL}/evenement/${slugEventTitle(event)}`,
5380
lastModified: new Date(event.dateTime),
5481
changeFrequency: 'yearly' as const,
5582
priority: eventYear === currentYear ? 0.6 : 0.3,
5683
};
84+
85+
if (images.length > 0) entry.images = images;
86+
if (videos.length > 0) entry.videos = videos;
87+
88+
return entry;
5789
}),
5890
];
5991
}

next-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3+
import "./dist/types/routes.d.ts";
34

45
// NOTE: This file should not be edited
56
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

tsconfig.json

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
22
"compilerOptions": {
33
"target": "ES2018",
4-
"lib": ["dom", "dom.iterable", "esnext"],
4+
"lib": [
5+
"dom",
6+
"dom.iterable",
7+
"esnext"
8+
],
59
"allowJs": true,
610
"skipLibCheck": true,
711
"strict": true,
@@ -12,14 +16,22 @@
1216
"moduleResolution": "bundler",
1317
"resolveJsonModule": true,
1418
"isolatedModules": true,
15-
"jsx": "preserve",
19+
"jsx": "react-jsx",
1620
"incremental": true,
1721
"plugins": [
1822
{
1923
"name": "next"
2024
}
2125
]
2226
},
23-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "dist/types/**/*.ts"],
24-
"exclude": ["node_modules"]
27+
"include": [
28+
"next-env.d.ts",
29+
"**/*.ts",
30+
"**/*.tsx",
31+
"dist/types/**/*.ts",
32+
"dist/dev/types/**/*.ts"
33+
],
34+
"exclude": [
35+
"node_modules"
36+
]
2537
}

0 commit comments

Comments
 (0)