fix(seo): ssr the wallet list on /wallets/find-wallet#18067
Merged
Conversation
Restores server-rendered wallet content so Googlebot sees real wallet names, chips, devices, languages, supported chains, and the Visit website link instead of the Loading skeleton. - Remove ssr:false from FindWalletProductTable dynamic import so the component is emitted in initial HTML. - Progressive virtualization in ProductTable/List: SSR + first client render emit the first 30 rows unvirtualized (in natural flow, no absolute positioning) so server and hydration HTML match; the useWindowVirtualizer takes over on mount for scroll perf on the full list. - Drop the MediaQuery wrappers around WalletInfo's mobile/desktop layouts. Both are already gated by Tailwind responsive classes (hidden lg:flex / lg:hidden), but MediaQuery returned null on the server (no fallbackMatches), which stripped the wallet name and Visit website button from SSR HTML. Fixes #17717
The component rendered two parallel trees for image+name+PersonaTags+ ChainImages, gated only by `hidden`/`lg:hidden`. Both hydrated on every device, doubling the per-row React node count for 52 rows. Single CSS Grid (`grid-cols-[auto_1fr]`, image `lg:row-span-full`, right-column children `col-span-2 lg:col-start-2`) reproduces both layouts from one tree. The two duplicated stripe spacers collapse into a single absolute element on the relative outer container. Cuts ~30% of WalletInfo nodes per row, attacking the dominant early-click hydration cost identified in the find-wallet INP follow-ups.
Filtering recomputed `filteredData` and React unmounted/remounted every non-matching Row on each filter change — cheap per click, but each remount re-instantiates 52 WalletInfo subtrees in aggregate as users tweak. Render every wallet always; toggle visibility with `display: none` via a matchedIds Set. Filter changes become re-renders that flip a class — no mount/unmount of WalletInfo/Row subtrees. ProductTable derives `matchedIds` from the same filterFn output as filteredData and exposes it (plus `data`) through the children render prop. List takes optional `matchedIds`; when omitted, every row is visible (legacy path used by Layer2NetworksTable). FindWalletProductTable switches to the new path; SSR still emits all 52 rows for SEO.
The `cn()` call already encodes the conflict-resolution behavior; the prose comment was duplicating what the conditional already says.
✅ Deploy Preview for ethereumorg ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Same pattern as /wallets/find-wallet (#18067, #17717): the page mounted Layer2NetworksTable through a `dynamic(..., { ssr: false })` wrapper, so Googlebot indexed only the loading skeleton — zero network names in the HTML. Drop the lazy wrapper and import directly. Add `"use client"` to `Layer2NetworksTable` since it needs hooks (`useTranslation`, `useNetworkFilters`) and is no longer crossing the boundary via `dynamic()`. Delete the now-unused `lazy.tsx` and `loading.tsx`. The shared `ProductTable` SSR fixes (`useSearchParams` → window.location, `MediaQuery` wrapper removal) already landed in #18067 and apply unchanged to this consumer.
7 tasks
wackerow
reviewed
Apr 30, 2026
| {/* Open-state stripe (desktop only), sits in the image-column gutter. */} | ||
| <div | ||
| aria-hidden | ||
| className={`pointer-events-none absolute top-14 -bottom-9 left-7 hidden w-1 -translate-x-1/2 lg:group-[[open]]/collapsible:block ${wallet.twBackgroundColor}`} |
Co-Authored-By: wackerow <54227730+wackerow@users.noreply.github.com>
wackerow
approved these changes
Apr 30, 2026
Member
wackerow
left a comment
There was a problem hiding this comment.
Looks good @pettinarip! Spotting a could things we could clean up, but they don't need to block.
The gutter decoration line loads a little buggy when expanding the card—could be nice to see if there is a cleaner way to have this load as a single dom element, perhaps even with a smooth animation so it's not so abrupt.
Also some clean up here with string interpolating classes–could use cn() to pass this as it's own arg.
Pushed a commit to patch an RTL bug, adjusting the positioning of that gutter decoration. Pulling in!
fix(seo): ssr the network list on /layer-2/networks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Fixes #17717 — Googlebot was indexing the loading skeleton on
/wallets/find-wallet(zero wallet content in SSR HTML). This branch restores SSR coverage for SEO, then defends INP against the regression that SSR introduces.SSR coverage
ssr: false, dropMediaQuerywrappers, drop the dynamic-import skeleton, replaceuseSearchParams()withwindow.location.searchto avoid Next's silent CSR-bailout on this SSG'd page.INP defense
Collapsiblewith native<details>/<summary>(with the samefocus-visiblering asButton).WalletInfodesktop + mobile trees behind a single CSS Grid (was two parallel hidden trees, both hydrating).matchedIds: Set<string>— filter clicks no longer churn 52 subtrees.Rowismemo'd and the lazy sub-component is passed viachildren, so closed rows skip re-render on filter changes.Verification
INP (Playwright iPhone 12, 4× CPU throttle):
¹ dev's "early" measurement happens post-hydration since the page renders client-side; not directly comparable. Steady-state INP is equivalent. The remaining early-click cost is concentrated in the hydration window of the 52 wallet trees the page now ships.
Wire size (brotli, deploy-preview-18067 vs prod): 78 kb → 107 kb (+29 kb). Uncompressed grows 535 kb → 1452 kb because we now ship 52 cards' worth of markup + inline lucide SVGs; brotli compresses the repetition well, so wire impact is small.
Test plan
Layer2NetworksTable(otherProductTableconsumer) unaffected<summary>doesn't toggle the row (manual)