Pular para o conteúdo
🎯 Módulo 2

tRPC Setup Completo

Configure tRPC do zero: type-safe APIs, middleware de autenticação e client setup

45 min
Nível:Intermediário

Por que isso é importante

tRPC revoluciona desenvolvimento full-stack oferecendo type-safety completo entre client e server. Elimina bugs de integração, acelera desenvolvimento em 40% e garante APIs sempre sincronizadas com TypeScript.

📚 Aula Educacional

Esta aula apresenta os códigos de configuração do tRPC como exemplos educacionais. Você aprenderá a estrutura, conceitos e implementação sem modificar o projeto atual. Use estes códigos como referência para seus próprios projetos SaaS.

🎯 O que Você Vai Aprender

Server Setup:

  • • Middleware de autenticação Clerk
  • • Context com user session
  • • Router structure profissional
  • • Type-safe procedures

Client Setup:

  • • React Query integration
  • • Provider setup
  • • Auto-typed hooks
  • • Error handling

📦 Passo 1: Instalação de Dependências

⚠️ Requisitos Importantes

Certifique-se que Clerk Authentication já está configurado no projeto. tRPC usará o contexto de autenticação do Clerk para proteger procedures.

terminal
# Comandos para instalar tRPC
# Instalar dependências principais do tRPC
npm install @trpc/server @trpc/client @trpc/tanstack-react-query client-only server-only @tanstack/react-query superjson 

# Verificar se Clerk já está instalado
npm list @clerk/nextjs

🔧 Passo 2: Configurando Middleware

🎯 Por que Middleware First?

Configuramos o middleware primeiro para habilitar autenticação em toda a aplicação. Isso permite que tRPC acesse rotas protegidas e contexto de usuário.

src/middleware.ts
// Configuração do middleware Clerk + tRPC 
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isPublicRoute = createRouteMatcher([
  '/sign-in(.*)',
  '/sign-up(.*)',
  '/',
  '/api/inngest(.*)',
  '/api/trpc(.*)', // 🎯 Importante: tRPC precisa ser público para auth flow
  '/api/webhooks/stripe(.*)',
  '/pricing(.*)',
]);

export default clerkMiddleware(async (auth, req) => {
  if (!isPublicRoute(req)) {
    await auth.protect();
  }
});

export const config = {
  matcher: [
    // ✅ Skip Next.js internals e arquivos estáticos
    '/((?!_next|[^?]*\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // ✅ Sempre executar para API routes
    '/(api|trpc)(.*)',
  ],
};

🏗️ Passo 3: tRPC Context e Initialization

✅ Arquitetura Profissional

Este setup cria context reutilizável, middleware de autenticação e procedures type-safe. Padrão usado em SaaS de milhões de usuários como Vercel e Railway.

src/trpc/init.ts
// Context e inicialização do tRPC  
import { initTRPC, TRPCError } from '@trpc/server';
import { cache } from 'react';
import superjson from 'superjson';
import { auth } from '@clerk/nextjs/server';

// 🎯 Context factory - chamado a cada request
export const createTRPCContext = cache(async () => {
  /**
   * @see: https://trpc.io/docs/server/context
   */
  return { auth: await auth() };
});

export type TRPCContext = Awaited<ReturnType<typeof createTRPCContext>>;

// 🏗️ Inicializar tRPC com context tipado
const t = initTRPC.context<TRPCContext>().create({
  /**
   * @see https://trpc.io/docs/server/data-transformers
   */
  transformer: superjson, // ✅ Serializa Date, Map, Set automaticamente
});

// 🔐 Middleware de autenticação reutilizável
const isAuthenticated = t.middleware(async ({ ctx, next }) => {
  if (!ctx.auth.userId) {
    throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authenticated' });
  }
  return next({
    ctx: {
      auth: ctx.auth, // ✅ Context tipado com user session
    },
  });
});

// 📡 Base router e procedure helpers
export const createTRPCRouter = t.router;
export const createCallerFactory = t.createCallerFactory;
export const baseProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthenticated);

⚡ Passo 4: Query Client Configuration

src/trpc/query-client.ts
// Configuração do React Query Client 
import { defaultShouldDehydrateQuery, QueryClient } from '@tanstack/react-query';
import superjson from 'superjson';

