Skip to content

Commit 72b09bd

Browse files
authored
Feat: accessibility seo (#681)
* a11y(con): add image alt text and semantic landmarks * feat(con): add Person & Event/subEvent JSON-LD for speakers and talks * feat(con): richer semantic HTML for talks & speakers (article, time, section)
1 parent 7c6fab8 commit 72b09bd

21 files changed

Lines changed: 204 additions & 69 deletions

File tree

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
export function Logos() {
22
return (
33
<>
4-
<img src="/images/con/2025/cfp/api-platform.png" alt="" />
5-
<img src="/images/con/2025/cfp/caddy.png" alt="" />
6-
<img src="/images/con/2025/cfp/elastic.png" alt="" />
7-
<img src="/images/con/2025/cfp/frankenphp.png" alt="" />
8-
<img src="/images/con/2025/cfp/JS.png" alt="" />
9-
<img src="/images/con/2025/cfp/laravel.png" alt="" />
10-
<img src="/images/con/2025/cfp/php.png" alt="" />
11-
<img src="/images/con/2025/cfp/rabbitmq.png" alt="" />
12-
<img src="/images/con/2025/cfp/react-admin.png" alt="" />
13-
<img src="/images/con/2025/cfp/xdebug.png" alt="" />
4+
<img src="/images/con/2025/cfp/api-platform.png" alt="API Platform" />
5+
<img src="/images/con/2025/cfp/caddy.png" alt="Caddy" />
6+
<img src="/images/con/2025/cfp/elastic.png" alt="Elastic" />
7+
<img src="/images/con/2025/cfp/frankenphp.png" alt="FrankenPHP" />
8+
<img src="/images/con/2025/cfp/JS.png" alt="JavaScript" />
9+
<img src="/images/con/2025/cfp/laravel.png" alt="Laravel" />
10+
<img src="/images/con/2025/cfp/php.png" alt="PHP" />
11+
<img src="/images/con/2025/cfp/rabbitmq.png" alt="RabbitMQ" />
12+
<img src="/images/con/2025/cfp/react-admin.png" alt="React Admin" />
13+
<img src="/images/con/2025/cfp/xdebug.png" alt="Xdebug" />
1414
</>
1515
);
1616
}

pwa/app/(con)/[locale]/con/2025/tickets/RegisterPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export default function RegisterPage() {
8585
<img
8686
className="relative"
8787
src="/images/con/2024/review/pic-06.jpg"
88+
alt="API Platform Conference 2024"
8889
/>
8990
</div>
9091
<div className="flex-1 relative bg-white shadow-floating dotted-corner p-12 pt-24 lg:pt-12 lg:pl-24 lg:-translate-x-12 leading-relaxed font-light">
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
export function Logos() {
22
return (
33
<>
4-
<img src="/images/con/2025/cfp/api-platform.png" alt="" />
5-
<img src="/images/con/2025/cfp/caddy.png" alt="" />
6-
<img src="/images/con/2025/cfp/elastic.png" alt="" />
7-
<img src="/images/con/2025/cfp/frankenphp.png" alt="" />
8-
<img src="/images/con/2025/cfp/JS.png" alt="" />
9-
<img src="/images/con/2025/cfp/laravel.png" alt="" />
10-
<img src="/images/con/2025/cfp/php.png" alt="" />
11-
<img src="/images/con/2025/cfp/rabbitmq.png" alt="" />
12-
<img src="/images/con/2025/cfp/react-admin.png" alt="" />
13-
<img src="/images/con/2025/cfp/xdebug.png" alt="" />
4+
<img src="/images/con/2025/cfp/api-platform.png" alt="API Platform" />
5+
<img src="/images/con/2025/cfp/caddy.png" alt="Caddy" />
6+
<img src="/images/con/2025/cfp/elastic.png" alt="Elastic" />
7+
<img src="/images/con/2025/cfp/frankenphp.png" alt="FrankenPHP" />
8+
<img src="/images/con/2025/cfp/JS.png" alt="JavaScript" />
9+
<img src="/images/con/2025/cfp/laravel.png" alt="Laravel" />
10+
<img src="/images/con/2025/cfp/php.png" alt="PHP" />
11+
<img src="/images/con/2025/cfp/rabbitmq.png" alt="RabbitMQ" />
12+
<img src="/images/con/2025/cfp/react-admin.png" alt="React Admin" />
13+
<img src="/images/con/2025/cfp/xdebug.png" alt="Xdebug" />
1414
</>
1515
);
1616
}

pwa/app/(con)/[locale]/con/2026/layout.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import nav from "data/con/2026/nav";
55
import footer from "data/con/2026/footer";
66
import { Metadata } from "next";
77
import { getEditionEventData } from "utils/con";
8-
import { i18n } from "i18n/i18n-config";
8+
import { i18n, Locale } from "i18n/i18n-config";
99
import { getRootUrl } from "utils";
10+
import { getAllSpeakers } from "api/con/speakers";
11+
import { getAllConferences } from "api/con/conferences";
1012

1113
type Props = {
1214
params: { edition: string; locale: string };
@@ -38,8 +40,17 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
3840
};
3941
}
4042

41-
function EditionLayout({ children }: { children: React.ReactNode }) {
42-
const eventData = getEditionEventData("2026");
43+
async function EditionLayout({
44+
children,
45+
params,
46+
}: {
47+
children: React.ReactNode;
48+
params: { locale: string };
49+
}) {
50+
const locale = (params?.locale || i18n.defaultLocale) as Locale;
51+
const speakers = await getAllSpeakers("2026", locale);
52+
const talks = await getAllConferences("2026", true, locale);
53+
const eventData = getEditionEventData("2026", speakers, talks);
4354
return (
4455
<LayoutBase edition="2026" nav={nav} footer={footer} isTicketingOpen>
4556
<script

pwa/app/(con)/[locale]/con/[edition]/conferences/ConferencesPage.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export default function SpeakerPageListTemplate({
141141
{conferences.map((conference, i) => {
142142
const day = days.find((day: Day) => day.date === conference.date);
143143
return (
144-
<div
144+
<article
145145
key={conference.title}
146146
className="flex flex-col-reverse md:flex-row text-center md:text-left gap-4 md:gap-12 bg-white p-4 pt-8 sm:p-8 text-blue-black relative"
147147
>
@@ -206,6 +206,7 @@ export default function SpeakerPageListTemplate({
206206
<SpeakerImage
207207
image={speaker.image}
208208
placeholder={speaker.placeholder}
209+
alt={speaker.name}
209210
/>
210211
</div>
211212
);
@@ -260,11 +261,19 @@ export default function SpeakerPageListTemplate({
260261
) : null}
261262
{conference.date ? (
262263
<p className="text-sm font-title">
263-
{getConferenceDate(
264-
conference.date,
265-
conference.start,
266-
conference.end
267-
)}
264+
<time
265+
dateTime={
266+
conference.start
267+
? `${conference.date}T${conference.start}`
268+
: conference.date
269+
}
270+
>
271+
{getConferenceDate(
272+
conference.date,
273+
conference.start,
274+
conference.end
275+
)}
276+
</time>
268277
</p>
269278
) : null}
270279
</>
@@ -283,7 +292,7 @@ export default function SpeakerPageListTemplate({
283292
/>
284293
) : null}
285294
</div>
286-
</div>
295+
</article>
287296
);
288297
})}
289298
</div>

pwa/app/(con)/[locale]/con/[edition]/conferences/[slug]/components/ConferencePage.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ const ConferencePageTemplate = ({ conference, day }: ConferencePageProps) => {
1818
const languageContext = useContext(LanguageContext);
1919

2020
return (
21-
<div className="container max-w-6xl flex flex-col items-center pt-10 pb-80 before:bg-wave before:absolute before:w-[2000px] before:h-[500px] before:bg-no-repeat before:opacity-30 before:-translate-x-1/2 before:top-[220px] before:left-[65%] | sm:pt-20">
22-
<div className="text-white text-center mb-20">
21+
<article className="container max-w-6xl flex flex-col items-center pt-10 pb-80 before:bg-wave before:absolute before:w-[2000px] before:h-[500px] before:bg-no-repeat before:opacity-30 before:-translate-x-1/2 before:top-[220px] before:left-[65%] | sm:pt-20">
22+
<header className="text-white text-center mb-20">
2323
<SectionTitle dark lined h1 small={50 < title.length}>
2424
<strong>{title}</strong>
2525
</SectionTitle>
@@ -34,11 +34,13 @@ const ConferencePageTemplate = ({ conference, day }: ConferencePageProps) => {
3434
) : null}
3535
{date ? (
3636
<p className="text-sm font-semibold mt-3">
37-
{getConferenceDate(date, start, end)}
37+
<time dateTime={start ? `${date}T${start}` : date}>
38+
{getConferenceDate(date, start, end)}
39+
</time>
3840
</p>
3941
) : null}
4042
</>
41-
</div>
43+
</header>
4244
<div className="flex flex-col-reverse items-center w-full relative -mb-24 before:bg-grey before:absolute before:w-screen before:h-4/6 before:left-1/2 before:bottom-[50px] before:-translate-x-1/2 | md:flex-row md:items-start">
4345
<ConferenceSpeaker conference={conference} />
4446
<div className="bg-white p-12 shadow-md dotted-corner corner-bottom flex-1">
@@ -53,7 +55,7 @@ const ConferencePageTemplate = ({ conference, day }: ConferencePageProps) => {
5355
) : null}
5456
</div>
5557
</div>
56-
</div>
58+
</article>
5759
);
5860
};
5961

pwa/app/(con)/[locale]/con/[edition]/conferences/[slug]/components/ConferenceSpeaker.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ const ConferenceSpeaker = ({ conference }: SpeakerProps) => {
5151
placeholder={placeholder}
5252
/>
5353
) : (
54-
<SpeakerImage image={image} placeholder={placeholder} />
54+
<SpeakerImage
55+
image={image}
56+
placeholder={placeholder}
57+
alt={name}
58+
/>
5559
)}
5660
</div>
5761

pwa/app/(con)/[locale]/con/[edition]/conferences/[slug]/page.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
3030
.replace("%name%", SPEAKERS)
3131
.replace("%edition%", params.edition);
3232

33-
const URL = `${getRootUrl()}/${params.locale}/con/${params.edition}/${params.slug}`;
33+
const URL = `${getRootUrl()}/${params.locale}/con/${params.edition}/${
34+
params.slug
35+
}`;
3436

3537
return {
3638
title: conference.title,
@@ -39,13 +41,13 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
3941
title: `${conference.title} - API Platform Conference`,
4042
description: DESCRIPTION,
4143
url: URL,
42-
type: 'website',
44+
type: "website",
4345
images: [
4446
{
4547
url: `${getRootUrl()}/images/con/og-${params.edition}.png`,
4648
width: 1200,
4749
height: 630,
48-
alt: `API Platform Conference ${params.edition}`
50+
alt: `API Platform Conference ${params.edition}`,
4951
},
5052
],
5153
},

pwa/app/(con)/[locale]/con/[edition]/layout.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import LayoutBase from "components/con/layout/LayoutBase";
33
import ContactCard from "components/con/layout/ContactCard";
44
import type { Metadata } from "next";
55
import { getEditionEventData } from "utils/con";
6-
import { i18n } from "i18n/i18n-config";
6+
import { i18n, Locale } from "i18n/i18n-config";
77
import { getRootUrl } from "utils";
8+
import { getAllSpeakers } from "api/con/speakers";
9+
import { getAllConferences } from "api/con/conferences";
10+
import { currentEdition } from "data/con/editions";
811

912
type Props = {
1013
params: { edition: string; locale: string };
@@ -68,13 +71,25 @@ async function EditionLayout({
6871
children: React.ReactNode;
6972
params: {
7073
edition: string;
74+
locale: string;
7175
};
7276
}) {
73-
const { edition } = params;
77+
const { edition, locale } = params;
7478
const nav = await import(`data/con/${edition}/nav`);
7579
const footer = await import(`data/con/${edition}/footer`);
7680

77-
const eventData = getEditionEventData(edition);
81+
const resolvedLocale = (locale || i18n.defaultLocale) as Locale;
82+
// Only the upcoming edition benefits from rich speaker/talk structured data
83+
// (Google does not surface past events). Skipping the fetch for past
84+
// editions keeps the build lean.
85+
const isCurrentEdition = edition === currentEdition;
86+
const speakers = isCurrentEdition
87+
? await getAllSpeakers(edition, resolvedLocale)
88+
: [];
89+
const talks = isCurrentEdition
90+
? await getAllConferences(edition, true, resolvedLocale)
91+
: [];
92+
const eventData = getEditionEventData(edition, speakers, talks);
7893

7994
return (
8095
<LayoutBase edition={edition} nav={nav.default} footer={footer.default}>

pwa/app/(con)/[locale]/con/[edition]/schedule/components/SlotAvatar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default function Avatar({
4848
) : (
4949
<Image
5050
key={speaker.name}
51-
alt=""
51+
alt={speaker.name}
5252
className="absolute rounded-full -translate-x-1/2 -translate-y-1/2 object-cover"
5353
src={speaker.image}
5454
width={getSize(speakers.length)}

0 commit comments

Comments
 (0)