Skip to content

Commit 480a682

Browse files
Slashgearclaude
andcommitted
feat: improve SEO, accessibility and error handling
- Add priority and changeFrequency to sitemap entries - Add preconnect hints for external image domains - Add skip-to-content link for keyboard navigation - Add error boundaries for event pages (Meetup API failures) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent becc261 commit 480a682

5 files changed

Lines changed: 96 additions & 14 deletions

File tree

app/evenement/[slug]/error.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use client';
2+
3+
import { Hero } from '../../../modules/hero/Hero';
4+
import { HeroTitle } from '../../../modules/hero/HeroTitle';
5+
import { HeroButtonsContainer } from '../../../modules/hero/HeroButtonsContainer';
6+
import { ButtonLink } from '../../../modules/atoms/button/Button';
7+
8+
export default function EventError() {
9+
return (
10+
<main>
11+
<Hero>
12+
<HeroTitle title="Oups !" strong="Impossible de charger l'événement" />
13+
<p>Une erreur est survenue lors du chargement. Réessayez dans quelques instants.</p>
14+
<HeroButtonsContainer>
15+
<ButtonLink href="/">Retourner à l&apos;accueil</ButtonLink>
16+
</HeroButtonsContainer>
17+
</Hero>
18+
</main>
19+
);
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use client';
2+
3+
import { Hero } from '../../../modules/hero/Hero';
4+
import { HeroTitle } from '../../../modules/hero/HeroTitle';
5+
import { HeroButtonsContainer } from '../../../modules/hero/HeroButtonsContainer';
6+
import { ButtonLink } from '../../../modules/atoms/button/Button';
7+
8+
export default function PastEventsError() {
9+
return (
10+
<main>
11+
<Hero>
12+
<HeroTitle title="Oups !" strong="Impossible de charger les événements" />
13+
<p>Une erreur est survenue lors du chargement. Réessayez dans quelques instants.</p>
14+
<HeroButtonsContainer>
15+
<ButtonLink href="/">Retourner à l&apos;accueil</ButtonLink>
16+
</HeroButtonsContainer>
17+
</Hero>
18+
</main>
19+
);
20+
}

app/layout.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ import { ORGANISATION_MARKUP } from './org-markup';
1010
export default function AppLayout({ children }: { children: ReactNode }) {
1111
return (
1212
<html lang="fr-FR">
13+
<head>
14+
<link rel="preconnect" href="https://secure-content.meetupstatic.com" />
15+
<link rel="preconnect" href="https://secure.meetupstatic.com" />
16+
<link rel="preconnect" href="https://img.youtube.com" />
17+
</head>
1318
<body>
19+
<a href="#main-content" className="skip-to-content">
20+
Aller au contenu
21+
</a>
1422
<div id="__next">
1523
<Header />
1624
<script
@@ -19,7 +27,7 @@ export default function AppLayout({ children }: { children: ReactNode }) {
1927
__html: JSON.stringify(ORGANISATION_MARKUP),
2028
}}
2129
/>
22-
{children}
30+
<div id="main-content">{children}</div>
2331
<Footer />
2432
</div>
2533
</body>

app/sitemap.ts

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,52 @@ const BASE_URL = 'https://www.lyonjs.org';
88
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
99
const years = await fetchYearsWithMeetups();
1010
const pastEvents = await fetchPastEvents();
11-
const lastModified = new Date();
11+
const currentYear = new Date().getFullYear().toString();
1212

1313
return [
1414
{
1515
url: BASE_URL,
16-
lastModified,
16+
lastModified: new Date(),
17+
changeFrequency: 'weekly',
18+
priority: 1,
1719
},
1820
{
1921
url: `${BASE_URL}/devenir-sponsor`,
20-
lastModified,
22+
lastModified: new Date(),
23+
changeFrequency: 'monthly',
24+
priority: 0.7,
2125
},
2226
{
2327
url: `${BASE_URL}/code-de-conduite`,
24-
lastModified,
28+
lastModified: new Date(),
29+
changeFrequency: 'yearly',
30+
priority: 0.3,
2531
},
2632
{
2733
url: `${BASE_URL}/a-propos`,
28-
lastModified,
34+
lastModified: new Date(),
35+
changeFrequency: 'monthly',
36+
priority: 0.5,
2937
},
3038
...Array.from(years)
3139
.toReversed()
32-
.map((year) => ({
33-
url: `${BASE_URL}/evenements-precedents/${year}`,
34-
lastModified,
35-
})),
36-
...pastEvents.map((event) => ({
37-
url: `${BASE_URL}/evenement/${slugEventTitle(event)}`,
38-
lastModified,
39-
})),
40+
.map((year) => {
41+
const changeFrequency: 'weekly' | 'yearly' = year === currentYear ? 'weekly' : 'yearly';
42+
return {
43+
url: `${BASE_URL}/evenements-precedents/${year}`,
44+
lastModified: new Date(),
45+
changeFrequency,
46+
priority: year === currentYear ? 0.8 : 0.4,
47+
};
48+
}),
49+
...pastEvents.map((event) => {
50+
const eventYear = new Date(event.dateTime).getFullYear().toString();
51+
return {
52+
url: `${BASE_URL}/evenement/${slugEventTitle(event)}`,
53+
lastModified: new Date(event.dateTime),
54+
changeFrequency: 'yearly' as const,
55+
priority: eventYear === currentYear ? 0.6 : 0.3,
56+
};
57+
}),
4058
];
4159
}

styles/globals.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,19 @@ a,
110110
[data-lock-scroll] {
111111
overflow: hidden !important;
112112
}
113+
114+
.skip-to-content {
115+
position: absolute;
116+
left: -9999px;
117+
top: 0;
118+
z-index: 100;
119+
padding: 8px 16px;
120+
background: var(--main-color);
121+
color: var(--grey-0);
122+
font-weight: 700;
123+
text-decoration: none;
124+
}
125+
126+
.skip-to-content:focus {
127+
left: 0;
128+
}

0 commit comments

Comments
 (0)