Skip to content

Optimize club site loading and scroll performance#474

Open
Adr1an04 wants to merge 2 commits into
mainfrom
clubsiteoptimization
Open

Optimize club site loading and scroll performance#474
Adr1an04 wants to merge 2 commits into
mainfrom
clubsiteoptimization

Conversation

@Adr1an04

@Adr1an04 Adr1an04 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Why

The Club site was loading and animating more than it needed to.

Some things were happening too early, like fetching events/team data and preloading routes before the user clicked anything. Fast scrolling also felt weird because some scroll-based animations were slightly delayed instead of moving with the page right away.

This PR makes the site lighter on first load and smoother while scrolling.

What

Closes: #473

  • Made scroll animations cheaper by caching element positions instead of checking layout on every scroll frame.
  • Fixed fast-scroll drift by removing delayed transitions from scroll-linked elements.
  • Delayed homepage events loading until the events section is close to the screen.
  • Delayed Teams roster loading until the user scrolls to the team section.
  • Turned off automatic route prefetching in the navbar and footer so the browser does not download extra pages immediately.
  • Removed priority loading from carousel images since they are not the main first-screen image.
  • Added a preconnect to the Club asset CDN so large remote images can start loading faster.

Test Plan

  • Ran pnpm --filter=@forge/club format
  • Ran pnpm --filter=@forge/club lint
  • Ran pnpm --filter=@forge/club typecheck
  • Ran pnpm --filter=@forge/club build

Also checked the built site:

  • Fast scroll no longer calls getBoundingClientRect()
  • Scroll-drift elements now use transition: none
  • Teams roster request does not fire before scrolling
  • Teams roster request fires after scrolling to the section which is more optimized for network calls should help with italy loading
  • Internal route prefetches stayed at 0 which is fire
  • Lighthouse showed 94 Performance and 100 for Accessibility, Best Practices, and SEO which is good on local testing before deployment ngl

Checklist

  • No changes to env or db

Summary by CodeRabbit

Release Notes

Performance

  • Events and team roster sections now load only when scrolled into view, improving initial page load times
  • Optimized link navigation to reduce unnecessary background resource prefetching
  • Enhanced animation performance with GPU acceleration improvements

Improvements

  • Smoother scroll-based animations and transitions throughout the site
  • Refined image loading strategy for better performance

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Adr1an04, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 13 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 6134659c-26d6-4aa1-8154-e274bc244865

📥 Commits

Reviewing files that changed from the base of the PR and between 8f2bc9d and 0f668da.

📒 Files selected for processing (1)
  • apps/club/src/app/_components/club-motion-runtime.tsx
📝 Walkthrough

Walkthrough

The PR optimizes the KnightHacks club site across three axes: all internal next/link components gain prefetch={false}, events/teams sections adopt IntersectionObserver-based lazy loading for Blade data, and the scroll-motion runtime is refactored to precompute and cache DOM metrics rather than querying the DOM each animation frame. Timeline fill/scrubber animations migrate from height/top properties to GPU-composited transform.

Changes

Club Site Performance Optimizations

