Skip to content

Commit 53d0b17

Browse files
committed
fix: reduce homepage render work on load
1 parent 62bd25e commit 53d0b17

9 files changed

Lines changed: 793 additions & 379 deletions
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import * as React from 'react'
2+
3+
import { HomeApplicationStarterFallback } from './HomeSectionFallbacks'
4+
5+
const LazyApplicationStarter = React.lazy(() =>
6+
import('~/components/ApplicationStarter').then((m) => ({
7+
default: m.ApplicationStarter,
8+
})),
9+
)
10+
11+
export function HomeApplicationStarter() {
12+
const wrapperRef = React.useRef<HTMLDivElement | null>(null)
13+
const [shouldLoad, setShouldLoad] = React.useState(false)
14+
15+
React.useEffect(() => {
16+
if (shouldLoad) {
17+
return
18+
}
19+
20+
const element = wrapperRef.current
21+
22+
if (!element || typeof IntersectionObserver === 'undefined') {
23+
setShouldLoad(true)
24+
return
25+
}
26+
27+
const observer = new IntersectionObserver(
28+
(entries) => {
29+
if (!entries.some((entry) => entry.isIntersecting)) {
30+
return
31+
}
32+
33+
React.startTransition(() => {
34+
setShouldLoad(true)
35+
})
36+
observer.disconnect()
37+
},
38+
{ rootMargin: '180px 0px' },
39+
)
40+
41+
observer.observe(element)
42+
43+
return () => {
44+
observer.disconnect()
45+
}
46+
}, [shouldLoad])
47+
48+
React.useEffect(() => {
49+
if (shouldLoad || typeof window === 'undefined') {
50+
return
51+
}
52+
53+
if (typeof window.requestIdleCallback === 'function') {
54+
const idleId = window.requestIdleCallback(
55+
() => {
56+
React.startTransition(() => {
57+
setShouldLoad(true)
58+
})
59+
},
60+
{ timeout: 3500 },
61+
)
62+
63+
return () => {
64+
window.cancelIdleCallback(idleId)
65+
}
66+
}
67+
68+
const timeoutId = window.setTimeout(() => {
69+
React.startTransition(() => {
70+
setShouldLoad(true)
71+
})
72+
}, 2500)
73+
74+
return () => {
75+
window.clearTimeout(timeoutId)
76+
}
77+
}, [shouldLoad])
78+
79+
return (
80+
<div ref={wrapperRef}>
81+
{shouldLoad ? (
82+
<React.Suspense fallback={<HomeApplicationStarterFallback />}>
83+
<LazyApplicationStarter
84+
context="home"
85+
enableHotkeys
86+
primaryButtonColor="cyan"
87+
tone="cyan"
88+
/>
89+
</React.Suspense>
90+
) : (
91+
<HomeApplicationStarterFallback />
92+
)}
93+
</div>
94+
)
95+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { Card } from '~/components/Card'
2+
import { Footer } from '~/components/Footer'
3+
import { useToast } from '~/components/ToastProvider'
4+
import { useMutation } from '~/hooks/useMutation'
5+
import bytesImage from '~/images/bytes.svg'
6+
import { Button } from '~/ui'
7+
8+
async function bytesSignupServerFn({ email }: { email: string }) {
9+
'use server'
10+
11+
return fetch(`https://bytes.dev/api/bytes-optin-cors`, {
12+
method: 'POST',
13+
body: JSON.stringify({
14+
email,
15+
influencer: 'tanstack',
16+
}),
17+
headers: {
18+
Accept: 'application/json',
19+
'Content-Type': 'application/json',
20+
},
21+
})
22+
}
23+
24+
export function HomeBytesSection() {
25+
const bytesSignupMutation = useMutation({
26+
fn: bytesSignupServerFn,
27+
})
28+
const { notify } = useToast()
29+
30+
return (
31+
<>
32+
<div className="px-4 mx-auto max-w-(--breakpoint-lg) relative">
33+
<Card className="rounded-md p-8 md:p-14">
34+
{!bytesSignupMutation.submittedAt ? (
35+
<form
36+
onSubmit={async (e) => {
37+
e.preventDefault()
38+
const formData = new FormData(e.currentTarget)
39+
const email = formData.get('email_address')?.toString() || ''
40+
41+
const result = await bytesSignupMutation.mutate({ email })
42+
if (result?.ok) {
43+
notify(
44+
<div>
45+
<div className="font-medium">Thanks for subscribing</div>
46+
<div className="text-gray-500 dark:text-gray-400 text-xs">
47+
Check your email to confirm your subscription
48+
</div>
49+
</div>,
50+
)
51+
} else if (bytesSignupMutation.status === 'error') {
52+
notify(
53+
<div>
54+
<div className="font-medium">Subscription failed</div>
55+
<div className="text-gray-500 dark:text-gray-400 text-xs">
56+
Please try again in a moment
57+
</div>
58+
</div>,
59+
)
60+
}
61+
}}
62+
>
63+
<div>
64+
<div className="relative inline-block">
65+
<h3 id="bytes" className="text-3xl font-bold scroll-mt-24">
66+
<a
67+
href="#bytes"
68+
className="hover:underline decoration-gray-400 dark:decoration-gray-600"
69+
>
70+
Subscribe to Bytes
71+
</a>
72+
</h3>
73+
<figure className="absolute top-0 right-[-48px]">
74+
<img
75+
src={bytesImage}
76+
alt="Bytes Logo"
77+
loading="lazy"
78+
width={40}
79+
height={40}
80+
/>
81+
</figure>
82+
</div>
83+
84+
<h3 className="text-lg mt-1">The Best JavaScript Newsletter</h3>
85+
</div>
86+
<div className="grid grid-cols-3 mt-4 gap-2">
87+
<input
88+
disabled={bytesSignupMutation.status === 'pending'}
89+
className="col-span-2 p-3 placeholder-gray-400 text-black bg-gray-200 rounded text-sm outline-none focus:outline-none w-full dark:(text-white bg-gray-700)"
90+
name="email_address"
91+
placeholder="Your email address"
92+
type="text"
93+
required
94+
/>
95+
<Button
96+
type="submit"
97+
className="bg-[#ED203D] border-[#ED203D] hover:bg-[#d41c35] text-white justify-center"
98+
>
99+
{bytesSignupMutation.status === 'pending'
100+
? 'Loading ...'
101+
: 'Subscribe'}
102+
</Button>
103+
</div>
104+
{bytesSignupMutation.error ? (
105+
<p className="text-sm text-red-500 font-semibold italic mt-2">
106+
Looks like something went wrong. Please try again.
107+
</p>
108+
) : (
109+
<p className="text-sm opacity-30 font-semibold italic mt-2">
110+
Join over 100,000 devs
111+
</p>
112+
)}
113+
</form>
114+
) : (
115+
<p>🎉 Thank you! Please confirm your email</p>
116+
)}
117+
</Card>
118+
</div>
119+
<div className="h-20" />
120+
<Footer />
121+
</>
122+
)
123+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { Link } from '@tanstack/react-router'
2+
import { Card } from '~/components/Card'
3+
import { LazySponsorSection } from '~/components/LazySponsorSection'
4+
import { MaintainerCard } from '~/components/MaintainerCard'
5+
import { coreMaintainers } from '~/libraries/maintainers'
6+
import { Button } from '~/ui'
7+
8+
const courses = [
9+
{
10+
name: 'The Official TanStack React Query Course',
11+
href: 'https://query.gg/?s=tanstack',
12+
description:
13+
"Learn how to build enterprise quality apps with TanStack's React Query the easy way with our brand new course.",
14+
},
15+
]
16+
17+
export function HomeCommunitySection() {
18+
return (
19+
<div className="space-y-24">
20+
<div className="lg:max-w-(--breakpoint-lg) px-4 mx-auto">
21+
<h3 id="courses" className="text-3xl font-bold mb-6 scroll-mt-24">
22+
<a
23+
href="#courses"
24+
className="hover:underline decoration-gray-400 dark:decoration-gray-600"
25+
>
26+
Courses
27+
</a>
28+
</h3>
29+
<div className="mt-4 grid grid-cols-1 gap-4">
30+
{courses.map((course) => (
31+
<Card
32+
as="a"
33+
key={course.name}
34+
href={course.href}
35+
className="flex gap-2 justify-between p-4 md:p-8 transition-all hover:shadow-md hover:border-green-500"
36+
target="_blank"
37+
rel="noreferrer"
38+
>
39+
<div className="col-span-2 md:col-span-5">
40+
<div className="text-2xl font-bold text-green-600">
41+
{course.name}
42+
</div>
43+
<div className="text-sm mt-2">{course.description}</div>
44+
<div className="inline-block mt-4 px-4 py-2 bg-green-700 text-white rounded shadow font-black text-sm">
45+
Check it out →
46+
</div>
47+
</div>
48+
</Card>
49+
))}
50+
</div>
51+
</div>
52+
53+
<div className="lg:max-w-(--breakpoint-lg) px-4 mx-auto">
54+
<div id="sponsors" className="scroll-mt-24">
55+
<LazySponsorSection
56+
title={
57+
<a
58+
href="#sponsors"
59+
className="hover:underline decoration-gray-400 dark:decoration-gray-600"
60+
>
61+
OSS Sponsors
62+
</a>
63+
}
64+
/>
65+
</div>
66+
<div className="h-4" />
67+
<p className="italic mx-auto max-w-(--breakpoint-sm) text-gray-500 dark:text-gray-400 text-center">
68+
Sponsors get special perks like{' '}
69+
<strong>
70+
private discord channels, priority issue requests, direct support
71+
and even course vouchers
72+
</strong>
73+
!
74+
</p>
75+
</div>
76+
77+
<div className="px-4 lg:max-w-(--breakpoint-lg) md:mx-auto">
78+
<h3 id="maintainers" className="text-3xl font-bold mb-6 scroll-mt-24">
79+
<a
80+
href="#maintainers"
81+
className="hover:underline decoration-gray-400 dark:decoration-gray-600"
82+
>
83+
Core Maintainers
84+
</a>
85+
</h3>
86+
<div className="grid gap-6 grid-cols-2 lg:grid-cols-3">
87+
{coreMaintainers.map((maintainer) => (
88+
<MaintainerCard key={maintainer.github} maintainer={maintainer} />
89+
))}
90+
</div>
91+
<div className="flex justify-center mt-6">
92+
<Button as={Link} to="/maintainers">
93+
View All Maintainers
94+
</Button>
95+
</div>
96+
</div>
97+
</div>
98+
)
99+
}

0 commit comments

Comments
 (0)