Skip to content

Commit 2f3dc54

Browse files
committed
Cache ContractCard, speed up /explore pageload
1 parent f92201d commit 2f3dc54

File tree

3 files changed

+115
-72
lines changed

3 files changed

+115
-72
lines changed

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

Lines changed: 113 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1+
import "server-only";
12
import { moduleToBase64 } from "app/(app)/(dashboard)/published-contract/utils/module-base-64";
23
import { RocketIcon, ShieldCheckIcon } from "lucide-react";
4+
import { unstable_cache } from "next/cache";
35
import Link from "next/link";
6+
import { resolveAvatar } from "thirdweb/extensions/ens";
7+
import { Blobbie } from "thirdweb/react";
48
import { fetchPublishedContractVersion } from "@/api/contract/fetch-contracts-with-versions";
5-
import { ClientOnly } from "@/components/blocks/client-only";
9+
import { Img } from "@/components/blocks/Img";
610
import { Badge } from "@/components/ui/badge";
711
import { Button } from "@/components/ui/button";
812
import { Skeleton } from "@/components/ui/skeleton";
913
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
1014
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
15+
import { resolveEns } from "@/lib/ens";
1116
import { replaceDeployerAddress } from "@/lib/publisher-utils";
1217
import { cn } from "@/lib/utils";
1318
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
14-
import { ContractPublisher } from "./contract-publisher";
19+
import { shortenIfAddress } from "@/utils/usedapp-external";
1520

16-
interface ContractCardProps {
21+
type ContractCardProps = {
1722
publisher: string;
1823
contractId: string;
1924
titleOverride?: string;
@@ -29,7 +34,7 @@ interface ContractCardProps {
2934
moduleId: string;
3035
version?: string;
3136
}[];
32-
}
37+
};
3338

3439
function getContractUrl(
3540
{
@@ -74,6 +79,72 @@ function getContractUrl(
7479
return replaceDeployerAddress(pathName);
7580
}
7681

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

93163
if (!publishedContractResult) {
94164
return null;
@@ -186,13 +256,16 @@ export async function ContractCard({
186256
!modules?.length && "mt-auto",
187257
)}
188258
>
189-
{publishedContractResult.publisher && (
190-
<ClientOnly ssr={<Skeleton className="size-5 rounded-full" />}>
191-
<ContractPublisher
192-
addressOrEns={publishedContractResult.publisher}
193-
client={getClientThirdwebClient()}
194-
/>
195-
</ClientOnly>
259+
{publishedContractResult.publisher.address && (
260+
<ContractPublisher
261+
address={publishedContractResult.publisher.address}
262+
addressOrEns={
263+
publishedContractResult.publisher.ensName ||
264+
publishedContractResult.publisher.address ||
265+
""
266+
}
267+
ensAvatar={publishedContractResult.publisher.ensAvatar || undefined}
268+
/>
196269
)}
197270

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

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)