Layer / File(s) Summary
Disable link prefetching site-wide
apps/club/src/app/_components/navbar.tsx, apps/club/src/app/_components/footer.tsx, apps/club/src/app/_components/club-content-page.tsx, apps/club/src/app/_components/home-events.tsx, apps/club/src/app/_components/redirect.tsx, apps/club/src/app/about/page.tsx, apps/club/src/app/not-found.tsx
All internal next/link usages add prefetch={false}, preventing automatic prefetch network requests for every visible link.
Asset preconnect and image priority removal
apps/club/src/app/layout.tsx, apps/club/src/app/_components/home-community-carousel.tsx, apps/club/src/app/sponsors/sponsors-client.tsx
Root layout adds a <link rel="preconnect"> to the club asset CDN origin. priority hints on carousel and sponsor next/image components are removed.
IntersectionObserver lazy loading for events and teams
apps/club/src/app/_components/home-events.tsx, apps/club/src/app/events/events-client.tsx, apps/club/src/app/teams/teams-client.tsx
Each component introduces a ref + shouldLoad state flag, an IntersectionObserver effect with setTimeout fallback, and gates Blade data-fetch effects behind that flag. Section containers receive the matching ref.
Navbar scroll-fade refactor to rAF listener
apps/club/src/app/_components/navbar.tsx
Removes framer-motion useScroll/useMotionValueEvent; replaces with a window scroll listener that computes delta via a previousScrollY ref, throttles via requestAnimationFrame, and drives rawNavFadeProgress.
Scroll-motion runtime: cached metrics and refactored update loop
apps/club/src/app/_components/club-motion-runtime.tsx
Introduces TypeScript interfaces and cached arrays for drift, hero, and timeline targets. observeElement is simplified to pure observer registration. updateScrollMotion loops over cached arrays instead of querying the DOM per frame. A resize handler repopulates caches. Tilt listeners are registered and cleaned up conditionally.
Timeline animation: height to GPU transform
apps/club/src/app/hackathons/hackathon-history.tsx, apps/club/src/app/globals.css
Timeline fill switches from height-based to scaleY transform; scrubber from top/clamp to translate3d. CSS adds transform-origin, will-change, and top: 0. Scroll-drift elements use transition: none. Scroll-progress bar gains translateZ(0).

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant IntersectionObserver
  participant Component as HomeEvents / EventsClient / TeamsClient
  participant BladeAPI as Blade API

  Browser->>Component: mount, attach containerRef/eventsSectionRef/rosterSectionRef
  Component->>IntersectionObserver: observe(sectionRef, rootMargin: 200px)
  alt IntersectionObserver unavailable
    Component->>Component: setTimeout → setShouldLoad(true)
  else Element intersects viewport
    IntersectionObserver-->>Component: isIntersecting = true
    Component->>Component: setShouldLoad(true), observer.disconnect()
  end
  Component->>BladeAPI: loadClubEvents() / loadEvents() / fetchRoster()
  BladeAPI-->>Component: data returned → render content
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • KnightHacks/forge#450: Introduced the ContentButton component in club-content-page.tsx that this PR modifies to add prefetch={false} on its internal next/link.

Suggested labels

UI

Suggested reviewers

  • DVidal1205
🚥 Pre-merge checks | ✅ 6 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title does not start with a bracketed issue number as required by the guidelines (e.g., "[#473]"). Update the title to start with the issue number in brackets: "[#473] Optimize club site loading and scroll performance" or similar.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR comprehensively addresses the optimization objectives in #473, implementing scroll performance caching, deferred loading, prefetch disabling, and priority image removal.
Out of Scope Changes check ✅ Passed All changes align with the stated optimization objectives; no unrelated modifications detected across the 14 modified files.
No Hardcoded Secrets ✅ Passed No hardcoded API keys, passwords, tokens, or secrets found in any of the 15 modified files; all configuration properly externalized via imports.
Validated Env Access ✅ Passed All 15 modified files correctly use validated env imports (import { env } from "~/env"). Only env.ts contains process.env, which is the appropriate configuration file.
No Typescript Escape Hatches ✅ Passed No TypeScript escape hatches found: zero instances of 'any', @ts-ignore, @ts-expect-error, or non-null assertions (!) across all 14 modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch clubsiteoptimization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/club/src/app/_components/club-motion-runtime.tsx (1)

47-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the now-dead revealElements bookkeeping.

After the synchronous reveal scan was removed, this set is only mutated and never read, so it adds state without behavior.

Proposed cleanup
     const observedElements = new WeakSet<Element>();