export function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 30 * 1000, // 🚀 30s cache para reduzir requests
      },
      dehydrate: {
        serializeData: superjson.serialize, // ✅ SSR + client sync
        shouldDehydrateQuery: (query) =>
          defaultShouldDehydrateQuery(query) || query.state.status === 'pending',
      },
      hydrate: {
        deserializeData: superjson.deserialize, // ✅ Hidratação type-safe
      },
    },
  });
}

🌐 Passo 5: App Router Structure

🔄 Estrutura Escalável

Router principal será expandido com modules específicos (messages, projects, users). Comentários mostram onde adicionar novos routers.

src/trpc/routers/_app.ts
// Router principal do tRPC 
import { createTRPCRouter } from '../init';
//import { messagesRouter } from '@/modules/messages/server/procedures';
//import { projectsRouter } from '@/modules/projects/server/procedures';
//import { usagesRouter } from '@/modules/usage/server/procedures';
import { subscriptionRouter } from '@/modules/subscription/server/procedures';

export const appRouter = createTRPCRouter({
  //messages: messagesRouter,
  //projects: projectsRouter,
  //usages: usagesRouter,
  subscription: subscriptionRouter,
});

// 🎯 Export type definition para client type-safety
export type AppRouter = typeof appRouter;

🖥️ Passo 6: Server-Side Setup

src/trpc/server.tsx
// Server-side setup do tRPC
import 'server-only'; // 🔒 Garante que não vaze para client
import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
import { cache } from 'react';
import { createTRPCContext } from './init';
import { makeQueryClient } from './query-client';
import { appRouter } from './routers/_app';

// 🎯 IMPORTANT: Stable getter para query client
// Retorna mesmo client durante o request
export const getQueryClient = cache(makeQueryClient);

export const trpc = createTRPCOptionsProxy({
  ctx: createTRPCContext,
  router: appRouter,
  queryClient: getQueryClient,
});

// 📞 Direct caller para server components
export const caller = appRouter.createCaller(createTRPCContext);

💻 Passo 7: Client-Side Setup

🎨 Client Provider Pattern

Provider separa server/client corretamente, evita hidration errors e garante performance otimizada com browser query client.

src/trpc/client.tsx
// Client-side setup do tRPC
'use client';
// ⬆️ Garante que Provider pode ser montado do server component
import superjson from 'superjson';
import type { QueryClient } from '@tanstack/react-query';
import { QueryClientProvider } from '@tanstack/react-query';
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import { createTRPCContext } from '@trpc/tanstack-react-query';
import { useState } from 'react';
import { makeQueryClient } from './query-client';
import type { AppRouter } from './routers/_app';

export const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>();

let browserQueryClient: QueryClient;

function getQueryClient() {
  if (typeof window === 'undefined') {
    // 🖥️ Server: sempre criar novo client
    return makeQueryClient();
  }
  
  // 🌐 Browser: reutilizar client para evitar re-criação
  // CRÍTICO para evitar suspense boundary issues
  if (!browserQueryClient) browserQueryClient = makeQueryClient();
  return browserQueryClient;
}

function getUrl() {
  const base = (() => {
    if (typeof window !== 'undefined') return '';
    return process.env.NEXT_PUBLIC_APP_URL;
  })();
  return `${base}/api/trpc`;
}

export function TRPCReactProvider(
  props: Readonly<{
    children: React.ReactNode;
  }>,
) {
  const queryClient = getQueryClient();
  
  const [trpcClient] = useState(() =>
    createTRPCClient<AppRouter>({
      links: [
        httpBatchLink({
          transformer: superjson,
          url: getUrl(),
        }),
      ],
    }),
  );
  
  return (
    <QueryClientProvider client={queryClient}>
      <TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
        {props.children}
      </TRPCProvider>
    </QueryClientProvider>
  );
}

🔗 Passo 8: Integração no Layout Principal

🎯 Finalizando a Integração

O último passo é envolver toda a aplicação com o TRPCReactProvider no layout principal. Isso disponibiliza os hooks tRPC em todos os componentes da aplicação.

src/app/layout.tsx
import type { Metadata } from 'next';
import {
  Geist,
  Geist_Mono,
} from 'next/font/google';
import './globals.css';
import { TRPCReactProvider } from '@/trpc/client';
import { Toaster } from '@/components/ui/sonner';
import { ThemeProvider } from 'next-themes';
import { ClerkProvider } from '@clerk/nextjs';

const geistSans = Geist({
  variable: '--font-geist-sans',
  subsets: ['latin'],
});

