Skip to content

Commit 0557f4b

Browse files
Merge branch 'perf/homepage-loading-speed' into perf/homepage-combined
# Conflicts: # src/pages/_app.tsx
2 parents 9fa609b + 6cea655 commit 0557f4b

11 files changed

Lines changed: 264 additions & 61 deletions

File tree

‎src/components/Common/ErrorBoundary.tsx‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import Link from 'next/link'
22
import Head from 'next/head'
3+
import dynamic from 'next/dynamic'
34
import { Component } from 'react'
4-
import Chessground from '@react-chess/chessground'
55
import { Header } from './Header'
66
import { Footer } from './Footer'
77
import { trackErrorEncountered } from 'src/lib/analytics'
88

9+
const ErrorChessground = dynamic(() => import('@react-chess/chessground'), {
10+
ssr: false,
11+
loading: () => <div className="h-full w-full rounded bg-glass" />,
12+
})
13+
914
interface Props {
1015
children: React.ReactNode
1116
}
@@ -109,7 +114,7 @@ export class ErrorBoundary extends Component<Props, State> {
109114
<div className="flex flex-1 flex-col items-center justify-center gap-8 p-6">
110115
<div className="flex max-w-2xl flex-col items-center gap-6 text-center">
111116
<div className="h-[200px] w-[200px] opacity-75">
112-
<Chessground
117+
<ErrorChessground
113118
contained
114119
config={{
115120
fen: 'rn1qkb1r/ppp1pBpp/5n2/4N3/8/2N5/PPPP1PPP/R1BbK2R b KQkq - 0 7',

‎src/components/Common/FeedbackButton.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useContext } from 'react'
2-
import { AuthContext } from 'src/contexts'
2+
import { AuthContext } from 'src/contexts/AuthContext'
33

44
export const FeedbackButton: React.FC = () => {
55
const { user } = useContext(AuthContext)

‎src/components/Common/PlaySetupModal.tsx‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
TimeControlOptionNames,
1313
TimeControlOptions,
1414
} from 'src/types'
15-
import { ModalContext } from 'src/contexts'
15+
import { ModalContext } from 'src/contexts/ModalContext'
1616
import { ModalContainer } from './ModalContainer'
1717

1818
const maiaOptions = [
@@ -72,7 +72,7 @@ function OptionSelect<T>({
7272
)
7373
}
7474

75-
interface Props {
75+
export interface PlaySetupModalProps {
7676
playType: PlayType
7777
player?: Color
7878
timeControl?: TimeControl
@@ -89,7 +89,9 @@ interface Props {
8989
modalSubtitle?: string
9090
}
9191

92-
export const PlaySetupModal: React.FC<Props> = (props: Props) => {
92+
export const PlaySetupModal: React.FC<PlaySetupModalProps> = (
93+
props: PlaySetupModalProps,
94+
) => {
9395
const { setPlaySetupModalProps } = useContext(ModalContext)
9496
const router = useRouter()
9597
const { push } = router

‎src/components/Home/GameCarousel.tsx‎

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,10 @@ export const GameCarousel: React.FC = () => {
181181
const router = useRouter()
182182
const [games, setGames] = useState<GameData[]>(SAMPLE_GAMES)
183183
const [isPaused, setIsPaused] = useState(false)
184+
const [isCarouselVisible, setIsCarouselVisible] = useState(false)
185+
const [shouldLoadLiveGames, setShouldLoadLiveGames] = useState(false)
184186
const abortController = useRef<AbortController | null>(null)
187+
const sectionRef = useRef<HTMLElement>(null)
185188
const carouselRef = useRef<HTMLDivElement>(null)
186189

187190
const handleGameStart = useCallback((gameData: StreamedGame) => {
@@ -300,7 +303,7 @@ export const GameCarousel: React.FC = () => {
300303
}, [])
301304

302305
useEffect(() => {
303-
if (isPaused || !carouselRef.current) return
306+
if (isPaused || !isCarouselVisible || !carouselRef.current) return
304307

305308
const scroll = () => {
306309
if (carouselRef.current) {
@@ -317,18 +320,74 @@ export const GameCarousel: React.FC = () => {
317320

318321
const interval = setInterval(scroll, 20)
319322
return () => clearInterval(interval)
320-
}, [isPaused])
323+
}, [isPaused, isCarouselVisible])
321324

322325
useEffect(() => {
323-
fetchNewGame()
324-
fetchBroadcast()
326+
const section = sectionRef.current
327+
328+
if (!section || typeof IntersectionObserver === 'undefined') {
329+
setIsCarouselVisible(true)
330+
setShouldLoadLiveGames(true)
331+
return
332+
}
333+
334+
const observer = new IntersectionObserver(
335+
([entry]) => {
336+
setIsCarouselVisible(entry.isIntersecting)
337+
338+
if (entry.isIntersecting) {
339+
setShouldLoadLiveGames(true)
340+
}
341+
},
342+
{ rootMargin: '300px 0px' },
343+
)
344+
345+
observer.observe(section)
346+
347+
return () => observer.disconnect()
348+
}, [])
349+
350+
useEffect(() => {
351+
if (!shouldLoadLiveGames) return
352+
353+
const startLiveFeeds = () => {
354+
fetchNewGame()
355+
fetchBroadcast()
356+
}
357+
358+
const windowWithIdleCallback = window as Window & {
359+
requestIdleCallback?: (
360+
callback: IdleRequestCallback,
361+
options?: IdleRequestOptions,
362+
) => number
363+
cancelIdleCallback?: (handle: number) => void
364+
}
365+
366+
if (windowWithIdleCallback.requestIdleCallback) {
367+
const idleHandle = windowWithIdleCallback.requestIdleCallback(
368+
startLiveFeeds,
369+
{
370+
timeout: 2500,
371+
},
372+
)
373+
374+
return () => {
375+
windowWithIdleCallback.cancelIdleCallback?.(idleHandle)
376+
if (abortController.current) {
377+
abortController.current.abort()
378+
}
379+
}
380+
}
381+
382+
const timeoutId = window.setTimeout(startLiveFeeds, 1000)
325383

326384
return () => {
385+
window.clearTimeout(timeoutId)
327386
if (abortController.current) {
328387
abortController.current.abort()
329388
}
330389
}
331-
}, [fetchNewGame, fetchBroadcast])
390+
}, [fetchNewGame, fetchBroadcast, shouldLoadLiveGames])
332391

333392
const handleGameClick = useCallback(
334393
(game: GameData) => {
@@ -340,7 +399,10 @@ export const GameCarousel: React.FC = () => {
340399
)
341400

342401
return (
343-
<section className="relative w-full overflow-y-visible py-6">
402+
<section
403+
ref={sectionRef}
404+
className="relative w-full overflow-y-visible py-6"
405+
>
344406
<div className="relative mb-10 w-full overflow-y-visible">
345407
<motion.div
346408
ref={carouselRef}

‎src/components/Home/HomeHero.tsx‎

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88

99
import { PlayType } from 'src/types'
1010
import { getGlobalStats } from 'src/api'
11-
import { AuthContext, ModalContext } from 'src/contexts'
11+
import { AuthContext } from 'src/contexts/AuthContext'
12+
import { ModalContext } from 'src/contexts/ModalContext'
1213
import { AnimatedNumber } from 'src/components/Common/AnimatedNumber'
1314

1415
interface Props {
@@ -125,17 +126,45 @@ export const HomeHero: React.FC<Props> = ({ scrollHandler }: Props) => {
125126
// Fetch global stats and set up periodic updates
126127
useEffect(() => {
127128
const fetchGlobalStats = async () => {
128-
const data = await getGlobalStats()
129-
setGlobalStats(data)
129+
try {
130+
const data = await getGlobalStats()
131+
setGlobalStats(data)
132+
} catch (error) {
133+
console.error('Error fetching global stats:', error)
134+
}
135+
}
136+
137+
const windowWithIdleCallback = window as Window & {
138+
requestIdleCallback?: (
139+
callback: IdleRequestCallback,
140+
options?: IdleRequestOptions,
141+
) => number
142+
cancelIdleCallback?: (handle: number) => void
130143
}
131144

132-
// Fetch immediately
133-
fetchGlobalStats()
145+
const idleHandle = windowWithIdleCallback.requestIdleCallback?.(
146+
fetchGlobalStats,
147+
{
148+
timeout: 2000,
149+
},
150+
)
151+
const timeoutId =
152+
idleHandle === undefined
153+
? window.setTimeout(fetchGlobalStats, 800)
154+
: undefined
134155

135156
// Update every 5 minutes
136157
const interval = setInterval(fetchGlobalStats, 5 * 60 * 1000)
137158

138-
return () => clearInterval(interval)
159+
return () => {
160+
if (idleHandle !== undefined) {
161+
windowWithIdleCallback.cancelIdleCallback?.(idleHandle)
162+
}
163+
if (timeoutId !== undefined) {
164+
window.clearTimeout(timeoutId)
165+
}
166+
clearInterval(interval)
167+
}
139168
}, [])
140169

141170
return (

‎src/components/Home/Sections/AdditionalFeaturesSection.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useContext, ReactNode } from 'react'
33
import { motion } from 'framer-motion'
44
import { useInView } from 'react-intersection-observer'
55

6-
import { ModalContext } from 'src/contexts'
6+
import { ModalContext } from 'src/contexts/ModalContext'
77
import { PlayType } from 'src/types'
88
import { StarIcon, BrainIcon, BotOrNotIcon } from 'src/components/Common/Icons'
99

‎src/components/Home/Sections/PlaySection.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import type { DrawShape } from 'chessground/draw'
66
import Chessground from '@react-chess/chessground'
77
import { useInView } from 'react-intersection-observer'
88
import { useContext, useState, useEffect } from 'react'
9-
import { ModalContext, AuthContext } from 'src/contexts'
9+
import { AuthContext } from 'src/contexts/AuthContext'
10+
import { ModalContext } from 'src/contexts/ModalContext'
1011

1112
const DEMO_POSITION = {
1213
fen: 'r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/3P1N2/PPP2PPP/RNBQK2R b KQkq - 0 1',

‎src/contexts/ModalContext.tsx‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
import { PlaySetupModal } from 'src/components'
2-
import React, { ComponentProps, ReactNode, useState } from 'react'
1+
import dynamic from 'next/dynamic'
2+
import React, { ReactNode, useState } from 'react'
3+
import type { PlaySetupModalProps } from 'src/components/Common/PlaySetupModal'
4+
5+
const PlaySetupModal = dynamic(
6+
() =>
7+
import('src/components/Common/PlaySetupModal').then(
8+
(mod) => mod.PlaySetupModal,
9+
),
10+
{ ssr: false },
11+
)
312

413
const fn = () => {
514
throw new Error('poorly provided ModalContext')
615
}
716

817
interface IModalContext {
9-
playSetupModalProps?: ComponentProps<typeof PlaySetupModal>
10-
setPlaySetupModalProps: (arg0?: ComponentProps<typeof PlaySetupModal>) => void
18+
playSetupModalProps?: PlaySetupModalProps
19+
setPlaySetupModalProps: (arg0?: PlaySetupModalProps) => void
1120
}
1221

1322
export const ModalContext = React.createContext<IModalContext>({
@@ -20,7 +29,7 @@ export const ModalContextProvider: React.FC<{ children: ReactNode }> = ({
2029
children: ReactNode
2130
}) => {
2231
const [playSetupModalProps, setPlaySetupModalProps] = useState<
23-
ComponentProps<typeof PlaySetupModal> | undefined
32+
PlaySetupModalProps | undefined
2433
>(undefined)
2534

2635
return (

0 commit comments

Comments
 (0)