Skip to content

Commit 1ce75f0

Browse files
committed
optimize avatars loading
1 parent 00b2082 commit 1ce75f0

File tree

15 files changed

+76
-93
lines changed

15 files changed

+76
-93
lines changed

app/components/insight-text.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ const InsightText: React.FC<InsightTextProps> = ({ insight }) => {
1616
if (segment.type === SegmentType.Mention) {
1717
const githubHandle = entities?.mentions[segment.entityKey!]?.handles?.github;
1818
return (
19-
<Link key={segment.text} href={`/profile/${githubHandle}`} className="text-foreground hover:text-foreground/80">
19+
<Link
20+
key={`${segment.type}-${segment.entityKey}`}
21+
href={`/profile/${githubHandle}`}
22+
className="text-foreground hover:text-foreground/80"
23+
>
2024
{segment.text}
2125
</Link>
2226
);
@@ -25,7 +29,11 @@ const InsightText: React.FC<InsightTextProps> = ({ insight }) => {
2529
if (segment.type === SegmentType.Link) {
2630
const link = entities?.links[segment.entityKey!]?.url;
2731
return (
28-
<Link key={segment.text} href={link} className="text-foreground hover:text-foreground/80">
32+
<Link
33+
key={`${segment.type}-${segment.entityKey}`}
34+
href={link}
35+
className="text-foreground hover:text-foreground/80"
36+
>
2937
{segment.text}
3038
</Link>
3139
);

app/components/messenger-integration-section.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { FaWhatsapp } from 'react-icons/fa';
2-
import { FaTelegramPlane } from 'react-icons/fa';
1+
import { FaWhatsapp, FaTelegramPlane } from 'react-icons/fa';
32

43
import { Button } from '@/components/ui/button';
54

app/languages/[country]/[orderBy]/[page]/components/language-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export const LanguageCard: FC<LanguageCardProps> = ({ data, country }) => {
6060
<UserCard
6161
login={topUser?.login}
6262
avatarUrl={topUser?.avatarUrl}
63-
avatarClassName="size-5"
63+
size={20}
6464
className="font-semibold text-sm"
6565
/>
6666
}

app/layout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export const metadata: Metadata = {
2323
export default function RootLayout({ children }: LayoutProps<'/'>) {
2424
return (
2525
<html lang="en" suppressHydrationWarning>
26+
<head>
27+
<link rel="preconnect" href="https://avatars.githubusercontent.com" crossOrigin="anonymous" />
28+
</head>
2629
<body
2730
className="antialiased"
2831
style={{ ...inter.style, fontFamily: `'Twemoji Country Flags', ${inter.style.fontFamily}` }}

app/profile/[login]/components/layout-left-column.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Image from 'next/image';
44
import Link from 'next/link';
55
import { FC, ReactNode } from 'react';
66

7-
import { Avatar, AvatarImage } from '@/components/ui/avatar';
87
import { Button } from '@/components/ui/button';
98
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
109
import { cn } from '@/lib/utils';
@@ -30,6 +29,8 @@ type LayoutLeftColumnProps = Readonly<{
3029
className?: string;
3130
}>;
3231

32+
const AVATAR_SIZE = 288;
33+
3334
export const LayoutLeftColumn: FC<LayoutLeftColumnProps> = ({ user, children, className }) => {
3435
const showContact = !!user?.email || !!user?.websiteUrl || !!user?.socialAccounts?.nodes?.length;
3536

@@ -42,9 +43,15 @@ export const LayoutLeftColumn: FC<LayoutLeftColumnProps> = ({ user, children, cl
4243
<LeftColumnContainer>
4344
<AvatarAndNameContainer>
4445
<AvatarContainer>
45-
<Avatar className="w-full h-full rounded-full" asChild>
46-
<AvatarImage src={user.avatarUrl!} />
47-
</Avatar>
46+
<Image
47+
src={`${user.avatarUrl}`}
48+
alt={`${user.login} avatar`}
49+
className="rounded-full w-full h-full"
50+
width={AVATAR_SIZE}
51+
height={AVATAR_SIZE}
52+
loading="eager"
53+
fetchPriority="high"
54+
/>
4855
</AvatarContainer>
4956
<NameContainer>
5057
<h1 className="font-semibold text-2xl" translate="no">

components/country-card/components/country-card-stat.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const CountryCardStat: FC<CountryCardStatProps> = ({ Icon, value, label,
3535
<div className="flex flex-col gap-1 text-xs items-center max-w-full overflow-hidden">
3636
<span>{topUserLabel}</span>
3737
<div className="font-semibold max-w-full">
38-
<UserCard login={topUser?.login} avatarUrl={topUser?.avatarUrl} avatarClassName="size-5" />
38+
<UserCard login={topUser?.login} avatarUrl={topUser?.avatarUrl} size={20} />
3939
</div>
4040
</div>
4141
)}

components/signin-button/signin-button.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
'use client';
2-
import { Avatar, AvatarFallback, AvatarImage } from '@radix-ui/react-avatar';
32
import { LogIn } from 'lucide-react';
3+
import Image from 'next/image';
44
import Link from 'next/link';
55
import { signIn, signOut, useSession } from 'next-auth/react';
66

7-
import { getInitials } from '@/utils/get-initials';
8-
97
import { Button } from '../ui/button';
108
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog';
119
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui/dropdown-menu';
1210

11+
const AVATAR_SIZE = 32;
12+
1313
export default function SigninButton() {
1414
const { data: session } = useSession();
1515

@@ -18,10 +18,15 @@ export default function SigninButton() {
1818
{session ? (
1919
<DropdownMenu>
2020
<DropdownMenuTrigger asChild>
21-
<Avatar className="cursor-pointer">
22-
<AvatarImage src={session.user.image!} className="rounded-full" width={32} height={32} />
23-
<AvatarFallback>{getInitials(session.user.name!)}</AvatarFallback>
24-
</Avatar>
21+
<Image
22+
src={`${session.user.image}&s=${AVATAR_SIZE * 2}`}
23+
alt={`${session.user.name} avatar`}
24+
className="rounded-full"
25+
width={AVATAR_SIZE}
26+
height={AVATAR_SIZE}
27+
loading="lazy"
28+
decoding="async"
29+
/>
2530
</DropdownMenuTrigger>
2631
<DropdownMenuContent>
2732
<DropdownMenuItem asChild>

components/theme-provider/theme-provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
import { ThemeProvider as NextThemesProvider } from 'next-themes';
44
import { ComponentProps } from 'react';
55

6-
export function ThemeProvider({ children, ...props }: ComponentProps<typeof NextThemesProvider>) {
6+
export function ThemeProvider({ children, ...props }: Readonly<ComponentProps<typeof NextThemesProvider>>) {
77
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
88
}

components/ui/avatar.tsx

Lines changed: 0 additions & 53 deletions
This file was deleted.
Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,35 @@
11
'use client';
2-
import { AvatarFallback } from '@radix-ui/react-avatar';
2+
import Image from 'next/image';
33
import { useLinkStatus } from 'next/link';
44
import { FC } from 'react';
55

6-
import { Avatar, AvatarImage } from '@/components/ui/avatar';
76
import { cn } from '@/lib/utils';
87

98
type ProfileAvatarProps = {
109
url?: string | null;
11-
initials?: string | null;
12-
className?: string;
10+
login?: string;
11+
size?: number;
1312
};
1413

15-
export const ProfileAvatar: FC<ProfileAvatarProps> = ({ url, initials, className }) => {
14+
export const ProfileAvatar: FC<ProfileAvatarProps> = ({ url, login, size = 32 }) => {
1615
const { pending } = useLinkStatus();
1716

1817
if (!url) {
1918
return null;
2019
}
2120

21+
// for retina displays
22+
const sourceSize = size * 2;
23+
2224
return (
23-
<Avatar className={className}>
24-
<AvatarImage src={url} className={cn('rounded-full', { 'animate-spin': pending })} />
25-
<AvatarFallback className="flex items-center justify-center">{initials}</AvatarFallback>
26-
</Avatar>
25+
<Image
26+
src={`${url}&s=${sourceSize}`}
27+
alt={`${login} avatar`}
28+
className={cn('rounded-full', { 'animate-spin': pending })}
29+
width={size}
30+
height={size}
31+
loading="lazy"
32+
decoding="async"
33+
/>
2734
);
2835
};

0 commit comments

Comments
 (0)