-    const revealElements = new Set<Element>();
     let driftElements: DriftState[] = [];
     let heroElements: HeroState[] = [];
     let timelines: TimelineState[] = [];

     function revealElement(element: Element) {
       element.classList.add("is-visible");
       revealObserver.unobserve(element);
-      revealElements.delete(element);
     }

     function observeElement(element: Element) {
       if (observedElements.has(element)) return;

       observedElements.add(element);
       element.classList.add("is-motion-observed");
-      revealElements.add(element);

       revealObserver.observe(element);
     }

As per coding guidelines, “Do not leave debug logging, dead code, or commented-out experiments behind.”

Also applies to: 102-105, 122-128

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/club/src/app/_components/club-motion-runtime.tsx` around lines 47 - 48,
The `revealElements` Set variable is declared but only mutated and never read,
making it dead code that should be removed entirely. Delete the declaration of
the `revealElements` variable and remove all places where it is mutated or
referenced throughout the file (including at the locations mentioned at lines
102-105 and 122-128) to clean up unused state variables per coding guidelines.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/club/src/app/_components/club-motion-runtime.tsx`:
- Around line 147-165: The current code only invalidates cached scroll targets
when DOM nodes are added or removed via the mutationObserver, but cached metrics
like offsetTop, offsetHeight, and scrollHeight can change due to layout reflow
from images, fonts, or content changes without DOM mutations. Add a
ResizeObserver alongside the existing MutationObserver to detect layout changes
on observed elements. When resize events are detected, also call
refreshScrollTargets() to ensure the cached geometry stays in sync. Apply this
ResizeObserver to the same elements being tracked by the MutationObserver in the
observeTree function to maintain consistent invalidation across all relevant
layout changes.
- Around line 145-165: The hasTiltTargets variable is calculated once before the
MutationObserver runs, so newly added tilt target elements never receive their
pointermove/pointerout listeners. In the MutationObserver callback's addedNodes
loop, after calling observeTree(node), check if the added node matches the
TILT_SELECTOR or contains descendants matching TILT_SELECTOR, and attach the
tilt event listeners to those matching elements. This ensures dynamically
inserted tilt targets get the necessary pointer event handlers even if no tilt
targets existed in the initial DOM.

---

Nitpick comments:
In `@apps/club/src/app/_components/club-motion-runtime.tsx`:
- Around line 47-48: The `revealElements` Set variable is declared but only
mutated and never read, making it dead code that should be removed entirely.
Delete the declaration of the `revealElements` variable and remove all places
where it is mutated or referenced throughout the file (including at the
locations mentioned at lines 102-105 and 122-128) to clean up unused state
variables per coding guidelines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: e52b7511-4c10-4478-b15c-7afe4ead2130

📥 Commits

Reviewing files that changed from the base of the PR and between 58ad852 and 8f2bc9d.

📒 Files selected for processing (15)
  • apps/club/src/app/_components/club-content-page.tsx
  • apps/club/src/app/_components/club-motion-runtime.tsx
  • apps/club/src/app/_components/footer.tsx
  • apps/club/src/app/_components/home-community-carousel.tsx
  • apps/club/src/app/_components/home-events.tsx
  • apps/club/src/app/_components/navbar.tsx
  • apps/club/src/app/_components/redirect.tsx
  • apps/club/src/app/about/page.tsx
  • apps/club/src/app/events/events-client.tsx
  • apps/club/src/app/globals.css
  • apps/club/src/app/hackathons/hackathon-history.tsx
  • apps/club/src/app/layout.tsx
  • apps/club/src/app/not-found.tsx
  • apps/club/src/app/sponsors/sponsors-client.tsx
  • apps/club/src/app/teams/teams-client.tsx
💤 Files with no reviewable changes (2)
  • apps/club/src/app/_components/home-community-carousel.tsx
  • apps/club/src/app/sponsors/sponsors-client.tsx

Comment thread apps/club/src/app/_components/club-motion-runtime.tsx Outdated
Comment thread apps/club/src/app/_components/club-motion-runtime.tsx
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.

Club Site optimzation

1 participant