const geistMono = Geist_Mono({
  variable: '--font-geist-mono',
  subsets: ['latin'],
});

export const metadata: Metadata = {
  title: 'Create Next App',
  description:
    'Generated by create next app',
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <ClerkProvider
      appearance={{
        variables: {
          colorPrimary: '#a48fff',
        },
      }}
    >
      <TRPCReactProvider>
        <html
          lang="pt-BR"
          suppressHydrationWarning={
            true
          }
          data-lt-installed="true"
        >
          <body
            className={`${geistSans.variable} ${geistMono.variable} antialiased`}
          >
            <ThemeProvider
              attribute="class"
              defaultTheme="light"
              enableSystem
              disableTransitionOnChange
            >
              <Toaster />
              {children}
            </ThemeProvider>
          </body>
        </html>
      </TRPCReactProvider>
    </ClerkProvider>
  );
}

🎨 Passo 9: Ajustando a Homepage

🎯 Landing Page Otimizada

Com tRPC configurado, agora temos uma homepage limpa e funcional que demonstra a integração das tecnologias. Esta é a estrutura base para expandir com dados dinâmicos.

app/(home)/page.tsx
import { Button } from '@/components/ui/button';
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from '@/components/ui/card';
import Link from 'next/link';

export default function HomePage() {
  return (
    <div className="min-h-screen bg-gradient-to-br from-background-50 to-background-100">
      <section className="container mx-auto px-4 py-20 text-center">
        <h2 className="text-5xl font-bold mb-6 text-muted-foreground bg-clip-text">
          O SaaS mais simples do mundo
        </h2>
        <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto">
          Uma plataforma SaaS que te
          ajuda a criar e gerenciar seus
          projetos de forma simples e
          eficiente.
        </p>
        <Link href="/pricing">
          <Button>Comece agora</Button>
        </Link>
      </section>
      <section className="container mx-auto px-4 py-16">
        <div className="grid md:grid-cols-3 gap-8">
          <Card>
            <CardHeader>
              <CardTitle>
                Simples
              </CardTitle>
              <CardDescription>
                Nossa plataforma é fácil
                de usar
              </CardDescription>
            </CardHeader>
            <CardContent>
              <p className="text-muted-foreground text-xs">
                Sem complicações, sem
                configurações
                desnecessárias. Tudo que
                você precisa em um
              </p>
            </CardContent>
          </Card>
          <Card>
            <CardHeader>
              <CardTitle>
                Seguro
              </CardTitle>
              <CardDescription>
                Pagamentos via Stripe
              </CardDescription>
            </CardHeader>
            <CardContent>
              <p className="text-muted-foreground text-xs">
                Integração completa com
                Stripe para pagamentos
                seguros.
              </p>
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>
                Rápido
              </CardTitle>
              <CardDescription>
                Setup em minutos
              </CardDescription>
            </CardHeader>
            <CardContent>
              <p className="text-muted-foreground text-xs">
                Comece a usar em poucos
                cliques, sem
                configurações complexas.
              </p>
            </CardContent>
          </Card>
        </div>
      </section>
    </div>
  );
}

📊 Passo 10: Router de Assinaturas

🎯 Procedures para SaaS

Exemplo prático de como criar procedures tRPC para gerenciar assinaturas. Este router demonstra consultas protegidas, validação Zod e tratamento de erros profissional.

src/modules/subscription/server/procedures.ts
import { createTRPCRouter, protectedProcedure } from '@/trpc/init';
import { z } from 'zod';
import { TRPCError } from '@trpc/server';
import { getSubscriptionByClerkId, cancelSubscription } from '@/lib/services/subscription';

export const subscriptionRouter = createTRPCRouter({
  // Buscar subscription do usuário atual
  getCurrent: protectedProcedure.query(async ({ ctx }) => {
    try {
      const subscription = await getSubscriptionByClerkId(ctx.auth.userId!);
      return subscription;
    } catch (error) {
      console.error('Get current subscription failed:', error);
      throw new TRPCError({
        code: 'INTERNAL_SERVER_ERROR',
        message: 'Falha ao buscar assinatura atual',
      });
    }
  }),

  // Cancelar subscription
  cancel: protectedProcedure
    .input(
      z.object({
        stripeSubscriptionId: z.string().min(1, { message: 'Stripe Subscription ID is required' }),
      }),
    )
    .mutation(async ({ input }) => {
      try {
        const subscription = await cancelSubscription(input.stripeSubscriptionId);
        if (!subscription) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: 'Assinatura não encontrada',
          });
        }
        return subscription;
      } catch (error) {
        console.error('Cancel subscription failed:', error);
        if (error instanceof TRPCError) {
          throw error;
        }
        throw new TRPCError({
          code: 'INTERNAL_SERVER_ERROR',
          message: 'Falha ao cancelar assinatura',
        });
      }
    }),
});

