From bd58839d7957b6e3625caf511d35d108a1bde7f1 Mon Sep 17 00:00:00 2001 From: Krish Agarwal Date: Sat, 31 Oct 2026 17:35:35 +0530 Subject: [PATCH 1/3] Poll the seat counter only while the register form is open The register/log-in overlay is mounted, closed, at idle on every home page visit, and its seat counter then polled GET /api/seats every 30 s for as long as the tab stayed visible: about 2 Vercel edge requests a minute per open tab, from visitors who never opened the form. Measured on production: 7 calls in under 4 minutes of idling on /. The counter now mounts when the overlay opens (immediate fetch, then the same 30 s polling) and unmounts after it closes. The seat check when DETAILS moves on to PAYMENT (authSubmit -> fetchSeatCount) and /api/seats itself are unchanged. Co-Authored-By: Claude Opus 5.5 --- src/components/innovision/Innovision.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/innovision/Innovision.tsx b/src/components/innovision/Innovision.tsx index a1a7c6c..6c8c6b6 100644 --- a/src/components/innovision/Innovision.tsx +++ b/src/components/innovision/Innovision.tsx @@ -2947,8 +2947,10 @@ export class Innovision extends Component { authSub: reg ? 'Three short stops to register for Innovision 2026 at NIT Rourkela.' : am === 'login' ? 'Sign in or create your account with Google.' : P.status === 'CONFIRMED' ? 'Your payment is verified. Your INNO pass is always available here.' : P.status === 'REJECTED' ? (this.canRegisterAgain() ? 'See why below, then register again with the right details.' : 'Please contact the registration helpdesk for assistance.') : 'Your payment is under review by the registration team. Your pass appears here after approval.', - // The seat counter (GET /api/seats) beside the registration form. - showSeats: reg, + // The seat counter (GET /api/seats) beside the registration form. Only while the overlay is open: it is mounted, + // closed, at idle (warmDeferred), and a mounted counter polls every 30 s. `auth` turns true before the overlay + // animates in and false after it has animated out, so the counter is never cut off mid-animation. + showSeats: reg && s.auth, // REGISTER AGAIN on a rejected pass; the pass itself decides whether to show it (attempts left). registerAgain: REGISTRATIONS_OPEN ? this.registerAgain : undefined, idCardUploaded: this.idCardUploaded, From 237c602820245e0be1d1a78d972ac0c360485fc3 Mon Sep 17 00:00:00 2001 From: Krish Agarwal Date: Sat, 31 Oct 2026 17:38:26 +0530 Subject: [PATCH 2/3] Stop prefetching /merch on every home page view warmDeferred called router.prefetch for OWN_PAGES (/merch while registrations are open) at idle on every visit. Next.js 16 splits that into 3 RSC requests (max-age=0, so never browser-cached): 3 Vercel edge requests per home page view, first or repeat, plus the merch page chunk. MERCH links still switch client-side through the existing document click listener; the route is fetched on click instead. Co-Authored-By: Claude Opus 5.5 --- src/components/innovision/Innovision.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/innovision/Innovision.tsx b/src/components/innovision/Innovision.tsx index 6c8c6b6..f7bc65e 100644 --- a/src/components/innovision/Innovision.tsx +++ b/src/components/innovision/Innovision.tsx @@ -728,8 +728,10 @@ export class Innovision extends Component { this._warmed = true; if (slowNet()) return; // The overlay and view a visitor is most likely to open next are fetched ahead too (their own hover/tap also does). - // The pages of their own too, so MERCH (and REGISTER while closed) open at once (a no-op on the dev server). - this.cleanups.push(this.idle(() => { this.prefetch('auth'); this.prefetch('detail'); OWN_PAGES.forEach((p) => this.props.router?.prefetch(p)); })); + // The pages of their own (OWN_PAGES: MERCH, REGISTER while closed) are NOT prefetched: each router.prefetch cost 3 + // uncached RSC requests on every home page view, for a page few visitors open. A click on one still switches + // client-side (the document click listener in componentDidMount), fetching the route then. + this.cleanups.push(this.idle(() => { this.prefetch('auth'); this.prefetch('detail'); })); // The menu (where the top bar has a MENU button) and the register / log-in overlay are mounted, closed, ahead of // time: mounting one on its first tap blocked a phone for ~100 ms just as its reveal began. One per idle period. this.cleanups.push(this.idle(() => { From b801cc8058d2cb86aadf2c16eccb8535dfefba8f Mon Sep 17 00:00:00 2001 From: Krish Agarwal Date: Sat, 31 Oct 2026 17:39:16 +0530 Subject: [PATCH 3/3] Turn off viewport prefetching for events page links Every on /events, /events/ and in EventsChrome (also the frame of /privacy and /terms) was prefetched as it entered the viewport: 3 to 5 RSC requests per page view on production, none browser-cached. With prefetch={false} the links still navigate client-side and fetch the route on click. Co-Authored-By: Claude Opus 5.5 --- src/app/events/[world]/page.tsx | 4 ++-- src/app/events/page.tsx | 6 +++--- src/components/events/EventsChrome.tsx | 20 +++++++++++--------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/app/events/[world]/page.tsx b/src/app/events/[world]/page.tsx index ba62e12..d73f78c 100644 --- a/src/app/events/[world]/page.tsx +++ b/src/app/events/[world]/page.tsx @@ -95,7 +95,7 @@ export default async function WorldPage({ params }: PageProps<'/events/[world]'>

{w.intro}

ENTER THE WORLD - ALL EVENTS + ALL EVENTS
@@ -123,7 +123,7 @@ export default async function WorldPage({ params }: PageProps<'/events/[world]'>

NEXT WORLD · {next.statL.toUpperCase()}

-

{next.name} →

+

{next.name} →

diff --git a/src/app/events/page.tsx b/src/app/events/page.tsx index 0134f8b..c082c9e 100644 --- a/src/app/events/page.tsx +++ b/src/app/events/page.tsx @@ -47,7 +47,7 @@ export default async function EventsPage() {

EXPLORE THE WORLDS - BACK TO INNOVISION + BACK TO INNOVISION
@@ -59,7 +59,7 @@ export default async function EventsPage() { {worlds.map(({ w, list }) => (

{w.statL.toUpperCase()} · {w.category.toUpperCase()}

-

{w.name}

+

{w.name}

{w.tagline}

{w.intro}

{list.length > 0 ? ( @@ -73,7 +73,7 @@ export default async function EventsPage() {

LINE-UP ANNOUNCED SOON

)}
- {w.name.toUpperCase()} → + {w.name.toUpperCase()} → ENTER THE WORLD
diff --git a/src/components/events/EventsChrome.tsx b/src/components/events/EventsChrome.tsx index e4bcc89..fa21aa3 100644 --- a/src/components/events/EventsChrome.tsx +++ b/src/components/events/EventsChrome.tsx @@ -10,6 +10,8 @@ import styles from './EventsPage.module.css'; * BreadcrumbList JSON-LD describes, and a footer linking every world page. Server-rendered, no client JS. * Links into the animated explorer (/#/...) are plain , not : the explorer reads its hash route once its * loader finishes, and a full page load is what keeps that hash (as for a visitor arriving from search). + * Every here (and on the /events pages) has prefetch={false}: by default each one in the viewport costs uncached + * RSC requests on Vercel (3 to 5 per page view were measured). A click still navigates client-side, fetching then. * * @param trail [name, path] from the home page down to this page; the last entry is the current page. */ @@ -19,7 +21,7 @@ export default function EventsChrome({ trail, children }: { trail: [string, stri