Skip to content

Boost Lighthouse Performance (Partially Fixes Issue #433)#509

Open
Roaring30s wants to merge 16 commits intolivepeer:mainfrom
Roaring30s:fix-lighthouse-performance
Open

Boost Lighthouse Performance (Partially Fixes Issue #433)#509
Roaring30s wants to merge 16 commits intolivepeer:mainfrom
Roaring30s:fix-lighthouse-performance

Conversation

@Roaring30s
Copy link
Contributor

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

  • Added meta description - Resolved Lighthouse SEO warning, boosting SEO score to near 100/100

Performance Improvements

  • Image optimization with Sharp - Resized ENS avatars from 704×704 to 96×96 and converted to WebP format, reducing file sizes by ~95% and improving performance metrics
    • Created reusable optimizeImage utility in lib/api/image-optimization.ts
    • Integrated Sharp optimization into /api/ens-data/image/[name] endpoint
    • Images now served as WebP with proper caching headers

Best Practices Improvements

  • Enabled production source maps - Added productionBrowserSourceMaps: true to next.config.js to 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.tsx
    • components/AccountCell/index.tsx
    • components/DelegatingWidget/Header.tsx
    • components/IdentityAvatar/index.tsx
    • Migration diagram pages
  • Fixed links without discernible names - Added aria-label attributes to links:

    • Logo link in components/Logo/index.tsx
    • External contract address links in layouts/main.tsx
    • Social media links in components/Profile/index.tsx
    • Added rel="noopener noreferrer" for security on external links
  • Fixed touch target areas - Increased minimum size to 44×44px and added proper spacing for:

    • "Performance Leaderboard" button
    • "View All" buttons in Orchestrators and Transactions tables
    • Added aria-hidden="true" to decorative arrow icons
  • Fixed aria-role mismatches - Added appropriate role attributes:

    • role="button" for interactive badges and action buttons
    • role="group" for chart titles with tooltips
    • role="group" for "Initialized"/"Locked" status indicators

Testing

Lighthouse Score Reproduction:

  • Used Chrome Incognito mode
  • Disabled cache to simulate first-time visitors
  • Screenshots of before/after Lighthouse scores included

Future Performance Improvements

To further boost the Performance section of Lighthouse, consider:

  1. Eliminate forced reflows from useWindowSize hook

    • Replace useWindowSize from react-use with CSS media queries or window.matchMedia API
    • Affected components:
      • components/BottomDrawer/index.tsx
      • components/DelegatingWidget/Input.tsx
      • layouts/account.tsx
      • pages/treasury/[proposal].tsx
      • pages/voting/[poll].tsx
    • The useWindowSize hook uses ResizeObserver and getBoundingClientRect() synchronously, causing forced reflows during initial render
  2. Host fonts internally with Next.js Font Optimization

    • Replace Google Fonts <link> tags in pages/_document.tsx with next/font (Inter and Roboto Mono)
    • Team permission needed to execute this step
    • Benefits:
      • Eliminates external font requests
      • Self-hosts fonts for better performance
      • Automatic font optimization and subsetting
      • Reduces render-blocking resources

Files Changed

  • pages/_app.tsx - Added meta description
  • next.config.js - Enabled production source maps
  • lib/api/image-optimization.ts - New utility for image optimization
  • pages/api/ens-data/image/[name].tsx - Integrated Sharp optimization
  • layouts/main.tsx - Added main landmark, aria-labels, removed useWindowSize
  • components/Logo/index.tsx - Added aria-label
  • components/Profile/index.tsx - Added alt tags and aria-labels
  • components/AccountCell/index.tsx - Added alt tags
  • components/DelegatingWidget/Header.tsx - Added alt tags
  • components/IdentityAvatar/index.tsx - Added alt tags and width/height attributes
  • pages/index.tsx - Fixed touch targets and spacing
  • pages/orchestrators.tsx - Fixed touch targets
  • components/RoundStatus/index.tsx - Fixed aria-role mismatches
  • components/OrchestratorList/index.tsx - Fixed aria-role mismatches
  • components/ExplorerChart/index.tsx - Fixed aria-role mismatches
  • Migration diagram pages - Added alt tags

Lighthouse scores locally before improvements

image

Lighthouse scores locally after improvements

image

@vercel
Copy link

vercel bot commented Jan 26, 2026

@Roaring30s is attempting to deploy a commit to the Livepeer Foundation Team on Vercel.

A member of the Team first needs to authorize it.

@vercel
Copy link

vercel bot commented Feb 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
explorer-arbitrum-one Ready Ready Preview, Comment Feb 13, 2026 4:34pm

Request Review

},
},
};