✅ Padrões Aplicados

protectedProcedure: Garante autenticação

Validação Zod: Input type-safe

Error Handling: TRPCError padronizado

Context Access: ctx.auth.userId

Query vs Mutation: Separação correta

Service Layer: Lógica isolada

🎨 Passo 11: Dashboard com tRPC

🎯 Integração Frontend Completa

Dashboard refatorado usando tRPC hooks para consumir as procedures de assinatura. Demonstra Suspense, loading states, mutations e invalidação de cache.

app/dashboard/page.tsx
'use client';

import { Button } from '@/components/ui/button';
import { CheckCircle, AlertCircle } from 'lucide-react';
import { useSearchParams } from 'next/navigation';
import { Suspense, useState } from 'react';
import { useTRPC } from '@/trpc/client';
import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Interface não é mais necessária pois usamos o tipo do Prisma diretamente

function DashboardContent() {
  const searchParams = useSearchParams();
  const sessionId = searchParams.get('session_id');

  const trpc = useTRPC();
  const queryClient = useQueryClient();
  const { data: subscriptionData } = useSuspenseQuery(trpc.subscription.getCurrent.queryOptions());
  
  const cancelSubscriptionMutation = useMutation({
    ...trpc.subscription.cancel.mutationOptions(),
    onSuccess: () => {
      queryClient.invalidateQueries(trpc.subscription.getCurrent.queryOptions());
    },
  });

  const formatDate = (dateString: string) => {
    return new Date(dateString).toLocaleDateString('pt-BR');
  };

  return (
    <div className="min-h-screen bg-background-50">
      <div className="container mx-auto px-4 py-8">
        {/* Success Message */}
        {sessionId && (
          <div className="mb-6 p-4 bg-green-50 border border-green-200 rounded-lg flex items-center">
            <CheckCircle className="h-5 w-5 text-green-500 mr-3" />
            <div>
              <p className="text-green-800 font-medium">Assinatura ativada com sucesso!</p>
              <p className="text-green-600 text-sm">Bem-vindo ao MinimalSaaS</p>
            </div>
          </div>
        )}

        {/* Welcome Section */}
        <div className="mt-8">
          <h2 className="text-3xl font-bold mb-2">Bem-vindo de volta!</h2>
          <p className="text-background-600">Aqui está o resumo da sua conta</p>
        </div>

        {/* Subscription Details */}
        {subscriptionData && (
          <div className="bg-background rounded-lg shadow p-6 mb-8">
            <h3 className="text-xl font-semibold mb-4">Detalhes da Assinatura</h3>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
              <div>
                <p className="text-sm text-background-600">Status</p>
                <p className="font-medium capitalize">{subscriptionData.status}</p>
              </div>
              <div>
                <p className="text-sm text-background-600">Cancelamento automático</p>
                <p className="font-medium">{subscriptionData.cancelAtPeriodEnd ? 'Sim' : 'Não'}</p>
              </div>
              <div>
                <p className="text-sm text-background-600">Criada em</p>
                <p className="font-medium">{formatDate(subscriptionData.createdAt.toString())}</p>
              </div>
              <div>
                <p className="text-sm text-background-600">Última atualização</p>
                <p className="font-medium">{formatDate(subscriptionData.updatedAt.toString())}</p>
              </div>
              <CustomerPortalButton customerId={subscriptionData.stripeCustomerId} />
              <CancelSubscriptionButton 
                onCancel={() => cancelSubscriptionMutation.mutate({ 
                  stripeSubscriptionId: subscriptionData.stripeSubscriptionId 
                })}
                isLoading={cancelSubscriptionMutation.isPending}
                disabled={subscriptionData.cancelAtPeriodEnd}
              />
            </div>
          </div>
        )}

        {/* No Subscription Message */}
        {!subscriptionData && (
          <div className="bg-background rounded-lg shadow p-6 text-center">
            <AlertCircle className="h-12 w-12 text-background-400 mx-auto mb-4" />
            <h3 className="text-lg font-semibold mb-2">Nenhuma assinatura ativa</h3>
            <p className="text-background-600 mb-4">
              Você não possui uma assinatura ativa no momento.
            </p>
            <Button asChild>
              <Link href="/pricing">Ver Planos</Link>
            </Button>
          </div>
        )}
      </div>
    </div>
  );
}

