Skip to content

Commit 057366a

Browse files
committed
Cache ContractCard, speed up /explore pageload
1 parent f92201d commit 057366a

File tree

3 files changed

+112
-72
lines changed

3 files changed

+112
-72
lines changed

apps/dashboard/src/@/components/contracts/contract-card/contract-card.tsx

Lines changed: 110 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
import { moduleToBase64 } from "app/(app)/(dashboard)/published-contract/utils/module-base-64";
22
import { RocketIcon, ShieldCheckIcon } from "lucide-react";
3+
import { unstable_cache } from "next/cache";
34
import Link from "next/link";
5+
import { resolveAvatar } from "thirdweb/extensions/ens";
6+
import { Blobbie } from "thirdweb/react";
47
import { fetchPublishedContractVersion } from "@/api/contract/fetch-contracts-with-versions";
5-
import { ClientOnly } from "@/components/blocks/client-only";
8+
import { Img } from "@/components/blocks/Img";
69
import { Badge } from "@/components/ui/badge";
710
import { Button } from "@/components/ui/button";
811
import { Skeleton } from "@/components/ui/skeleton";
912
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
1013
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
14+
import { resolveEns } from "@/lib/ens";
1115
import { replaceDeployerAddress } from "@/lib/publisher-utils";
1216
import { cn } from "@/lib/utils";
1317
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
14-
import { ContractPublisher } from "./contract-publisher";
18+
import { shortenIfAddress } from "@/utils/usedapp-external";
1519

16-
interface ContractCardProps {
20+
type ContractCardProps = {
1721
publisher: string;
1822
contractId: string;
1923
titleOverride?: string;
@@ -29,7 +33,7 @@ interface ContractCardProps {
2933
moduleId: string;
3034
version?: string;
3135
}[];
32-
}
36+
};
3337

3438
function getContractUrl(
3539
{
@@ -74,6 +78,72 @@ function getContractUrl(
7478
return replaceDeployerAddress(pathName);
7579
}
7680

81+
const cached_fetchPublishedContractVersion = unstable_cache(
82+
async (publisher: string, contractId: string, version: string = "latest") => {
83+
const result = await fetchPublishedContractVersion(
84+
publisher,
85+
contractId,
86+
serverThirdwebClient,
87+
version,
88+
).catch(() => null);
89+
90+
if (!result) {
91+
return null;
92+
}
93+
94+
const publisherEnsAndAvatar = result.publisher
95+
? await cached_resolvePublisherEnsAndAvatar(result.publisher)
96+
: undefined;
97+
98+
// Note: Do not return BigInt - it can't be serialized and cached by unstable_cache and will throw an error
99+
return {
100+
name: result.name,
101+
displayName: result.displayName,
102+
description: result.description,
103+
publisher: {
104+
address: result.publisher,
105+
ensName: publisherEnsAndAvatar?.ensName,
106+
ensAvatar: publisherEnsAndAvatar?.ensAvatar,
107+
},
108+
version: result.version,
109+
audit: result.audit,
110+
};
111+
},
112+
["fetchPublishedContractVersion"],
113+
{
114+
revalidate: 3600, // 1 hour
115+
},
116+
);
117+
118+
const cached_resolvePublisherEnsAndAvatar = unstable_cache(
119+
async (_addressOrEns: string) => {
120+
const addressOrEns = replaceDeployerAddress(_addressOrEns);
121+
const [ensNameInfo, ensAvatar] = await Promise.allSettled([
122+
resolveEns(addressOrEns, serverThirdwebClient),
123+
resolveAvatar({
124+
client: serverThirdwebClient,
125+
name: addressOrEns,
126+
}),
127+
]);
128+
129+
return {
130+
ensName:
131+
ensNameInfo.status === "fulfilled"
132+
? ensNameInfo.value?.ensName
133+
: undefined,
134+
address:
135+
ensNameInfo.status === "fulfilled"
136+
? ensNameInfo.value?.address
137+
: undefined,
138+
ensAvatar: ensAvatar.status === "fulfilled" ? ensAvatar.value : undefined,
139+
};
140+
},
141+
["resolvePublisherEnsAndAvatar"],
142+
{
143+
revalidate: 3600, // 1 hour
144+
},
145+
);
146+
77147
export async function ContractCard({
78148
publisher,
79149
contractId,
@@ -83,12 +153,11 @@ export async function ContractCard({
83153
modules = [],
84154
isBeta,
85155
}: ContractCardProps) {
86-
const publishedContractResult = await fetchPublishedContractVersion(
156+
const publishedContractResult = await cached_fetchPublishedContractVersion(
87157
publisher,
88158
contractId,
89-
serverThirdwebClient,
90159
version,
91-
).catch(() => null);
160+
);
92161

93162
if (!publishedContractResult) {
94163
return null;
@@ -186,13 +255,12 @@ export async function ContractCard({
186255
!modules?.length && "mt-auto",
187256
)}
188257
>
189-
{publishedContractResult.publisher && (
190-
<ClientOnly ssr={<Skeleton className="size-5 rounded-full" />}>
191-
<ContractPublisher
192-
addressOrEns={publishedContractResult.publisher}
193-
client={getClientThirdwebClient()}
194-
/>
195-
</ClientOnly>
258+
{publishedContractResult.publisher.address && (
259+
<ContractPublisher
260+
address={publishedContractResult.publisher.address}
261+
ensName={publishedContractResult.publisher.ensName || undefined}
262+
ensAvatar={publishedContractResult.publisher.ensAvatar || undefined}
263+
/>
196264
)}
197265

198266
<div className="flex items-center justify-between">
@@ -226,3 +294,31 @@ export async function ContractCard({
226294
export function ContractCardSkeleton() {
227295
return <Skeleton className="h-[218px] border" />;
228296
}
297+
298+
function ContractPublisher(props: {
299+
ensName: string | undefined;
300+
address: string;
301+
ensAvatar: string | undefined;
302+
}) {
303+
const displayName = props.ensName || props.address;
304+
return (
305+
<Link
306+
className="flex shrink-0 items-center gap-1.5 hover:underline"
307+
href={`/${displayName}`}
308+
target="_blank"
309+
>
310+
<Img
311+
className="size-5 rounded-full object-cover"
312+
src={
313+
resolveSchemeWithErrorHandler({
314+
client: getClientThirdwebClient(),
315+
uri: props.ensAvatar,
316+
}) || ""
317+
}
318+
fallback={<Blobbie address={props.address} />}
319+
/>
320+
321+
<span className="text-xs"> {shortenIfAddress(displayName)}</span>
322+
</Link>
323+
);
324+
}

apps/dashboard/src/@/components/contracts/contract-card/contract-publisher.tsx

Lines changed: 0 additions & 53 deletions
This file was deleted.

apps/dashboard/src/app/(app)/(dashboard)/explore/components/contract-row/index.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import "server-only";
12
import { ArrowRightIcon } from "lucide-react";
23
import Link from "next/link";
34
import { Suspense } from "react";
@@ -7,11 +8,7 @@ import {
78
} from "@/components/contracts/contract-card/contract-card";
89
import type { ExploreCategory } from "../../data";
910

10-
interface ContractRowProps {
11-
category: ExploreCategory;
12-
}
13-
14-
export function ContractRow({ category }: ContractRowProps) {
11+
export function ContractRow({ category }: { category: ExploreCategory }) {
1512
return (
1613
<section>
1714
{/* Title, Description + View all link */}

0 commit comments

Comments
 (0)