docs: cut homepage weight on slow connections (logos, avatars, fonts, poster) (#11430)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Bereket Engida
2026-09-27 07:16:27 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent b6ce639f4b
commit fc1f21aaa4
14 changed files with 77 additions and 187 deletions
+23 -21
View File
@@ -16,47 +16,49 @@ export default async function HomePage() {
{/* Left side — Hero title */}
<div className="relative w-full lg:w-[40%] lg:h-dvh border-b lg:border-b-0 lg:border-r border-foreground/[0.06] px-5 sm:px-6 lg:px-7 lg:sticky lg:top-0 z-10 bg-background lg:overflow-clip">
<LineFieldBackground />
{/* 3D Logo */}
{/* 3D Logo: WebPs are rendered from the original SVGs at 2x their
display size with the grain baked in, so they're `unoptimized` to
keep next/image from resampling the grain away. */}
<div className="hidden lg:flex justify-center h-full absolute items-center left-1/2 -translate-x-1/2 w-full pointer-events-auto select-none animate-logo-reveal z-[1]">
{/* 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
width={311}
height={400}
className="h-auto max-h-[200px] object-contain z-10 animate-logo-snap-left transition-transform duration-300 ease-out group-hover:-translate-x-3 group-hover:-rotate-5"
unoptimized
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
width={321}
height={400}
className="h-auto object-contain -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"
unoptimized
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
width={309}
height={400}
className="h-auto max-h-[200px] object-contain z-10 animate-logo-snap-left transition-transform duration-300 ease-out group-hover:-translate-x-3 group-hover:-rotate-5"
unoptimized
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
width={302}
height={400}
className="h-auto object-contain -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"
unoptimized
draggable={false}
/>
</div>
Binary file not shown.
+43 -12
View File
@@ -704,6 +704,29 @@ function ContributorsSection({
contributors: ContributorInfo[];
contributorCount: number;
}) {
const wallRef = useRef<HTMLDivElement>(null);
const [wallNearViewport, setWallNearViewport] = useState(false);
// Hundreds of avatars scroll through the wall; the links always render,
// but the avatar images are only mounted once the wall is 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 +758,7 @@ function ContributorsSection({
{contributors.length > 0 && (
<div
ref={wallRef}
className="relative overflow-hidden h-[220px] rounded-md"
style={{
perspective: "600px",
@@ -776,16 +800,21 @@ function ContributorsSection({
target="_blank"
rel="noopener noreferrer"
title={c.login}
aria-label={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"
/>
{wallNearViewport ? (
<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"
/>
) : (
<span className="block size-8 rounded-sm bg-foreground/5" />
)}
<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>
@@ -1017,23 +1046,25 @@ function DemoVideo() {
return (
<div ref={containerRef}>
{/* The poster is a CSS background rather than the `poster` attribute:
hidden <video>s still fetch their poster, but browsers skip
backgrounds on display:none elements, so only the active theme's
poster loads and it's there on first paint. */}
<video
src="/demo-dark.mp4"
poster="/demo-dark-poster.webp"
preload="none"
loop
muted
playsInline
className="w-full h-auto -mt-[2px] dark:block hidden"
className="w-full h-auto aspect-[2528/1440] -mt-[2px] bg-[url(/demo-dark-poster.webp)] bg-cover dark:block hidden"
/>
<video
src="/demo-light.mp4"
poster="/demo-light-poster.webp"
preload="none"
loop
muted
playsInline
className="w-full h-auto -mt-[2px] dark:hidden"
className="w-full h-auto aspect-[2544/1440] -mt-[2px] bg-[url(/demo-light-poster.webp)] bg-cover dark:hidden"
/>
</div>
);
+11 -1
View File
@@ -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,
-1
View File
@@ -73,7 +73,6 @@
"fumadocs-mdx": "^15.0.12",
"fumadocs-typescript": "^5.2.6",
"fumadocs-ui": "16.12.0",
"geist": "^1.7.2",
"gray-matter": "^4.0.3",
"hast-util-to-jsx-runtime": "^2.3.6",
"input-otp": "^1.4.2",
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 836 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 836 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

-12
View File
@@ -338,9 +338,6 @@ importers:
fumadocs-ui:
specifier: 16.12.0
version: 16.12.0(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.12.0(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.21.0(react@19.2.7))(next@16.3.4(@babel/core@8.0.1)(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(@types/node@25.9.4)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.5.4))(next@16.3.4(@babel/core@8.0.1)(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(@types/node@25.9.4)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(tailwindcss@4.3.1)
geist:
specifier: ^1.7.2
version: 1.7.2(next@16.3.4(@babel/core@8.0.1)(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(@types/node@25.9.4)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))
gray-matter:
specifier: ^4.0.3
version: 4.0.3
@@ -10448,11 +10445,6 @@ packages:
fzf@0.5.2:
resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==}
geist@1.7.2:
resolution: {integrity: sha512-Gu5lDFa3pLRyoBlBPf0QIFHVdWAnpco7fS1bJm41jyLPFoguBgiubseUN2oLXMgqZ7uxAxDoXcHMhCY/fOTTgg==}
peerDependencies:
next: '>=13.2.0'
generate-function@2.3.1:
resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
@@ -26710,10 +26702,6 @@ snapshots:
fzf@0.5.2: {}
geist@1.7.2(next@16.3.4(@babel/core@8.0.1)(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(@types/node@25.9.4)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)):
dependencies:
next: 16.3.4(@babel/core@8.0.1)(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(@types/node@25.9.4)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
generate-function@2.3.1:
dependencies:
is-property: 1.0.2