export default function DashboardPage() {
  return (
    <Suspense
      fallback={
        <div className="min-h-screen bg-background-50 flex items-center justify-center">
          <div className="text-center">
            <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-background-900 mx-auto mb-4"></div>
            <p>Carregando...</p>
          </div>
        </div>
      }
    >
      <DashboardContent />
    </Suspense>
  );
}

function CancelSubscriptionButton({ 
  onCancel, 
  isLoading, 
  disabled 
}: { 
  onCancel: () => void; 
  isLoading: boolean; 
  disabled: boolean; 
}) {
  const [showConfirmation, setShowConfirmation] = useState(false);

  const handleCancel = () => {
    onCancel();
    setShowConfirmation(false);
  };

  if (disabled) {
    return (
      <div className="text-sm text-fg-dim">
        Assinatura já está programada para cancelamento
      </div>
    );
  }

  return (
    <div>
      {!showConfirmation ? (
        <Button
          variant="destructive"
          size="sm"
          onClick={() => setShowConfirmation(true)}
          disabled={isLoading}
        >
          Cancelar Assinatura
        </Button>
      ) : (
        <div className="space-y-2">
          <p className="text-sm text-fg-dim">
            Tem certeza que deseja cancelar sua assinatura?
          </p>
          <div className="flex gap-2">
            <Button
              variant="destructive"
              size="sm"
              onClick={handleCancel}
              disabled={isLoading}
            >
              {isLoading ? 'Cancelando...' : 'Sim, cancelar'}
            </Button>
            <Button
              variant="outline"
              size="sm"
              onClick={() => setShowConfirmation(false)}
              disabled={isLoading}
            >
              Não, manter
            </Button>
          </div>
        </div>
      )}
    </div>
  );
}

