Skip to content

feat: reduce CLS with skeleton loaders and remove useWindowSize#598

Open
rickstaa wants to merge 1 commit intomainfrom
feat/reduce-cls-skeleton-loaders
Open

feat: reduce CLS with skeleton loaders and remove useWindowSize#598
rickstaa wants to merge 1 commit intomainfrom
feat/reduce-cls-skeleton-loaders

Conversation

@rickstaa
Copy link
Member

Summary

  • Add ConnectButton/Skeleton.tsx to prevent layout shift while ConnectButton loads client-side
  • Add OrchestratorList/Skeleton.tsx to show a loading skeleton instead of "Loading orchestrators…" text
  • Remove useWindowSize hook from main layout to eliminate forced reflows during initial render
  • Remove associated useEffect that managed body overflow based on window width

Extracted from #509 by @Roaring30s — Lighthouse performance / CLS improvements.

Partially addresses #433.

Test plan

  • Verify ConnectButton area shows skeleton on initial page load (disable JS momentarily or throttle network)
  • Verify orchestrator list shows skeleton loader before data arrives
  • Confirm no layout shift when ConnectButton hydrates
  • Test mobile drawer still works correctly without useWindowSize-based overflow management
  • Run Lighthouse and check CLS score improvement

🤖 Generated with Claude Code

Add skeleton loading states for ConnectButton and OrchestratorList
to prevent layout shifts during hydration. Remove useWindowSize hook
that caused forced reflows during initial render.

Extracted from #509.

Co-Authored-By: Sebastian <115311276+Roaring30s@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rickstaa rickstaa requested a review from ECWireless as a code owner March 25, 2026 11:00
Copilot AI review requested due to automatic review settings March 25, 2026 11:00
@vercel
Copy link
Contributor

vercel bot commented Mar 25, 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 Mar 25, 2026 11:04am

Request Review

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 targets Lighthouse CLS improvements by adding skeleton fallbacks for client-only UI and reducing forced reflows in the main layout by removing useWindowSize usage.

Changes:

  • Add skeleton loaders for ConnectButton (dynamic import fallback) and the home-page Orchestrator list.
  • Replace the “Loading orchestrators…” placeholder with a skeleton component.
  • Remove useWindowSize and the width-based useEffect that previously adjusted document.body overflow in layouts/main.tsx.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
pages/index.tsx Uses OrchestratorListSkeleton instead of a text placeholder before the orchestrator list mounts.
layouts/main.tsx Removes useWindowSize and adds a dynamic-import loading skeleton for ConnectButton.
components/OrchestratorList/Skeleton.tsx New skeleton UI intended to match the OrchestratorList table layout while it’s deferred.
components/ConnectButton/Skeleton.tsx New skeleton UI used as the loading fallback for the client-only ConnectButton.
Comments suppressed due to low confidence (1)

layouts/main.tsx:216

  • Removing the width-based effect means document.body.style.overflow = "hidden" (set in onDrawerOpen) may remain stuck if the viewport crosses the mobile/desktop breakpoint while the drawer is open (e.g., resize/rotate), since there’s no longer any breakpoint listener to clear it. Consider using a lightweight matchMedia listener for the @bp3 breakpoint (or closing the drawer on breakpoint change) to ensure body overflow is always restored when leaving the mobile drawer layout.
  useEffect(() => {
    ReactGA.set({
      customBrowserType: !isMobile
        ? "desktop"

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +31 to +36
{/* Table header skeleton */}
<Box
css={{ padding: "$3 $5", borderBottom: "1px solid $colors$neutral4" }}
>
<Flex css={{ gap: "$4" }}>
<Skeleton css={{ width: 150, height: 16 }} />
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This skeleton doesn’t replicate the Table layout constraints (notably the overflowX: "auto" wrapper and minWidth: 960 table width in components/Table/index.tsx). On narrow viewports this can reintroduce CLS when the real OrchestratorList mounts and suddenly forces horizontal scrolling/min-width. Consider matching the Table wrapper structure/constraints so the skeleton and real table occupy the same space.

Copilot uses AI. Check for mistakes.
css={{
marginTop: "$4",
marginLeft: "$5",
marginBottom: "$4",
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The input-section spacing here doesn’t match the real Table input wrapper: components/Table/index.tsx applies marginTop: "$4" and marginLeft: "$5" but no wrapper marginBottom. The extra marginBottom: "$4" in the skeleton makes it taller than the real OrchestratorList header area, which can cause a vertical layout shift when swapping from skeleton to the actual table.

Suggested change
marginBottom: "$4",

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +115
import ConnectButtonSkeleton from "../components/ConnectButton/Skeleton";

const ConnectButton = dynamic(() => import("../components/ConnectButton"), {
ssr: false,
loading: () => <ConnectButtonSkeleton />,
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

ConnectButtonSkeleton is imported mid-file (after non-import statements) and via a relative path. With simple-import-sort/imports enforced, this will fail lint and also deviates from the existing @components/* import convention in this file. Move the import into the main import block and use the @components/ConnectButton/Skeleton alias so imports can be sorted correctly.

Copilot uses AI. Check for mistakes.
Comment on lines 7 to +8
import RoundStatus from "@components/RoundStatus";
import OrchestratorListSkeleton from "@components/OrchestratorList/Skeleton";
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

Import order here is no longer sorted (@components/OrchestratorList/Skeleton comes after @components/RoundStatus). With simple-import-sort/imports enforced in this repo, this will fail lint; re-run import sorting so the new skeleton import is placed in the correct order within the @components/* group.

Suggested change
import RoundStatus from "@components/RoundStatus";
import OrchestratorListSkeleton from "@components/OrchestratorList/Skeleton";
import OrchestratorListSkeleton from "@components/OrchestratorList/Skeleton";
import RoundStatus from "@components/RoundStatus";

Copilot uses AI. Check for mistakes.
const activeChain = useActiveChain();
const [drawerOpen, setDrawerOpen] = useState(false);
const [bannerActive, setBannerActive] = useState<boolean>(false);
const { width } = useWindowSize();
Copy link
Contributor

Choose a reason for hiding this comment

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

Drawer open on mobile leaves body.overflow hidden after window resize to desktop, preventing page scrolling

Fix on Vercel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

2 participants