perf(logos): pré-resolver mapeamentos Logo.dev no servidor

Cada EstablishmentLogo dispara um GET para /api/logo/mapping por
nome único (deduplicado pelo React Query, mas ainda N requests por
página). Em /dashboard, /transactions e /payers/[payerId] agora
fazemos uma única query SQL em batch (fetchEstablishmentLogoMap) e
semeamos o cache do React Query antes do primeiro render via novo
LogoPrefetchProvider — eliminando os requests da rede.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-04-25 14:45:54 +00:00
parent 7f05d2a681
commit b453b432ed
8 changed files with 257 additions and 125 deletions

View File

@@ -5,3 +5,4 @@ export type {
export { CategoryIconBadge } from "./category-icon-badge";
export { EstablishmentLogo } from "./establishment-logo";
export { EstablishmentLogoPicker } from "./establishment-logo-picker";
export { LogoPrefetchProvider } from "./logo-prefetch-provider";

View File

@@ -0,0 +1,36 @@
"use client";
import { useQueryClient } from "@tanstack/react-query";
import { type ReactNode, useRef } from "react";
import { logoQueryKeys } from "@/shared/lib/logo";
import type { LogoPrefetchEntry } from "@/shared/lib/logo/types";
type LogoPrefetchProviderProps = {
mappings: LogoPrefetchEntry[];
children: ReactNode;
};
/**
* Semeia o cache do React Query com mapeamentos de logo já resolvidos
* no servidor. Evita que cada `EstablishmentLogo` dispare seu próprio
* GET para `/api/logo/mapping` no primeiro render.
*/
export function LogoPrefetchProvider({
mappings,
children,
}: LogoPrefetchProviderProps) {
const queryClient = useQueryClient();
const seeded = useRef(false);
if (!seeded.current) {
for (const { nameKey, domain, logoUrl } of mappings) {
queryClient.setQueryData(logoQueryKeys.mapping(nameKey), {
domain,
logoUrl,
});
}
seeded.current = true;
}
return <>{children}</>;
}

View File

@@ -0,0 +1,31 @@
import "server-only";
import { fetchEstablishmentLogoMap } from "./establishment-logo-queries";
import { toNameKey } from "./index";
import { buildLogoDevUrl } from "./server";
import type { LogoPrefetchEntry } from "./types";
export async function prefetchLogoMappings(
userId: string,
names: string[],
): Promise<LogoPrefetchEntry[]> {
const uniqueNames = [
...new Set(
names.filter((n) => typeof n === "string" && n.trim().length > 0),
),
];
if (uniqueNames.length === 0) return [];
const map = await fetchEstablishmentLogoMap(userId, uniqueNames);
const seen = new Set<string>();
const entries: LogoPrefetchEntry[] = [];
for (const name of uniqueNames) {
const nameKey = toNameKey(name);
if (seen.has(nameKey)) continue;
seen.add(nameKey);
const domain = map.get(nameKey) ?? null;
entries.push({ nameKey, domain, logoUrl: buildLogoDevUrl(domain) });
}
return entries;
}

View File

@@ -0,0 +1,5 @@
export type LogoPrefetchEntry = {
nameKey: string;
domain: string | null;
logoUrl: string | null;
};