Domine técnicas avançadas de otimização e cache no tRPC: React Query, invalidação inteligente, cache distribuído e performance para SaaS de alto tráfego.
Performance = Conversão: Cada segundo adicional de carregamento reduz conversões em 20% e afeta diretamente a receita.
Escalabilidade: Cache inteligente permite suportar milhares de usuários simultâneos sem degradar performance.
// 📁 src/lib/trpc/client.ts
import { createTRPCReact } from '@trpc/react-query';
import { QueryClient } from '@tanstack/react-query';
import { persistQueryClient } from '@tanstack/react-query-persist-client-core';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
import { compress, decompress } from 'lz-string';
import type { AppRouter } from '@/server/trpc/router';
export const trpc = createTRPCReact<AppRouter>();
// 🔧 Configuração avançada do QueryClient
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
// 🚀 Configurações de performance
staleTime: 5 * 60 * 1000, // 5 minutos
cacheTime: 30 * 60 * 1000, // 30 minutos
retry: (failureCount, error) => {
// 🔄 Retry inteligente baseado no tipo de erro
if (error?.data?.httpStatus === 404) return false;
if (error?.data?.httpStatus === 403) return false;
if (error?.data?.httpStatus >= 500) return failureCount < 3;
return failureCount < 1;
},
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
// 📊 Configurações de refetch
refetchOnWindowFocus: false,
refetchOnMount: true,
refetchOnReconnect: true,
// 🎯 Configurações de background updates
refetchInterval: (data, query) => {
// 🔄 Refetch baseado no tipo de dados
if (query.queryKey[0] === 'dashboard') return 30 * 1000; // 30s
if (query.queryKey[0] === 'analytics') return 60 * 1000; // 1min
if (query.queryKey[0] === 'user') return 5 * 60 * 1000; // 5min
return false; // Não refetch automático
},
// 🎨 Configurações de placeholder
placeholderData: (previousData) => previousData,
// 📱 Configurações de suspense
suspense: false,
useErrorBoundary: (error) => {
// 🚨 Enviar apenas erros críticos para error boundary
return error?.data?.httpStatus >= 500;
},
},
mutations: {
// 🔄 Configurações de retry para mutations
retry: (failureCount, error) => {
// 💡 Só retry em erros de rede/servidor
if (error?.data?.httpStatus >= 500) return failureCount < 2;
return false;
},
// 🎯 Configurações de optimistic updates
onMutate: async (variables) => {
// 🔄 Cancelar queries relacionadas
await queryClient.cancelQueries();
// 📊 Snapshot do estado anterior
const previousData = queryClient.getQueryData(['related-data']);
return { previousData };
},
onError: (error, variables, context) => {
// 🔄 Rollback em caso de erro
if (context?.previousData) {
queryClient.setQueryData(['related-data'], context.previousData);
}
// 📊 Log de erro
console.error('Mutation error:', error);
},
onSettled: () => {
// 🔄 Invalidar queries relacionadas
queryClient.invalidateQueries(['related-data']);
},
},
},
});
// 💾 Configuração de persistência
const persister = createSyncStoragePersister({
storage: typeof window !== 'undefined' ? window.localStorage : undefined,
serialize: (data) => compress(JSON.stringify(data)),
deserialize: (data) => JSON.parse(decompress(data)),
key: 'trpc-cache-v1',
});
// 🔧 Inicializar persistência
if (typeof window !== 'undefined') {
persistQueryClient({
queryClient,
persister,
maxAge: 24 * 60 * 60 * 1000, // 24 horas
buster: 'v1.0.0', // Versão do cache
});
}
// 🎯 Hooks customizados para otimização
export function useOptimizedQuery<T>(
queryKey: string[],
queryFn: () => Promise<T>,
options: {
staleTime?: number;
cacheTime?: number;
enabled?: boolean;
suspense?: boolean;
keepPreviousData?: boolean;
refetchInterval?: number;
select?: (data: T) => any;
} = {}
) {
return trpc.useQuery(queryKey, queryFn, {
// 🚀 Configurações de performance específicas
staleTime: options.staleTime ?? 5 * 60 * 1000,
cacheTime: options.cacheTime ?? 30 * 60 * 1000,
keepPreviousData: options.keepPreviousData ?? true,
// 📊 Transformação de dados
select: options.select,
// 🔄 Configurações de refetch
refetchInterval: options.refetchInterval,
// 🎯 Configurações de estado
enabled: options.enabled,
suspense: options.suspense,
// 🔧 Configurações de retry específicas
retry: (failureCount, error) => {
// 💡 Queries críticas têm mais tentativas
if (queryKey.includes('critical')) return failureCount < 5;
return failureCount < 2;
},
// 📱 Configurações de loading
placeholderData: queryClient.getQueryData(queryKey),
});
}
// 🔄 Hook para invalidação inteligente
export function useSmartInvalidate() {
return {
// 🎯 Invalidar queries específicas
invalidateQueries: (patterns: string[]) => {
patterns.forEach(pattern => {
queryClient.invalidateQueries({
predicate: (query) => {
return query.queryKey.some(key =>
typeof key === 'string' && key.includes(pattern)
);
}
});
});
},
// 🔄 Invalidar por tags
invalidateByTags: (tags: string[]) => {
tags.forEach(tag => {
queryClient.invalidateQueries({
predicate: (query) => {
const queryTags = query.meta?.tags as string[] || [];
return queryTags.includes(tag);
}
});
});
},
// 📊 Invalidar dados relacionados
invalidateRelated: (entityId: string, entityType: string) => {
const patterns = [
`${entityType}.get`,
`${entityType}.list`,
`${entityType}.search`,
`related-${entityType}`,
];
patterns.forEach(pattern => {
queryClient.invalidateQueries({
predicate: (query) => {
const queryKey = query.queryKey;
return queryKey.some(key =>
typeof key === 'string' && key.includes(pattern)
) || (
typeof queryKey[1] === 'object' &&
queryKey[1]?.id === entityId
);
}
});
});
},
// 🎯 Refetch background queries
refetchBackground: () => {
queryClient.refetchQueries({
type: 'active',
stale: true,
});
},
};
}
// 📊 Hook para monitoramento de performance
export function useQueryPerformance() {
return {
// 🎯 Estatísticas de cache
getCacheStats: () => {
const cache = queryClient.getQueryCache();
const queries = cache.getAll();
return {
total: queries.length,
stale: queries.filter(q => q.isStale()).length,
fresh: queries.filter(q => !q.isStale()).length,
loading: queries.filter(q => q.isFetching()).length,
error: queries.filter(q => q.isError()).length,
};
},
// 📈 Métricas de performance
getPerformanceMetrics: () => {
const cache = queryClient.getQueryCache();
const queries = cache.getAll();
const metrics = queries.map(query => ({
queryKey: query.queryKey,
fetchTime: query.state.dataUpdateCount,
lastFetch: query.state.dataUpdatedAt,
errorCount: query.state.errorUpdateCount,
isStale: query.isStale(),
isFetching: query.isFetching(),
}));
return metrics;
},
// 🧹 Limpeza de cache
cleanupCache: () => {
// 🗑️ Remover queries antigas
queryClient.getQueryCache().clear();
// 🔄 Garbage collection
queryClient.getQueryCache().forEach((query) => {
if (query.getObserversCount() === 0) {
query.destroy();
}
});
},
};
}
// 📁 src/lib/cache/redis.ts
import { Redis } from 'ioredis';
import { TRPCError } from '@trpc/server';
import { createHash } from 'crypto';
import { compress, decompress } from 'lz-string';
// 🔧 Configuração do Redis
const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3,
lazyConnect: true,
keepAlive: 30000,
// 🚀 Configurações de performance
enableReadyCheck: true,
maxLoadingTimeout: 3000,
// 🔄 Configurações de retry
retryStrategy: (times) => {
if (times > 3) return null;
return Math.min(times * 50, 2000);
},
// 📊 Configurações de conexão
family: 4,
connectTimeout: 10000,
commandTimeout: 5000,
});
// 🎯 Interface para cache
interface CacheOptions {
ttl?: number; // Time to live em segundos
compress?: boolean; // Compressão
tags?: string[]; // Tags para invalidação
namespace?: string; // Namespace para organização
}
// 🔧 Classe de cache distribuído
export class DistributedCache {
private defaultTTL = 3600; // 1 hora
private keyPrefix = 'trpc:cache:';
// 🎯 Gerar chave única
private generateKey(key: string, namespace?: string): string {
const hash = createHash('sha256').update(key).digest('hex').slice(0, 16);
return `${this.keyPrefix}${namespace ? namespace + ':' : ''}${hash}`;
}
// 💾 Salvar no cache
async set<T>(
key: string,
value: T,
options: CacheOptions = {}
): Promise<void> {
try {
const cacheKey = this.generateKey(key, options.namespace);
const ttl = options.ttl || this.defaultTTL;
// 📊 Preparar dados para cache
const data = {
value,
timestamp: Date.now(),
tags: options.tags || [],
compressed: options.compress || false,
};
// 🗜️ Compressão opcional
let serializedData = JSON.stringify(data);
if (options.compress) {
serializedData = compress(serializedData);
}
// 💾 Salvar no Redis
await redis.setex(cacheKey, ttl, serializedData);
// 🏷️ Indexar por tags
if (options.tags && options.tags.length > 0) {
await this.indexByTags(cacheKey, options.tags, ttl);
}
console.log(`📦 Cache SET: ${key} (TTL: ${ttl}s)`);
} catch (error) {
console.error('❌ Cache SET error:', error);
// 🚨 Não bloquear aplicação por erro de cache
}
}
// 🔍 Buscar do cache
async get<T>(key: string, namespace?: string): Promise<T | null> {
try {
const cacheKey = this.generateKey(key, namespace);
const cachedData = await redis.get(cacheKey);
if (!cachedData) {
console.log(`🔍 Cache MISS: ${key}`);
return null;
}
// 📊 Deserializar dados
let data;
try {
// 🗜️ Tentar decomprimir primeiro
const decompressed = decompress(cachedData);
data = JSON.parse(decompressed || cachedData);
} catch {
// 📊 Fallback para dados não comprimidos
data = JSON.parse(cachedData);
}
console.log(`✅ Cache HIT: ${key}`);
return data.value;
} catch (error) {
console.error('❌ Cache GET error:', error);
return null;
}
}
// 🗑️ Remover do cache
async del(key: string, namespace?: string): Promise<void> {
try {
const cacheKey = this.generateKey(key, namespace);
await redis.del(cacheKey);
console.log(`🗑️ Cache DEL: ${key}`);
} catch (error) {
console.error('❌ Cache DEL error:', error);
}
}
// 🏷️ Invalidar por tags
async invalidateByTags(tags: string[]): Promise<void> {
try {
const pipeline = redis.pipeline();
for (const tag of tags) {
const tagKey = `${this.keyPrefix}tags:${tag}`;
const keys = await redis.smembers(tagKey);
// 🗑️ Remover todas as chaves da tag
if (keys.length > 0) {
pipeline.del(...keys);
}
// 🧹 Limpar a tag
pipeline.del(tagKey);
}
await pipeline.exec();
console.log(`🏷️ Cache invalidated by tags: ${tags.join(', ')}`);
} catch (error) {
console.error('❌ Cache invalidation error:', error);
}
}
// 🔄 Invalidar por padrão
async invalidateByPattern(pattern: string): Promise<void> {
try {
const keys = await redis.keys(`${this.keyPrefix}${pattern}`);
if (keys.length > 0) {
await redis.del(...keys);
console.log(`🔄 Cache invalidated by pattern: ${pattern} (${keys.length} keys)`);
}
} catch (error) {
console.error('❌ Cache pattern invalidation error:', error);
}
}
// 📊 Estatísticas do cache
async getStats(): Promise<{
totalKeys: number;
memoryUsage: string;
hitRate: number;
missRate: number;
}> {
try {
const info = await redis.info('memory');
const stats = await redis.info('stats');
const totalKeys = await redis.dbsize();
const memoryUsage = this.parseMemoryUsage(info);
const { hitRate, missRate } = this.parseHitRate(stats);
return {
totalKeys,
memoryUsage,
hitRate,
missRate,
};
} catch (error) {
console.error('❌ Cache stats error:', error);
return {
totalKeys: 0,
memoryUsage: 'N/A',
hitRate: 0,
missRate: 0,
};
}
}
// 🧹 Limpeza de cache
async cleanup(): Promise<void> {
try {
// 🗑️ Remover chaves expiradas
const keys = await redis.keys(`${this.keyPrefix}*`);
const pipeline = redis.pipeline();
for (const key of keys) {
const ttl = await redis.ttl(key);
if (ttl === -1) {
// 🕰️ Chave sem TTL, aplicar TTL padrão
pipeline.expire(key, this.defaultTTL);
}
}
await pipeline.exec();
console.log(`🧹 Cache cleanup completed (${keys.length} keys processed)`);
} catch (error) {
console.error('❌ Cache cleanup error:', error);
}
}
// 🏷️ Indexar por tags (método privado)
private async indexByTags(key: string, tags: string[], ttl: number): Promise<void> {
const pipeline = redis.pipeline();
for (const tag of tags) {
const tagKey = `${this.keyPrefix}tags:${tag}`;
pipeline.sadd(tagKey, key);
pipeline.expire(tagKey, ttl);
}
await pipeline.exec();
}
// 📊 Parse de uso de memória
private parseMemoryUsage(info: string): string {
const match = info.match(/used_memory_human:(.+)/);
return match ? match[1].trim() : 'N/A';
}
// 📈 Parse de hit rate
private parseHitRate(stats: string): { hitRate: number; missRate: number } {
const hitsMatch = stats.match(/keyspace_hits:(d+)/);
const missesMatch = stats.match(/keyspace_misses:(d+)/);
if (!hitsMatch || !missesMatch) {
return { hitRate: 0, missRate: 0 };
}
const hits = parseInt(hitsMatch[1]);
const misses = parseInt(missesMatch[1]);
const total = hits + misses;
if (total === 0) {
return { hitRate: 0, missRate: 0 };
}
return {
hitRate: (hits / total) * 100,
missRate: (misses / total) * 100,
};
}
}
// 🎯 Instância global do cache
export const cache = new DistributedCache();
// 🔧 Decorator para cache automático
export function cached<T extends any[], R>(
options: CacheOptions & { keyGenerator?: (...args: T) => string } = {}
) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: T): Promise<R> {
// 🔑 Gerar chave do cache
const cacheKey = options.keyGenerator
? options.keyGenerator(...args)
: `${propertyKey}:${JSON.stringify(args)}`;
// 🔍 Tentar buscar do cache
const cachedResult = await cache.get<R>(cacheKey, options.namespace);
if (cachedResult !== null) {
return cachedResult;
}
// 🔄 Executar método original
const result = await originalMethod.apply(this, args);
// 💾 Salvar no cache
await cache.set(cacheKey, result, options);
return result;
};
return descriptor;
};
}
// 🎯 Middleware para cache de tRPC
export function createCacheMiddleware(options: CacheOptions = {}) {
return async (opts: any) => {
const { next, type, path, input } = opts;
// 🔍 Apenas cachear queries
if (type !== 'query') {
return next();
}
// 🔑 Gerar chave do cache
const cacheKey = `${path}:${JSON.stringify(input)}`;
// 🔍 Tentar buscar do cache
const cachedResult = await cache.get(cacheKey, options.namespace);
if (cachedResult !== null) {
return cachedResult;
}
// 🔄 Executar query
const result = await next();
// 💾 Salvar no cache
await cache.set(cacheKey, result, options);
return result;
};
}
// 📁 src/server/trpc/middlewares/cache.ts
import { TRPCError } from '@trpc/server';
import { middleware } from '../trpc';
import { cache } from '@/lib/cache/redis';
// 🔧 Middleware para invalidação automática
export const cacheInvalidationMiddleware = middleware(async (opts) => {
const { next, type, path, input, ctx } = opts;
// 🔄 Executar operação
const result = await next();
// 🎯 Invalidar cache após mutations
if (type === 'mutation') {
await invalidateRelatedCache(path, input, ctx);
}
return result;
});
// 🎯 Função para invalidação inteligente
async function invalidateRelatedCache(path: string, input: any, ctx: any) {
const invalidationRules = {
// 👤 Invalidações relacionadas a usuário
'user.update': ['user.get', 'user.profile'],
'user.delete': ['user.*'],
// 📦 Invalidações relacionadas a produtos
'product.create': ['product.list', 'product.search', 'dashboard.stats'],
'product.update': ['product.get', 'product.list', 'product.search'],
'product.delete': ['product.*', 'dashboard.*'],
// 🛒 Invalidações relacionadas a pedidos
'order.create': ['order.list', 'dashboard.stats', 'analytics.sales'],
'order.update': ['order.get', 'order.list', 'dashboard.stats'],
'order.cancel': ['order.*', 'dashboard.*', 'analytics.*'],
// 💾 Invalidações relacionadas a uploads
'upload.uploadFile': ['upload.getFiles'],
'upload.deleteFile': ['upload.getFiles', 'dashboard.storage'],
// 📊 Invalidações relacionadas a analytics
'analytics.track': ['analytics.dashboard', 'analytics.reports'],
};
// 🔍 Buscar regras de invalidação
const patterns = invalidationRules[path as keyof typeof invalidationRules] || [];
if (patterns.length === 0) return;
// 🔄 Invalidar por padrões
for (const pattern of patterns) {
if (pattern.includes('*')) {
// 🌟 Invalidação por wildcard
await cache.invalidateByPattern(pattern.replace('*', ''));
} else {
// 🎯 Invalidação específica
await cache.del(pattern);
}
}
// 🏷️ Invalidar por tags específicas
await invalidateByEntity(path, input, ctx);
}
// 🏷️ Invalidação por entidade
async function invalidateByEntity(path: string, input: any, ctx: any) {
const entityInvalidations = {
// 👤 Invalidações por usuário
user: (input: any) => [
`user:${input.id || ctx.session?.user?.id}`,
`profile:${input.id || ctx.session?.user?.id}`,
],
// 📦 Invalidações por produto
product: (input: any) => [
`product:${input.id}`,
`category:${input.categoryId}`,
'product-list',
],
// 🛒 Invalidações por pedido
order: (input: any) => [
`order:${input.id || input.orderId}`,
`user-orders:${ctx.session?.user?.id}`,
'order-list',
],
// 🏢 Invalidações por organização
organization: (input: any) => [
`org:${input.id || input.organizationId}`,
`org-users:${input.id || input.organizationId}`,
],
};
// 🔍 Extrair entidade do path
const entity = path.split('.')[0];
const invalidationFn = entityInvalidations[entity as keyof typeof entityInvalidations];
if (invalidationFn) {
const tags = invalidationFn(input);
await cache.invalidateByTags(tags);
}
}
// 🔧 Decorator para cache inteligente
export function smartCache(options: {
ttl?: number;
tags?: string[];
dependsOn?: string[];
invalidateOn?: string[];
} = {}) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]): Promise<any> {
// 🔑 Gerar chave única
const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
// 🔍 Verificar dependências
if (options.dependsOn) {
const dependencyValid = await checkDependencies(options.dependsOn);
if (!dependencyValid) {
// 🗑️ Invalidar cache se dependências mudaram
await cache.del(cacheKey);
}
}
// 🔍 Buscar do cache
const cachedResult = await cache.get(cacheKey);
if (cachedResult !== null) {
return cachedResult;
}
// 🔄 Executar método
const result = await originalMethod.apply(this, args);
// 💾 Salvar com tags
await cache.set(cacheKey, result, {
ttl: options.ttl || 3600,
tags: options.tags || [],
});
return result;
};
return descriptor;
};
}
// 🔍 Verificar dependências
async function checkDependencies(dependencies: string[]): Promise<boolean> {
for (const dependency of dependencies) {
const lastModified = await cache.get(`lastModified:${dependency}`);
if (lastModified && Date.now() - lastModified < 5 * 60 * 1000) {
return false; // Dependência foi modificada nos últimos 5 minutos
}
}
return true;
}
// 🎯 Hook para invalidação manual
export function useInvalidateCache() {
const utils = trpc.useUtils();
return {
// 🔄 Invalidar queries específicas
invalidateQueries: async (patterns: string[]) => {
for (const pattern of patterns) {
await utils.invalidate(pattern as any);
}
},
// 🏷️ Invalidar por tags
invalidateByTags: async (tags: string[]) => {
await cache.invalidateByTags(tags);
// 🔄 Também invalidar no React Query
await utils.invalidate();
},
// 🎯 Invalidar dados relacionados
invalidateRelated: async (entity: string, entityId: string) => {
const tags = [
`${entity}:${entityId}`,
`${entity}-list`,
`related-${entity}`,
];
await cache.invalidateByTags(tags);
await utils.invalidate();
},
// 🧹 Limpeza completa
clearAll: async () => {
await cache.cleanup();
await utils.invalidate();
},
};
}
// 📊 Sistema de cache em camadas
export class LayeredCache {
private memoryCache = new Map<string, { data: any; expiry: number }>();
private readonly maxMemoryItems = 1000;
private readonly memoryTTL = 60 * 1000; // 1 minuto
async get<T>(key: string): Promise<T | null> {
// 🔍 Camada 1: Memória
const memoryResult = this.getFromMemory<T>(key);
if (memoryResult !== null) {
return memoryResult;
}
// 🔍 Camada 2: Redis
const redisResult = await cache.get<T>(key);
if (redisResult !== null) {
// 💾 Salvar na memória para próximas consultas
this.setInMemory(key, redisResult);
return redisResult;
}
return null;
}
async set<T>(key: string, value: T, options: { ttl?: number } = {}): Promise<void> {
// 💾 Salvar nas duas camadas
this.setInMemory(key, value);
await cache.set(key, value, options);
}
async invalidate(key: string): Promise<void> {
// 🗑️ Remover das duas camadas
this.memoryCache.delete(key);
await cache.del(key);
}
private getFromMemory<T>(key: string): T | null {
const item = this.memoryCache.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
this.memoryCache.delete(key);
return null;
}
return item.data;
}
private setInMemory<T>(key: string, value: T): void {
// 🧹 Limpar cache se muito cheio
if (this.memoryCache.size >= this.maxMemoryItems) {
const oldestKey = this.memoryCache.keys().next().value;
this.memoryCache.delete(oldestKey);
}
this.memoryCache.set(key, {
data: value,
expiry: Date.now() + this.memoryTTL,
});
}
}
// 🎯 Instância global do cache em camadas
export const layeredCache = new LayeredCache();
// 📁 src/lib/monitoring/performance.ts
import { TRPCError } from '@trpc/server';
import { middleware } from '../trpc';
// 📊 Interface para métricas
interface PerformanceMetrics {
operationName: string;
duration: number;
timestamp: number;
userId?: string;
cacheHit: boolean;
errorCount: number;
inputSize: number;
outputSize: number;
}
// 🎯 Classe para monitoramento
export class PerformanceMonitor {
private metrics: PerformanceMetrics[] = [];
private readonly maxMetrics = 10000;
// 📊 Registrar métrica
recordMetric(metric: PerformanceMetrics): void {
this.metrics.push(metric);
// 🧹 Limpar métricas antigas
if (this.metrics.length > this.maxMetrics) {
this.metrics.shift();
}
// 🚨 Alertar sobre performance crítica
if (metric.duration > 5000) { // 5 segundos
console.warn(`🚨 Slow operation detected: ${metric.operationName} took ${metric.duration}ms`);
}
}
// 📈 Obter estatísticas
getStats(timeRange: number = 60 * 60 * 1000): {
avgDuration: number;
maxDuration: number;
minDuration: number;
totalOperations: number;
cacheHitRate: number;
errorRate: number;
operationStats: Record<string, any>;
} {
const cutoff = Date.now() - timeRange;
const recentMetrics = this.metrics.filter(m => m.timestamp > cutoff);
if (recentMetrics.length === 0) {
return {
avgDuration: 0,
maxDuration: 0,
minDuration: 0,
totalOperations: 0,
cacheHitRate: 0,
errorRate: 0,
operationStats: {},
};
}
const durations = recentMetrics.map(m => m.duration);
const cacheHits = recentMetrics.filter(m => m.cacheHit).length;
const errors = recentMetrics.reduce((sum, m) => sum + m.errorCount, 0);
// 📊 Estatísticas por operação
const operationStats: Record<string, any> = {};
recentMetrics.forEach(metric => {
if (!operationStats[metric.operationName]) {
operationStats[metric.operationName] = {
count: 0,
totalDuration: 0,
errors: 0,
cacheHits: 0,
};
}
const stats = operationStats[metric.operationName];
stats.count++;
stats.totalDuration += metric.duration;
stats.errors += metric.errorCount;
if (metric.cacheHit) stats.cacheHits++;
});
// 📈 Calcular médias
Object.keys(operationStats).forEach(op => {
const stats = operationStats[op];
stats.avgDuration = stats.totalDuration / stats.count;
stats.cacheHitRate = (stats.cacheHits / stats.count) * 100;
stats.errorRate = (stats.errors / stats.count) * 100;
});
return {
avgDuration: durations.reduce((a, b) => a + b, 0) / durations.length,
maxDuration: Math.max(...durations),
minDuration: Math.min(...durations),
totalOperations: recentMetrics.length,
cacheHitRate: (cacheHits / recentMetrics.length) * 100,
errorRate: (errors / recentMetrics.length) * 100,
operationStats,
};
}
// 🔍 Operações mais lentas
getSlowestOperations(limit: number = 10): PerformanceMetrics[] {
return [...this.metrics]
.sort((a, b) => b.duration - a.duration)
.slice(0, limit);
}
// 📊 Relatório de performance
generateReport(): string {
const stats = this.getStats();
const slowest = this.getSlowestOperations(5);
return `
📊 Performance Report
=====================
📈 General Stats:
- Total Operations: ${stats.totalOperations}
- Average Duration: ${stats.avgDuration.toFixed(2)}ms
- Max Duration: ${stats.maxDuration}ms
- Cache Hit Rate: ${stats.cacheHitRate.toFixed(1)}%
- Error Rate: ${stats.errorRate.toFixed(1)}%
🐌 Slowest Operations:
${slowest.map(m => `- ${m.operationName}: ${m.duration}ms`).join('\n')}
📋 Operation Details:
${Object.entries(stats.operationStats).map(([op, stats]) =>
`- ${op}: ${stats.count} calls, ${stats.avgDuration.toFixed(2)}ms avg`
).join('\n')}
`.trim();
}
}
// 🎯 Instância global do monitor
export const performanceMonitor = new PerformanceMonitor();
// 🔧 Middleware de monitoramento
export const monitoringMiddleware = middleware(async (opts) => {
const startTime = Date.now();
const { type, path, input, ctx } = opts;
let cacheHit = false;
let errorCount = 0;
let result: any;
try {
// 🔍 Verificar se é hit de cache
const cacheKey = `${path}:${JSON.stringify(input)}`;
const cachedResult = await cache.get(cacheKey);
if (cachedResult) {
cacheHit = true;
result = cachedResult;
} else {
// 🔄 Executar operação
result = await next();
}
} catch (error) {
errorCount = 1;
throw error;
} finally {
// 📊 Registrar métrica
const duration = Date.now() - startTime;
const inputSize = JSON.stringify(input).length;
const outputSize = result ? JSON.stringify(result).length : 0;
performanceMonitor.recordMetric({
operationName: path,
duration,
timestamp: Date.now(),
userId: ctx.session?.user?.id,
cacheHit,
errorCount,
inputSize,
outputSize,
});
}
return result;
});
// 📊 Hook para métricas em tempo real
export function usePerformanceMetrics() {
return {
// 📈 Obter estatísticas
getStats: () => performanceMonitor.getStats(),
// 🔍 Operações mais lentas
getSlowestOperations: (limit?: number) =>
performanceMonitor.getSlowestOperations(limit),
// 📊 Relatório completo
generateReport: () => performanceMonitor.generateReport(),
// 🎯 Métricas específicas
getOperationMetrics: (operationName: string) => {
const stats = performanceMonitor.getStats();
return stats.operationStats[operationName] || null;
},
};
}
// 🚨 Sistema de alertas
export class AlertSystem {
private alerts: Array<{
type: 'warning' | 'error' | 'info';
message: string;
timestamp: number;
acknowledged: boolean;
}> = [];
// 🔔 Criar alerta
createAlert(
type: 'warning' | 'error' | 'info',
message: string
): void {
this.alerts.push({
type,
message,
timestamp: Date.now(),
acknowledged: false,
});
// 🚨 Log baseado no tipo
if (type === 'error') {
console.error(`🚨 ALERT: ${message}`);
} else if (type === 'warning') {
console.warn(`⚠️ ALERT: ${message}`);
} else {
console.info(`ℹ️ ALERT: ${message}`);
}
}
// 📊 Verificar condições de alerta
checkConditions(): void {
const stats = performanceMonitor.getStats();
// 🐌 Performance alerts
if (stats.avgDuration > 1000) {
this.createAlert('warning', `Average response time is ${stats.avgDuration}ms`);
}
if (stats.maxDuration > 5000) {
this.createAlert('error', `Maximum response time is ${stats.maxDuration}ms`);
}
// 💾 Cache alerts
if (stats.cacheHitRate < 50) {
this.createAlert('warning', `Cache hit rate is low: ${stats.cacheHitRate}%`);
}
// 🚨 Error alerts
if (stats.errorRate > 5) {
this.createAlert('error', `Error rate is high: ${stats.errorRate}%`);
}
}
// 📋 Obter alertas
getAlerts(unacknowledgedOnly: boolean = false): Array<any> {
return this.alerts.filter(alert =>
!unacknowledgedOnly || !alert.acknowledged
);
}
// ✅ Confirmar alerta
acknowledgeAlert(index: number): void {
if (this.alerts[index]) {
this.alerts[index].acknowledged = true;
}
}
}
// 🎯 Instância global do sistema de alertas
export const alertSystem = new AlertSystem();
// 🔄 Verificar alertas periodicamente
setInterval(() => {
alertSystem.checkConditions();
}, 60 * 1000); // A cada minuto
// 📁 src/components/optimized/VirtualizedList.tsx
import { useState, useCallback, useMemo } from 'react';
import { FixedSizeList as List } from 'react-window';
import { trpc } from '@/lib/trpc/client';
interface VirtualizedListProps<T> {
queryKey: string;
itemHeight: number;
renderItem: (item: T, index: number) => React.ReactNode;
searchTerm?: string;
filters?: Record<string, any>;
}
export function VirtualizedList<T>({
queryKey,
itemHeight,
renderItem,
searchTerm,
filters,
}: VirtualizedListProps<T>) {
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 10 });
// 📊 Query com paginação infinita
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
isError,
} = trpc.useInfiniteQuery(
[queryKey, { searchTerm, filters }],
({ pageParam = 0 }) => ({
limit: 50,
offset: pageParam,
searchTerm,
filters,
}),
{
getNextPageParam: (lastPage, pages) => {
if (lastPage.length < 50) return undefined;
return pages.length * 50;
},
// 🚀 Configurações de performance
keepPreviousData: true,
staleTime: 5 * 60 * 1000, // 5 minutos
cacheTime: 30 * 60 * 1000, // 30 minutos
}
);
// 📊 Memoizar items combinados
const allItems = useMemo(() => {
return data?.pages.flatMap(page => page) || [];
}, [data]);
// 🔄 Callback para mudança de range visível
const handleVisibleRangeChange = useCallback(({ visibleStartIndex, visibleStopIndex }) => {
setVisibleRange({ start: visibleStartIndex, end: visibleStopIndex });
// 📈 Carregar mais dados quando chegando perto do fim
if (
visibleStopIndex >= allItems.length - 10 &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage();
}
}, [allItems.length, hasNextPage, isFetchingNextPage, fetchNextPage]);
// 🎨 Renderizar item da lista
const ListItem = useCallback(({ index, style }) => {
const item = allItems[index];
if (!item) {
return (
<div style={style} className="flex items-center justify-center">
<div className="animate-pulse bg-gray-800 h-8 w-full rounded"></div>
</div>
);
}
return (
<div style={style}>
{renderItem(item, index)}
</div>
);
}, [allItems, renderItem]);
if (isLoading) {
return (
<div className="space-y-4">
{Array.from({ length: 10 }).map((_, i) => (
<div key={i} className="animate-pulse bg-gray-800 h-16 rounded"></div>
))}
</div>
);
}
if (isError) {
return (
<div className="text-red-400 text-center py-8">
Erro ao carregar dados
</div>
);
}
return (
<div className="h-96">
<List
height={384} // 96 * 4
itemCount={allItems.length}
itemSize={itemHeight}
onItemsRendered={handleVisibleRangeChange}
overscanCount={5}
className="scrollbar-thin scrollbar-thumb-gray-600 scrollbar-track-gray-800"
>
{ListItem}
</List>
{isFetchingNextPage && (
<div className="text-center py-4">
<div className="animate-spin w-6 h-6 border-2 border-lime-400 border-t-transparent rounded-full mx-auto"></div>
</div>
)}
</div>
);
}
// 📊 Hook para dados otimizados
export function useOptimizedData<T>(
queryKey: string,
options: {
enabled?: boolean;
searchTerm?: string;
filters?: Record<string, any>;
selectFields?: string[];
} = {}
) {
const { enabled = true, searchTerm, filters, selectFields } = options;
return trpc.useQuery(
[queryKey, { searchTerm, filters, selectFields }],
{
enabled,
// 🚀 Configurações otimizadas
keepPreviousData: true,
staleTime: 5 * 60 * 1000,
cacheTime: 30 * 60 * 1000,
// 📊 Transformar dados apenas se necessário
select: useCallback((data: any) => {
if (!selectFields) return data;
// 🎯 Selecionar apenas campos necessários
if (Array.isArray(data)) {
return data.map(item =>
selectFields.reduce((acc, field) => {
acc[field] = item[field];
return acc;
}, {} as any)
);
}
return selectFields.reduce((acc, field) => {
acc[field] = data[field];
return acc;
}, {} as any);
}, [selectFields]),
// 🔄 Refetch inteligente
refetchInterval: (data, query) => {
// 📊 Refetch baseado no tipo de dados
if (queryKey.includes('realtime')) return 5000; // 5s
if (queryKey.includes('dashboard')) return 30000; // 30s
return false;
},
// 🎯 Retry inteligente
retry: (failureCount, error) => {
if (error?.data?.httpStatus === 404) return false;
return failureCount < 3;
},
}
);
}
// 🔄 Component para preload inteligente
export function SmartPreloader({ routes }: { routes: string[] }) {
const utils = trpc.useUtils();
// 🎯 Preload em hover
const handleHover = useCallback(async (route: string) => {
await utils.prefetch(route as any);
}, [utils]);
// 📊 Preload crítico no mount
useEffect(() => {
const criticalRoutes = routes.filter(route =>
route.includes('dashboard') || route.includes('user')
);
criticalRoutes.forEach(route => {
utils.prefetch(route as any);
});
}, [routes, utils]);
return null; // Componente invisível
}
// 🎨 Hook para debounce de busca
export function useDebouncedSearch(
queryKey: string,
delay: number = 300
) {
const [searchTerm, setSearchTerm] = useState('');
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
// 🔄 Debounce do termo de busca
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearchTerm(searchTerm);
}, delay);
return () => clearTimeout(handler);
}, [searchTerm, delay]);
// 📊 Query com busca debounced
const query = trpc.useQuery(
[queryKey, { searchTerm: debouncedSearchTerm }],
{
enabled: Boolean(debouncedSearchTerm),
keepPreviousData: true,
staleTime: 2 * 60 * 1000, // 2 minutos
}
);
return {
searchTerm,
setSearchTerm,
debouncedSearchTerm,
...query,
};
}
// 📊 Component para analytics otimizado
export function OptimizedAnalytics({
trackingId,
events
}: {
trackingId: string;
events: string[];
}) {
const mutation = trpc.useMutation('analytics.track', {
// 🎯 Não mostrar loading para analytics
onError: (error) => {
console.warn('Analytics error:', error);
},
});
// 📊 Batch tracking
const trackBatch = useCallback((eventData: any[]) => {
mutation.mutate({
trackingId,
events: eventData,
});
}, [trackingId, mutation]);
// 🔄 Queue de eventos
const [eventQueue, setEventQueue] = useState<any[]>([]);
// 📈 Flush da queue periodicamente
useEffect(() => {
const interval = setInterval(() => {
if (eventQueue.length > 0) {
trackBatch(eventQueue);
setEventQueue([]);
}
}, 5000); // 5 segundos
return () => clearInterval(interval);
}, [eventQueue, trackBatch]);
// 🎯 Função para adicionar evento
const trackEvent = useCallback((event: string, data?: any) => {
setEventQueue(prev => [...prev, {
event,
data,
timestamp: Date.now(),
}]);
}, []);
return { trackEvent };
}
Cache em Camadas:Use memória + Redis para máxima performance com fallback seguro.
Invalidação Inteligente:Invalide apenas dados relacionados, não todo o cache.
Monitoramento Contínuo:Monitore métricas em tempo real para identificar gargalos.
Virtualização:Use listas virtualizadas para grandes volumes de dados.