Copy link

@vercel vercel bot Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PopoverLink incorrectly treats internal links with newWindow=true as external, causing full page reloads instead of client-side navigation

Fix on Vercel

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think this is wrong. But would double-check @Roaring30s

effort: 6,
});

// Set appropriate content type (fallback to original if optimization failed)
Copy link

@vercel vercel bot Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect content-type fallback logic in image optimization creates ambiguity between success and failure cases

Fix on Vercel

Copy link
Collaborator

@ECWireless ECWireless left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes inconsistent styling in the mobile drawer:

Image

},
},
};

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think this is wrong. But would double-check @Roaring30s

href="https://docs.livepeer.org/references/contract-addresses"
>
<A>
<A
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For whatever reason, this still does not seem to open a new tab

color: "$hiContrast",
fontSize: "$2",
minHeight: "44px",
padding: "$2 $3",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the biggest fan of the extra padding/height

css={{
color: "$hiContrast",
fontSize: "$2",
minHeight: "44px",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the biggest fan of the extra padding/height. Also seems to be pushing the window to the right on mobile:

Image

@ECWireless ECWireless requested a review from Copilot February 13, 2026 18:16
>
Loading orchestrators…
</Box>
<OrchestratorListSkeleton />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we actually add this to the orchestrators page too?

href="https://docs.livepeer.org/references/contract-addresses"
>
<A>
<A
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

External absolute URL wrapped in Next.js Link component with nested anchor doesn't open in new tab properly

Fix on Vercel

trigger={
<A
as={Text}
<Box
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Get LPT" button lacks styling consistency with surrounding drawer links, appearing visually inconsistent due to missing color, hover effects, and proper button reset styling

Fix on Vercel

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 productionBrowserSourceMaps in next.config.js and 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 (

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Suggested change
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");
}

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +31
export async function optimizeImage(
imageBuffer: ArrayBuffer,
options: OptimizeImageOptions = {}
): Promise<OptimizeImageResult> {
const { width = 96, height = 96, quality = 75, effort = 6 } = options;

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@@ -159,12 +162,20 @@
href={identity.url}
target="__blank"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
target="__blank"
target="_blank"

Copilot uses AI. Check for mistakes.
Comment on lines +185 to +191
<A
variant="contrast"
css={{ fontSize: "$2" }}
href={`https://twitter.com/${identity.twitter}`}
target="__blank"
rel="noopener noreferrer"
aria-label={`View ${identity.twitter} on Twitter`}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 309 to 312
<ExplorerTooltip
multiline
role="group"
content={
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
}
>
<Box>Delegated Stake</Box>
<Box role="button">Delegated Stake</Box>
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<Box role="button">Delegated Stake</Box>
<Box as="button" type="button">Delegated Stake</Box>

Copilot uses AI. Check for mistakes.
Comment on lines +216 to +222
<A
variant="contrast"
css={{ fontSize: "$2" }}
href={`https://github.com/${identity.github}`}
target="__blank"
rel="noopener noreferrer"
aria-label={`View ${identity.github} on GitHub`}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
</Text>
</Box>
<ExplorerTooltip
role="group"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
role="group"

Copilot uses AI. Check for mistakes.
}
>
<Box>Forecasted Yield</Box>
<Box role="button">Forecasted Yield</Box>
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<Box role="button">Forecasted Yield</Box>
<Box as="button" type="button">Forecasted Yield</Box>

Copilot uses AI. Check for mistakes.
@@ -179,7 +179,7 @@ const LivepeerLogo = ({
if (!isLink) return markup;
return (
<Link href="/" passHref>
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<Link href="/" passHref>
<Link href="/" passHref legacyBehavior>

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Possible performance improvements

3 participants