Skip to content

Conversation

@fscgustavo
Copy link

This PR introduces performance and user experience improvements to the dashboard, focusing on reducing layout shifts, optimizing bundle size, and speeding up perceived load times.

🚀 Performance & UX Improvements

  • Adopted the same structural strategy as the main Morpho app (e.g., morpho.org/ethereum/earn). Static content (CTA cards) is now displayed immediately at the top, while asynchronous/heavy content (tables) is loaded in the Tabs section below.

  • Benefit: Provides immediate visual feedback to the user and removes perceived latency.

  • Benefit: Eliminates Layout Shift: Static content no longer "jumps" when dynamic data loads.

  • Fixed Column Sizing: Applied explicit width percentages and minimum constraints to table headers in borrow-table.tsx.

    • Benefit: Prevents the table layout from shifting as cell data populates, ensuring a stable UI from the first render frame.
  • Efficient Sheet Rendering: Refactored the Borrow/Earn tables to use a single <Sheet> instance shared across all rows, rather than rendering a separate Sheet for every row.

    • Benefit: reduces the DOM node count and React component overhead.
    • Benefit: Less code to compile and smaller bundle size
  • CSS Optimization: Extracted and reused common CSS utility classes for CtaCard components, reducing duplication.

  • Code Splitting & Lazy Loading:

    • Lazy-loaded BorrowSheetContent and EarnSheetContent so its dependencies are only fetched when a user actually interacts with the sheet.

📊 Metrics

Previous Lighthouse Score:

Earn Borrow
image image

Current Lighthouse Score (built version of branch):

Earn Borrow
image image

Visual Comparison:

Borrow (before)

Screen.Recording.2025-11-20.at.00.01.14.mov

Borrow (after)

Screen.Recording.2025-11-20.at.00.21.09.mov

Earn (before)

Screen.Recording.2025-11-20.at.00.23.08.mov

Earn (after)

Screen.Recording.2025-11-20.at.00.17.11.mov

📝 Notes on Image Optimization

If I had direct access to the infrastructure, further optimizations could be made:

  1. CDN Cache TTL: I would configure a longer Cache-Control TTL for assets on cdn.morpho.org (like earn-animation.webm) to reduce repeated downloads for returning users.
  2. Image Resizing: The images for RE7 (800x800) and MEV Capital (150x150) are currently being rendered at just 16x16.
    • Action: I would upload optimized 16x16 versions of these logos to the CDN to significantly reduce payload size.
    • Query: If cdn.morpho.org supports on-the-fly resizing via query params (e.g., ?width=16), please let me know so I can update the curator.ts file to use them instead of uploading new files.
- -
image image

@vercel
Copy link

vercel bot commented Nov 20, 2025

@fscgustavo is attempting to deploy a commit to the Morpho Association Team on Vercel.

A member of the Team first needs to authorize it.

@fscgustavo fscgustavo force-pushed the performance-optimizations branch from e88a87c to 7a10e91 Compare November 20, 2025 03:45
Copy link
Author

Choose a reason for hiding this comment

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

Saving 1.7KB by using https://vecta.io/nano

image

</SafeLinksProvider>
<AddressScreeningModal />
</AddressScreeningProvider>
<TooltipProvider>
Copy link
Author

@fscgustavo fscgustavo Nov 20, 2025

Choose a reason for hiding this comment

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

Following Radix UI best practices, wrapping the entire application in a single is efficient because it allows all tooltip components to share a common configuration (like delay duration). This avoids redundant provider instances, keeps the component tree cleaner and with less code to compile.

https://www.radix-ui.com/primitives/docs/components/tooltip#provider

If a specific tooltip ever needs unique timing or behavior, we can still nest a separate provider locally for that case, but the global one handles the default case for the whole app.

defaultValue={userMarkets.length > 0 ? "user-positions" : "markets"}
className="w-full max-w-7xl px-2 lg:px-8"
>
<TabsList className="grid grid-cols-2 p-0">
Copy link
Author

Choose a reason for hiding this comment

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

Here we are matching the strategy used on the main Morpho app. By placing the static Call-to-Action (CTA) cards at the top, we ensure immediate visual feedback for the user without blocking the render. The data-heavy, asynchronous content (tables for user positions and markets) is moved to the bottom within Tabs. This separation allows the core interface to load instantly while heavier data fetches happen in the background, significantly improving perceived performance and eliminating the layout shift that would occur if dynamic content pushed the static elements down after loading.

}}
/>
</div>
<BoxTopRoundedCorners>
Copy link
Author

Choose a reason for hiding this comment

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

BoxTopRoundedCorners to standardize the layout structure and avoid repetition

<Table className="border-separate border-spacing-y-3">
<TableHeader className="bg-primary">
<TableRow className="text-xs font-light">
<TableHead className="text-secondary-foreground w-[30%] min-w-[18rem] rounded-l-lg pl-4">Vault</TableHead>
Copy link
Author

Choose a reason for hiding this comment

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

We applied explicit width percentages and minimum width constraints (e.g., w-[23%] min-w-[13rem]) to the table headers. By strictly defining the column geometry upfront, we prevent the browser from auto-calculating widths based on content length. This ensures the table structure remains completely stable regardless of the data loaded, effectively eliminating the layout shift
(CLS) that typically occurs when dynamic content populates the cells.

})}
</TableBody>
</Table>
{selectedRow && (
Copy link
Author

Choose a reason for hiding this comment

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

Refactored the table to use a single instance that wraps the entire table, rather than rendering a separate Sheet component for every single row. The rows now act as SheetTriggers that simply update the selectedMarket state. This reduces the number of React component instances, leading to a lighter DOM and less code to compile.

Additionally, the EarnSheetContent is now imported via lazy() loading. This ensures that the heavy logic and dependencies required for the sheet (transaction forms, detailed market data) are only fetched and parsed when the user actually opens a position

url: "https://www.cicada.partners/",
imageSrc:
"https://static.wixstatic.com/media/f9d184_1702c7c11ec647f480ad8e0c8c4859c3~mv2.png/v1/fill/w_120,h_155,al_c,lg_1,q_85,enc_avif,quality_auto/Cicada%20Image_Black%20on%20White_25%25.png",
"https://static.wixstatic.com/media/f9d184_1702c7c11ec647f480ad8e0c8c4859c3~mv2.png/v1/fill/w_16,h_16,al_c,lg_1,q_85,enc_avif,quality_auto/Cicada%20Image_Black%20on%20White_25%25.png",
Copy link
Author

Choose a reason for hiding this comment

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

This resolves the following report

image

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant