mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 11:24:57 -05:00
docs: cut homepage weight on slow connections
Under Fast 3G the homepage still took ~19s to LCP. The main costs: - The hero 3D logos were SVGs wrapping large embedded PNGs (256-590KB each), and all four (both themes) were `priority`, so every visit preloaded 1.69MB, including on mobile where they're hidden. They're now WebP (21-59KB) served through next/image with `sizes`, lazy-loaded so only the visible theme's pair is fetched (~37KB). - The contributor wall rendered 473 GitHub avatars (~1.6MB) that the browser's lazy-loading threshold pulled in right after first paint. The wall now mounts its avatars only when it nears the viewport. - `geist/font/pixel` declared and preloaded all five Geist Pixel fonts; only Square is used, so load just that one. - Both demo video posters were fetched even though one is hidden; only the active theme's poster is set now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d1e74989f7
commit
f396ec6c39
+8
-8
@@ -21,42 +21,42 @@ export default async function HomePage() {
|
||||
{/* Dark mode logos */}
|
||||
<div className="group max-w-[300px] w-full max-h-[200px] -mt-[30%] hidden dark:flex justify-center opacity-100">
|
||||
<Image
|
||||
src="/left-3d-logo.svg"
|
||||
src="/left-3d-logo.webp"
|
||||
alt=""
|
||||
width={518}
|
||||
height={667}
|
||||
className="h-auto max-h-[200px] z-10 animate-logo-snap-left transition-transform duration-300 ease-out group-hover:-translate-x-3 group-hover:-rotate-5"
|
||||
priority
|
||||
sizes="160px"
|
||||
draggable={false}
|
||||
/>
|
||||
<Image
|
||||
src="/right-3d-logo.svg"
|
||||
src="/right-3d-logo.webp"
|
||||
alt=""
|
||||
width={518}
|
||||
height={667}
|
||||
className="h-auto -ml-28 -mt-3 max-h-[200px] animate-logo-snap-right transition-transform duration-300 ease-out group-hover:translate-x-3 group-hover:rotate-5"
|
||||
priority
|
||||
sizes="160px"
|
||||
draggable={false}
|
||||
/>
|
||||
</div>
|
||||
{/* Light mode logos */}
|
||||
<div className="group max-w-[300px] w-full max-h-[200px] -mt-[30%] flex dark:hidden justify-center opacity-100">
|
||||
<Image
|
||||
src="/left-3d-logo-light.svg"
|
||||
src="/left-3d-logo-light.webp"
|
||||
alt=""
|
||||
width={518}
|
||||
height={667}
|
||||
className="h-auto max-h-[200px] z-10 animate-logo-snap-left transition-transform duration-300 ease-out group-hover:-translate-x-3 group-hover:-rotate-5"
|
||||
priority
|
||||
sizes="160px"
|
||||
draggable={false}
|
||||
/>
|
||||
<Image
|
||||
src="/right-3d-logo-light.svg"
|
||||
src="/right-3d-logo-light.webp"
|
||||
alt=""
|
||||
width={518}
|
||||
height={667}
|
||||
className="h-auto -ml-28 -mt-3 max-h-[200px] animate-logo-snap-right transition-transform duration-300 ease-out group-hover:translate-x-3 group-hover:rotate-5"
|
||||
priority
|
||||
sizes="160px"
|
||||
draggable={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
@@ -704,6 +704,28 @@ function ContributorsSection({
|
||||
contributors: ContributorInfo[];
|
||||
contributorCount: number;
|
||||
}) {
|
||||
const wallRef = useRef<HTMLDivElement>(null);
|
||||
const [wallNearViewport, setWallNearViewport] = useState(false);
|
||||
|
||||
// Hundreds of avatars scroll through the wall; only fetch them once it's
|
||||
// close to the viewport instead of relying on the browser's generous
|
||||
// native lazy-loading distance.
|
||||
useEffect(() => {
|
||||
const wall = wallRef.current;
|
||||
if (!wall) return;
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setWallNearViewport(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{ rootMargin: "200px" },
|
||||
);
|
||||
observer.observe(wall);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
if (contributors.length === 0) return null;
|
||||
|
||||
const colCount = 18;
|
||||
@@ -735,6 +757,7 @@ function ContributorsSection({
|
||||
|
||||
{contributors.length > 0 && (
|
||||
<div
|
||||
ref={wallRef}
|
||||
className="relative overflow-hidden h-[220px] rounded-md"
|
||||
style={{
|
||||
perspective: "600px",
|
||||
@@ -761,39 +784,40 @@ function ContributorsSection({
|
||||
transformOrigin: "center center",
|
||||
}}
|
||||
>
|
||||
{columns.map((col, i) => (
|
||||
<div key={i} className="relative overflow-hidden h-full">
|
||||
<div
|
||||
className="flex flex-col gap-1 items-center"
|
||||
style={{
|
||||
animation: `vertical-marquee ${speeds[i]}s linear infinite`,
|
||||
}}
|
||||
>
|
||||
{[...col, ...col].map((c, j) => (
|
||||
<a
|
||||
key={`${c.login}-${j}`}
|
||||
href={c.html_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={c.login}
|
||||
className="relative group shrink-0"
|
||||
>
|
||||
<img
|
||||
src={`${c.avatar_url}&s=64`}
|
||||
alt={c.login}
|
||||
width={32}
|
||||
height={32}
|
||||
loading="lazy"
|
||||
className="rounded-sm grayscale opacity-50 hover:grayscale-0 hover:opacity-100 transition-all duration-200 hover:scale-125 hover:z-10 relative"
|
||||
/>
|
||||
<div className="absolute -top-7 left-1/2 -translate-x-1/2 px-1.5 py-0.5 bg-foreground text-background text-[8px] font-mono rounded-sm opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-20">
|
||||
{c.login}
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
{wallNearViewport &&
|
||||
columns.map((col, i) => (
|
||||
<div key={i} className="relative overflow-hidden h-full">
|
||||
<div
|
||||
className="flex flex-col gap-1 items-center"
|
||||
style={{
|
||||
animation: `vertical-marquee ${speeds[i]}s linear infinite`,
|
||||
}}
|
||||
>
|
||||
{[...col, ...col].map((c, j) => (
|
||||
<a
|
||||
key={`${c.login}-${j}`}
|
||||
href={c.html_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={c.login}
|
||||
className="relative group shrink-0"
|
||||
>
|
||||
<img
|
||||
src={`${c.avatar_url}&s=64`}
|
||||
alt={c.login}
|
||||
width={32}
|
||||
height={32}
|
||||
loading="lazy"
|
||||
className="rounded-sm grayscale opacity-50 hover:grayscale-0 hover:opacity-100 transition-all duration-200 hover:scale-125 hover:z-10 relative"
|
||||
/>
|
||||
<div className="absolute -top-7 left-1/2 -translate-x-1/2 px-1.5 py-0.5 bg-foreground text-background text-[8px] font-mono rounded-sm opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-20">
|
||||
{c.login}
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
<div className="absolute inset-y-0 left-0 w-8 bg-linear-to-r from-background to-transparent pointer-events-none z-10" />
|
||||
<div className="absolute inset-y-0 right-0 w-8 bg-linear-to-l from-background to-transparent pointer-events-none z-10" />
|
||||
@@ -1019,7 +1043,9 @@ function DemoVideo() {
|
||||
<div ref={containerRef}>
|
||||
<video
|
||||
src="/demo-dark.mp4"
|
||||
poster="/demo-dark-poster.webp"
|
||||
// Hidden <video>s still fetch their poster, so only set the
|
||||
// poster for the active theme (known after hydration).
|
||||
poster={resolvedTheme === "dark" ? "/demo-dark-poster.webp" : undefined}
|
||||
preload="none"
|
||||
loop
|
||||
muted
|
||||
@@ -1028,7 +1054,9 @@ function DemoVideo() {
|
||||
/>
|
||||
<video
|
||||
src="/demo-light.mp4"
|
||||
poster="/demo-light-poster.webp"
|
||||
poster={
|
||||
resolvedTheme === "light" ? "/demo-light-poster.webp" : undefined
|
||||
}
|
||||
preload="none"
|
||||
loop
|
||||
muted
|
||||
|
||||
+11
-1
@@ -1,5 +1,5 @@
|
||||
import { GeistPixelSquare } from "geist/font/pixel";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const fontSans = Geist({
|
||||
@@ -12,6 +12,16 @@ const fontMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
});
|
||||
|
||||
// Only the Square variant is used. Importing from `geist/font/pixel` would
|
||||
// declare (and preload) all five Geist Pixel fonts on every page.
|
||||
const GeistPixelSquare = localFont({
|
||||
src: "../assets/GeistPixel-Square.woff2",
|
||||
variable: "--font-geist-pixel-square",
|
||||
weight: "500",
|
||||
fallback: ["Geist Mono", "ui-monospace", "monospace"],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
export const fontVariables = cn(
|
||||
fontSans.variable,
|
||||
fontMono.variable,
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Reference in New Issue
Block a user