Boost Lighthouse Performance (Partially Fixes Issue #433)#509
Boost Lighthouse Performance (Partially Fixes Issue #433)#509Roaring30s wants to merge 16 commits intolivepeer:mainfrom
Conversation
|
@Roaring30s is attempting to deploy a commit to the Livepeer Foundation Team on Vercel. A member of the Team first needs to authorize it. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| }, | ||
| }, | ||
| }; | ||
|
|
There was a problem hiding this comment.
I actually think this is wrong. But would double-check @Roaring30s
| effort: 6, | ||
| }); | ||
|
|
||
| // Set appropriate content type (fallback to original if optimization failed) |
ECWireless
left a comment
There was a problem hiding this comment.
The improvements look generally great, especially the OrchestratorListSkeleton. Could you just address the comments below + AI comments (some AI comments could very well just be wrong)?
| trigger={ | ||
| <A | ||
| as={Text} | ||
| <Box |
| }, | ||
| }, | ||
| }; | ||
|
|
There was a problem hiding this comment.
I actually think this is wrong. But would double-check @Roaring30s
| href="https://docs.livepeer.org/references/contract-addresses" | ||
| > | ||
| <A> | ||
| <A |
There was a problem hiding this comment.
For whatever reason, this still does not seem to open a new tab
| color: "$hiContrast", | ||
| fontSize: "$2", | ||
| minHeight: "44px", | ||
| padding: "$2 $3", |
There was a problem hiding this comment.
Not the biggest fan of the extra padding/height
| css={{ | ||
| color: "$hiContrast", | ||
| fontSize: "$2", | ||
| minHeight: "44px", |
| > | ||
| Loading orchestrators… | ||
| </Box> | ||
| <OrchestratorListSkeleton /> |
There was a problem hiding this comment.
Could we actually add this to the orchestrators page too?
There was a problem hiding this comment.
Pull request overview
This PR aims to improve Lighthouse Performance/Accessibility/Best Practices/SEO scores for the Livepeer Explorer by optimizing ENS avatar images, adding semantic/accessibility attributes, improving touch targets, and enabling production browser source maps.
Changes:
- Add a Sharp-based image optimization utility and integrate it into the ENS avatar image API to serve resized WebP images.
- Improve accessibility/semantics across the UI (alt text, aria-labels,
<main>landmark, decorative icons hidden from AT, touch target sizing). - Enable
productionBrowserSourceMapsinnext.config.jsand add a global meta description.
Reviewed changes
Copilot reviewed 21 out of 23 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks Sharp and its transitive dependencies. |
| package.json | Adds sharp dependency. |
| next.config.js | Enables production browser source maps. |
| lib/api/image-optimization.ts | New reusable Sharp-based image optimizer utility. |
| pages/api/ens-data/image/[name].tsx | Uses optimizer to resize/convert ENS avatars and sets caching/headers. |
| pages/_app.tsx | Adds global meta description. |
| layouts/main.tsx | Adds <main> landmark, improves link a11y/security, removes useWindowSize, adds ConnectButton skeleton. |
| pages/index.tsx | Improves touch targets/spacing and uses orchestrator list skeleton while loading. |
| components/OrchestratorList/Skeleton.tsx | New skeleton UI for orchestrator list loading state. |
| components/ConnectButton/Skeleton.tsx | New skeleton UI for ConnectButton dynamic import loading state. |
| components/Logo/index.tsx | Adds accessible label to the logo link. |
| components/Profile/index.tsx | Adds alt text and aria-labels to profile links/icons. |
| components/AccountCell/index.tsx | Adds alt text for account avatar image. |
| components/DelegatingWidget/Header.tsx | Adds alt text for delegate avatar image. |
| components/IdentityAvatar/index.tsx | Adds alt text for identity avatar image. |
| components/Drawer/index.tsx | Uses a semantic button element for “Get LPT” trigger. |
| components/PopoverLink/index.tsx | Refactors styles and changes handling of external/internal links. |
| components/OrchestratorList/index.tsx | Adds roles/aria adjustments for tooltips/popovers/actions. |
| components/ExplorerChart/index.tsx | Adds grouping roles to chart title layout. |
| components/RoundStatus/index.tsx | Adds roles/aria-hidden to icons and tooltip-related role adjustments. |
| pages/migrate/orchestrator.tsx | Adds alt text to migration diagram image. |
| pages/migrate/delegator/index.tsx | Adds alt text to migration diagram image. |
| pages/migrate/broadcaster.tsx | Adds alt text to migration diagram image. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -48,9 +49,30 @@ const handler = async ( | |||
|
|
|||
There was a problem hiding this comment.
The fetch response isn’t checked for ok before reading/processing the body. If the remote returns a 404/500 HTML page (or other non-image), optimizeImage will fall back and this route will still return that body with cache headers, which is incorrect for an image endpoint. Consider validating response.ok (and possibly content-type starts with image/) and returning notFound/badRequest before calling arrayBuffer().
| if (!response.ok) { | |
| return notFound(res, "ENS avatar not found"); | |
| } | |
| const contentTypeHeader = response.headers.get("content-type") || ""; | |
| if (!contentTypeHeader.toLowerCase().startsWith("image/")) { | |
| return notFound(res, "ENS avatar not found"); | |
| } |
| export async function optimizeImage( | ||
| imageBuffer: ArrayBuffer, | ||
| options: OptimizeImageOptions = {} | ||
| ): Promise<OptimizeImageResult> { | ||
| const { width = 96, height = 96, quality = 75, effort = 6 } = options; | ||
|
|
There was a problem hiding this comment.
New image optimization logic is introduced here but there are no unit tests covering success vs fallback paths (e.g., valid PNG/JPEG input -> WebP output; invalid/corrupt input -> original buffer returned). Since the repo already has Jest unit tests for lib utilities, adding tests will help prevent regressions and make behavior explicit.
| @@ -159,12 +162,20 @@ | |||
| href={identity.url} | |||
| target="__blank" | |||
There was a problem hiding this comment.
target="__blank" is not the standard value and will open a named browsing context rather than reliably opening a new tab/window. This should be target="_blank" for external links.
| target="__blank" | |
| target="_blank" |
| <A | ||
| variant="contrast" | ||
| css={{ fontSize: "$2" }} | ||
| href={`https://twitter.com/${identity.twitter}`} | ||
| target="__blank" | ||
| rel="noopener noreferrer" | ||
| aria-label={`View ${identity.twitter} on Twitter`} |
There was a problem hiding this comment.
target="__blank" is not the standard value and will open a named browsing context rather than reliably opening a new tab/window. This should be target="_blank" for external links.
| <ExplorerTooltip | ||
| multiline | ||
| role="group" | ||
| content={ |
There was a problem hiding this comment.
Passing role="group" into ExplorerTooltip will be forwarded to the underlying Radix Tooltip.Content, overriding its default role="tooltip". That breaks tooltip semantics for assistive tech and can reintroduce aria-role issues. Remove the role prop here, or apply grouping semantics to a wrapper around the trigger/content instead of the tooltip content element.
| } | ||
| > | ||
| <Box>Delegated Stake</Box> | ||
| <Box role="button">Delegated Stake</Box> |
There was a problem hiding this comment.
role="button" is applied to a Box used as a tooltip trigger, but the element still isn’t keyboard-focusable/clickable (no tabIndex, no key handlers, and it’s likely rendered as a div). For accessibility, use an actual <button type="button"> (e.g., as="button") or ensure the element is focusable and supports keyboard interaction.
| <Box role="button">Delegated Stake</Box> | |
| <Box as="button" type="button">Delegated Stake</Box> |
| <A | ||
| variant="contrast" | ||
| css={{ fontSize: "$2" }} | ||
| href={`https://github.com/${identity.github}`} | ||
| target="__blank" | ||
| rel="noopener noreferrer" | ||
| aria-label={`View ${identity.github} on GitHub`} |
There was a problem hiding this comment.
target="__blank" is not the standard value and will open a named browsing context rather than reliably opening a new tab/window. This should be target="_blank" for external links.
| </Text> | ||
| </Box> | ||
| <ExplorerTooltip | ||
| role="group" |
There was a problem hiding this comment.
Passing role="group" into ExplorerTooltip will be forwarded to the underlying Radix Tooltip.Content, overriding its default role="tooltip". That breaks tooltip semantics for assistive tech and can reintroduce aria-role issues. Remove the role prop here, or apply grouping semantics to a wrapper around the trigger/content instead of the tooltip content element.
| role="group" |
| } | ||
| > | ||
| <Box>Forecasted Yield</Box> | ||
| <Box role="button">Forecasted Yield</Box> |
There was a problem hiding this comment.
role="button" is applied to a Box used as a tooltip trigger, but the element still isn’t keyboard-focusable/clickable (no tabIndex, no key handlers, and it’s likely rendered as a div). For accessibility, use an actual <button type="button"> (e.g., as="button") or ensure the element is focusable and supports keyboard interaction.
| <Box role="button">Forecasted Yield</Box> | |
| <Box as="button" type="button">Forecasted Yield</Box> |
| @@ -179,7 +179,7 @@ const LivepeerLogo = ({ | |||
| if (!isLink) return markup; | |||
| return ( | |||
| <Link href="/" passHref> | |||
There was a problem hiding this comment.
With the current Next.js Link behavior (and existing usage in this repo), nesting a raw <a> inside <Link> typically requires legacyBehavior, otherwise it can produce warnings/errors (“Link with child”). To avoid that, either add legacyBehavior here or remove the <a> wrapper and put the aria-label on the Link/child component in a supported way.
| <Link href="/" passHref> | |
| <Link href="/" passHref legacyBehavior> |


Lighthouse Performance & Accessibility Improvements
Summary
This PR improves Lighthouse scores across Performance, Accessibility, Best Practices, and SEO by optimizing images, adding semantic HTML, improving accessibility attributes, and enabling production source maps.
Fixes #433
Changes Made
SEO Improvements
Performance Improvements
optimizeImageutility inlib/api/image-optimization.ts/api/ens-data/image/[name]endpointBest Practices Improvements
Enabled production source maps - Added
productionBrowserSourceMaps: truetonext.config.jsto resolve Lighthouse Best Practices warning for large first-party JavaScript. Improves debugging and error tracking without impacting end users.Added main landmark - Wrapped page content in semantic
<main>element to resolve Lighthouse Best Practices warning. Improves accessibility for screen readers and assistive technologies by providing clear page structure navigation.Accessibility Improvements
Added alt tags to all images - Ensured all
Box as="img"elements have descriptive alt text across:components/Profile/index.tsxcomponents/AccountCell/index.tsxcomponents/DelegatingWidget/Header.tsxcomponents/IdentityAvatar/index.tsxFixed links without discernible names - Added
aria-labelattributes to links:components/Logo/index.tsxlayouts/main.tsxcomponents/Profile/index.tsxrel="noopener noreferrer"for security on external linksFixed touch target areas - Increased minimum size to 44×44px and added proper spacing for:
aria-hidden="true"to decorative arrow iconsFixed aria-role mismatches - Added appropriate
roleattributes:role="button"for interactive badges and action buttonsrole="group"for chart titles with tooltipsrole="group"for "Initialized"/"Locked" status indicatorsTesting
Lighthouse Score Reproduction:
Future Performance Improvements
To further boost the Performance section of Lighthouse, consider:
Eliminate forced reflows from
useWindowSizehookuseWindowSizefromreact-usewith CSS media queries orwindow.matchMediaAPIcomponents/BottomDrawer/index.tsxcomponents/DelegatingWidget/Input.tsxlayouts/account.tsxpages/treasury/[proposal].tsxpages/voting/[poll].tsxuseWindowSizehook usesResizeObserverandgetBoundingClientRect()synchronously, causing forced reflows during initial renderHost fonts internally with Next.js Font Optimization
<link>tags inpages/_document.tsxwithnext/font(Inter and Roboto Mono)Files Changed
pages/_app.tsx- Added meta descriptionnext.config.js- Enabled production source mapslib/api/image-optimization.ts- New utility for image optimizationpages/api/ens-data/image/[name].tsx- Integrated Sharp optimizationlayouts/main.tsx- Added main landmark, aria-labels, removed useWindowSizecomponents/Logo/index.tsx- Added aria-labelcomponents/Profile/index.tsx- Added alt tags and aria-labelscomponents/AccountCell/index.tsx- Added alt tagscomponents/DelegatingWidget/Header.tsx- Added alt tagscomponents/IdentityAvatar/index.tsx- Added alt tags and width/height attributespages/index.tsx- Fixed touch targets and spacingpages/orchestrators.tsx- Fixed touch targetscomponents/RoundStatus/index.tsx- Fixed aria-role mismatchescomponents/OrchestratorList/index.tsx- Fixed aria-role mismatchescomponents/ExplorerChart/index.tsx- Fixed aria-role mismatchesLighthouse scores locally before improvements
Lighthouse scores locally after improvements