perf/a11y: replace raw img tags, fix alt text, aria-hidden, avif/webp

This commit is contained in:
Jeffrey 2026-03-13 13:39:16 +01:00
parent 15c3194eb6
commit 534a5644bf
7 changed files with 27 additions and 10 deletions

View File

@ -77,6 +77,8 @@ const nextConfig = {
cpus: 1,
},
images: {
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 3600,
remotePatterns: [
...[NEXT_PUBLIC_SERVER_URL].map((item) => {
const url = new URL(item)
@ -85,6 +87,8 @@ const nextConfig = {
protocol: url.protocol.replace(':', ''),
}
}),
{ hostname: 'img.youtube.com', protocol: 'https' },
{ hostname: 'i.vimeocdn.com', protocol: 'https' },
],
},
webpack: (webpackConfig) => {

View File

@ -72,7 +72,7 @@ export const FDHeroBlockComponent: React.FC<FDHeroBlockProps> = ({
priority
className="absolute inset-0 w-full h-full object-cover"
sizes="100vw"
fallbackAlt=""
fallbackAlt={heading || ''}
/>
<div className={`absolute inset-0 ${overlayClass}`} aria-hidden="true" />
</>

View File

@ -83,6 +83,7 @@ export const FDLinkCardsBlockComponent: React.FC<Props> = ({
className="w-12 h-12 md:w-16 md:h-16 object-contain"
sizes="64px"
fallbackAlt=""
aria-hidden="true"
/>
)}
<h2 className={`font-joey-heavy text-fd-h1 max-w-[700px] ${hClr}`}>

View File

@ -1,5 +1,6 @@
import React from 'react'
import type { FDLocationsGridBlock as Props, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage'
import { fdCardRadius, fdContainer} from '@/utilities/fdTheme'
export const FDLocationsGridBlockComponent: React.FC<Props> = ({
@ -65,10 +66,13 @@ export const FDLocationsGridBlockComponent: React.FC<Props> = ({
const inner = (
<>
{media?.url && (
<img
src={media.url}
alt={(media as any).alt || card.locationName}
className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
<FDImage
media={media}
size="large"
fill
className="object-cover transition-transform duration-500 group-hover:scale-105"
sizes="(max-width: 640px) 100vw, (max-width: 768px) 50vw, 33vw"
fallbackAlt={card.locationName || ''}
/>
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent transition-opacity duration-300 group-hover:opacity-0" />

View File

@ -1,5 +1,6 @@
import React from 'react'
import type { FDPartnersLogosBlock as FDPartnersLogosBlockProps, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage'
import { fdContainer } from '@/utilities/fdTheme'
export const FDPartnersLogosBlockComponent: React.FC<FDPartnersLogosBlockProps> = ({
@ -39,10 +40,12 @@ export const FDPartnersLogosBlockComponent: React.FC<FDPartnersLogosBlockProps>
if (!media?.url) return null
const logoEl = (
<img
src={media.url}
alt={item.alt || ''}
<FDImage
media={media}
size="medium"
className={`h-14 sm:h-12 md:h-16 lg:h-[72px] w-auto max-w-[160px] sm:max-w-[180px] md:max-w-[220px] object-contain ${imgFilter}`}
sizes="220px"
fallbackAlt={item.alt || ''}
/>
)

View File

@ -18,6 +18,8 @@ interface FDImageProps {
sizes?: string
/** Fallback alt text if media has none */
fallbackAlt?: string
/** Hide from assistive tech (decorative images) */
'aria-hidden'?: boolean | 'true' | 'false'
}
export const FDImage: React.FC<FDImageProps> = ({
@ -28,6 +30,7 @@ export const FDImage: React.FC<FDImageProps> = ({
priority = false,
sizes = '(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px',
fallbackAlt = '',
'aria-hidden': ariaHidden,
}) => {
// If media is not a populated object, bail
if (!media || typeof media === 'string' || typeof media === 'number') return null
@ -47,7 +50,7 @@ export const FDImage: React.FC<FDImageProps> = ({
if (isSvg) {
return (
// eslint-disable-next-line @next/next/no-img-element
<img src={src} alt={alt} className={className} loading="lazy" />
<img src={src} alt={alt} className={className} loading="lazy" aria-hidden={ariaHidden} />
)
}
@ -60,6 +63,7 @@ export const FDImage: React.FC<FDImageProps> = ({
className={className}
priority={priority}
sizes={sizes}
aria-hidden={ariaHidden}
/>
)
}
@ -78,6 +82,7 @@ export const FDImage: React.FC<FDImageProps> = ({
className={className}
priority={priority}
sizes={sizes}
aria-hidden={ariaHidden}
/>
)
}

File diff suppressed because one or more lines are too long