function CustomerPortalButton({ customerId }: { customerId: string }) {
  const [loading, setLoading] = useState(false);

  const handlePortalAccess = async () => {
    setLoading(true);

    try {
      const response = await fetch('/api/stripe/create-portal-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ customerId }),
      });

      const { url } = await response.json();
      window.location.href = url;
    } catch (error) {
      console.error('Erro:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <Button
      onClick={handlePortalAccess}
      disabled={loading}
      variant="outline"
      className="border-background-600"
    >
      {loading ? 'Carregando...' : '⚙️ Gerenciar Assinatura'}
    </Button>
  );
}

⚡ useSuspenseQuery: O Melhor dos 2 Mundos

🎯 Por que useSuspenseQuery é Superior?

useSuspenseQuery combina as vantagens de Server Components (data fetching automático) com Client Components (interatividade). Elimina loading states manuais e garante que dados estejam sempre disponíveis quando o componente renderiza.

🖥️ Server Components (tRPC Caller)
  • ✅ SEO-friendly (renderização no servidor)
  • ✅ Performance inicial mais rápida
  • ❌ Sem interatividade (onClick, useState)
  • ❌ Sem cache do React Query
  • ❌ Sem invalidação automática
💻 Client Components (useQuery tradicional)
  • ✅ Totalmente interativo
  • ✅ Cache e invalidação do React Query
  • ❌ Loading states manuais complexos
  • ❌ Conditional rendering (`if (isLoading)`)
  • ❌ TypeScript mais verboso
🚀 useSuspenseQuery: Melhor dos 2 Mundos
  • Data sempre disponível: Sem `data | undefined`
  • Loading automático: Suspense boundary cuida disso
  • Cache do React Query: Performance otimizada
  • Interatividade completa: Mutations, invalidação
  • TypeScript limpo: Tipos garantidos sem checks
  • Error boundaries: Tratamento de erro centralizado
🔄 Exemplo Prático na Prática

❌ useQuery tradicional:

const { data, isLoading, error } = trpc.subscription.getCurrent.useQuery(); if (isLoading) return <Spinner />; if (error) return <Error />; if (!data) return <NoData />; return <div>{data.status}</div>;

✅ useSuspenseQuery:

const { data } = useSuspenseQuery( trpc.subscription.getCurrent.queryOptions() ); // data sempre existe! Sem checks condicionais return <div>{data.status}</div>;

✅ Features Implementadas

useSuspenseQuery: Loading automático

useMutation: Cancelar assinatura

Cache Invalidation: UI sempre atualizada

Type Safety: Tipos automáticos do Prisma

Error Handling: Estados de loading

UX Otimizada: Confirmações e feedback

⚡ Passo 12: Resolvendo Performance TypeScript

🚨 Problemas Reais de Performance

Realidade crua: TypeScript tem problemas sérios de performance em projetos grandes com tRPC. Não é performance de runtime - é o Language Server que falha. Desenvolvedores ficam reiniciando o TS Server constantemente com Cmd+Shift+R.

🎯 Por que tRPC Causa Lentidão?

Inferência Excessiva: tRPC usa inferência de tipos massiva. Cada procedure é inferida do input até o output, sem definições manuais.

Problema N×N: Cada mudança recomputa TODOS os routers. Um novo endpoint força re-checagem de centenas de outros.

Cache Quebrado: Project References não funcionam adequadamente com tipos inferidos profundamente.

🔬 A Investigação

Alex (criador do tRPC) e Theo investiram dinheiro para contratar David Sherret (especialista em performance TS) para auditar o tRPC e descobrir o problema.

Descoberta surpreendente: O problema não era no tRPC - era um bug de performance no próprio TypeScript relacionado ao cache de tipos.

🚀 A Solução: TypeScript 5.9+

Resultado: Andarist (contribuidor lendário do TS) criou fix que reduziu instanciações de tipos em 78% em projetos tRPC.

Performance gains: Verificação de tipos 2x mais rápida, menos travamentos do Language Server, experiência mais fluida.

💡 Como Resolver Hoje
1. Upgrade TypeScript
terminal
# Instalar TypeScript 5.9+ (tem o fix de performance)
npm install typescript@beta

# Ou usar a versão estável mais recente
npm install typescript@latest

# Verificar versão
npx tsc --version
2. Configurar VS Code
.vscode/settings.json
{
  // Usar versão local do TypeScript
  "typescript.preferences.includePackageJsonAutoImports": "off",
  "typescript.suggest.autoImports": false,
  
  // Aumentar limite de memory para projetos grandes
  "typescript.tsserver.maxTsServerMemory": 8192,
  
  // Desabilitar features pesadas em projetos grandes
  "typescript.disableAutomaticTypeAcquisition": true
}
3. Otimizar tsconfig.json
tsconfig.json
{
  "compilerOptions": {
    // Acelerar compilação
    "incremental": true,
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
    
    // Reduzir work load
    "skipLibCheck": true,
    "skipDefaultLibCheck": true,
    
    // Module resolution mais eficiente
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "noEmit": true
  },
  
  // Excluir arquivos desnecessários
  "exclude": [
    "node_modules",
    "dist",
    ".next",
    "coverage"
  ]
}
🎯 Dicas de Performance para tRPC
✅ Faça
  • • Use TypeScript 5.9+ sempre
  • • Separe routers por módulo
  • • Evite routers muito grandes
  • • Use `skipLibCheck: true`
  • • Reinicie TS Server quando necessário
❌ Evite
  • • Centenas de procedures em um router
  • • Tipos muito profundos desnecessários
  • • TypeScript muito antigo
  • • Project references complexas
  • • Definições de tipo manuais
🎉 Estado Atual

Com TypeScript 5.9+, tRPC tem performance muito melhor. O investimento da comunidade resolveu um problema real que afetava milhares de desenvolvedores. Hoje você pode usar tRPC em projetos grandes sem sofrer com travamentos constantes.

✅ Setup Completo Finalizado

🎯 O que Você Conquistou:

  • ✅ tRPC configurado com type-safety completo
  • ✅ Middleware de autenticação Clerk integrado
  • ✅ Context reutilizável com user session
  • ✅ Query client otimizado para performance
  • ✅ Provider integrado no layout principal
  • ✅ Homepage otimizada com componentes shadcn/ui
  • ✅ Router de assinaturas com procedures protegidas
  • ✅ Dashboard refatorado com tRPC hooks
  • ✅ Performance TypeScript otimizada para produção

🚀 Próximos Passos:

  • • Criar primeiro router com procedures
  • • Implementar CRUD operations
  • • Adicionar validação com Zod
  • • Integrar com Prisma ORM