Belajar tRPC - Performansi & Optimasi tRPC
Series/Belajar tRPC/Episode 13
Episode 13 of 19

Belajar tRPC - Performansi & Optimasi tRPC

Episode ini mengoptimalkan aplikasi tRPC: memangkas round-trips dengan batch request, memakai dehydrate dan hydration di SSR agar client tidak fetch ulang, serta optimasi server dengan caching, data loader, dan lazy routers.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Di production, kecepatan adalah fitur. Episode 13 membahas cara memangkas latensi aplikasi tRPC dari tiga sisi: mengurangi round-trip di client, meminimalisasi fetch ulang dengan SSR hydration, dan mengoptimalkan sisi server dengan caching, data loader, serta lazy routers.

Kalian akan belajar bahwa optimasi terbaik dimulai dari mengurangi jumlah kerja yang tidak perlu, bukan sekadar membuat satu kerja lebih cepat.

Mengoptimalkan Batch Request

Memangkas Round-Trips

Round-trip adalah musuh latensi. Setiap request HTTP punya biaya tetap — TCP handshake, TLS, dan latency jaringan. httpBatchLink menggabungkan query yang bersamaan menjadi satu request:

Batch otomatis di tRPC React
const { data: user } = trpc.user.byId.useQuery({ id: 1 });
const { data: posts } = trpc.post.list.useQuery({ authorId: 1 });
const { data: comments } = trpc.comment.list.useQuery({ postId: 2 });

Ketiga query di atas di-render dalam satu komponen dan dibungkus httpBatchLink — tRPC React menggabungkannya menjadi satu round-trip HTTP, bukan tiga. Semakin banyak query per halaman, semakin besar penghematannya.

Menggabungkan Data Secara Manual

Batch bekerja untuk query paralel. Untuk data yang selalu dibutuhkan bersama, pertimbangkan prosedur agregat dari episode 12:

Prosedur agregat
dashboard: t.procedure.query(async ({ ctx }) => {
  const [stats, recentPosts, notifikasi] = await Promise.all([
    ambilStats(),
    ambilRecentPosts(),
    ambilNotifikasi(ctx.userId),
  ]);
  return { stats, recentPosts, notifikasi };
});

Promise.all menjalankan tiga akses data paralel di dalam satu procedure — satu round-trip ke client, tapi tiga query database yang berjalan bersamaan.

Dehydrate dan Hydration di SSR

Mengapa SSR Perlu Hydration

Di Server-Side Rendering, halaman dirender server dan dikirim sebagai HTML. Tanpa dehydration, browser akan fetch ulang semua data yang sudah dirender server — sia-sia. tRPC plus React Query menyelesaikannya dengan dehydrate:

Dehydrate query di server (App Router)
import { dehydrate, HydrationBoundary, QueryClient } from "@tanstack/react-query";
import { createCaller } from "@/server/api/root";
 
export default async function ProfilePage() {
  const queryClient = new QueryClient();
 
  await queryClient.prefetchQuery({
    queryKey: ["user.byId", { id: 1 }],
    queryFn: () => caller.user.byId({ id: 1 }),
  });
 
  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Profile />
    </HydrationBoundary>
  );
}

queryClient.prefetchQuery mengisi cache di server, lalu dehydrate(queryClient) menyerahkan cache tersebut ke client sebagai prop. Komponen Profile yang memakai useQuery akan membaca cache langsung — tanpa fetch tambahan saat browser me-render.

Manfaat untuk Latency dan SEO

Dengan hydration, data yang sama dikirim sekali dalam HTML; browser tidak perlu menunggu request tambahan untuk menampilkan konten pertama. Ini mempercepat Time to First Byte dan membuat konten dapat diindeks mesin pencari. Episode 16 akan mengintegrasikan pola ini penuh dengan Next.js.

Optimasi Server-Side

Caching Response

Untuk data yang jarang berubah, cache hasil procedure:

Caching sederhana dengan Map
const cache = new Map<string, { data: unknown; expires: number }>();
 
const ambilDenganCache = async (key: string, fetcher: () => Promise<unknown>) => {
  const item = cache.get(key);
  if (item && item.expires > Date.now()) return item.data;
 
  const data = await fetcher();
  cache.set(key, { data, expires: Date.now() + 60_000 });
  return data;
};
 
topProducts: t.procedure.query(() =>
  ambilDenganCache("top-products", () => db.product.findTop(10)),
),

Cache Map sederhana cukup untuk satu instance dengan TTL 60 detik. Untuk deployment terdistribusi, pindahkan cache ke Redis — pola logikanya tetap sama, hanya penyimpanannya yang berganti.

Data Loader untuk Menghindari N+1

Masalah N+1 terjadi saat satu request memicu banyak query kecil — misalnya mengambil 10 posting lalu 10 kali query penulis. Solusinya: batch query dalam satu resolver, atau gunakan library DataLoader:

Mengelompokkan query penulis
listWithAuthors: t.procedure.query(async () => {
  const posts = await db.post.findMany();
  const authorIds = [...new Set(posts.map((p) => p.authorId))];
  const authors = await db.user.findMany({ where: { id: { in: authorIds } } });
  const byId = new Map(authors.map((a) => [a.id, a]));
 
  return posts.map((p) => ({ ...p, author: byId.get(p.authorId) }));
});

Satu query untuk semua posting, satu query untuk semua penulis, lalu digabung di memori — bukan 11 query.

Lazy Routers

Aplikasi besar punya router yang jarang diakses. Pisahkan ke file terpisah dan muat sesuai kebutuhan. tRPC v11 mendukung lazy router yang baru di-load saat procedure-nya dipanggil, sehingga startup server tidak mengeksekusi kode router yang berat:

Lazy router di tRPC v11
const appRouter = t.router({
  user: userRouter,
  analytics: t.router({}, {
    lazy: () => import("./routers/analytics"),
  }),
});

Dengan pola ini, dependency berat milik analytics baru ditarik ketika procedure-nya benar-benar dipanggil — mempercepat startup dan menurunkan penggunaan memori.

Tip

Urutkan optimasi dari yang paling berdampak: kurangi round-trip dan fetch ulang dulu, lalu caching, baru pikirkan hal-hal mikro. Data yang sudah tidak di-fetch ulang tidak perlu caching.

Penutup

Episode 13 mengoptimalkan tRPC di tiga lapis: batch dan agregasi memangkas round-trip di client, dehydrate-hydration mencegah fetch ulang di SSR, dan caching, data loader, serta lazy routers meringankan beban server.

Inti yang harus dibawa pulang:

  • httpBatchLink menggabungkan query paralel menjadi satu request.
  • Prosedur agregat dengan Promise.all memangkas round-trip tambahan.
  • dehydrate dan HydrationBoundary menghindari fetch ulang setelah SSR.
  • Caching dengan TTL menyimpan hasil procedure yang jarang berubah.
  • Batch query penulis mencegah masalah N+1.
  • Lazy routers memuat kode berat hanya saat dipanggil.

Di episode 14 selanjutnya kita akan membahas observability, tracing & monitoring — memantau panggilan tRPC dengan OpenTelemetry, metrics, dan logs, melacak request end-to-end di client dan server, serta debugging dengan devtools dan built-in logger.

Belajar tRPC - Performansi & Optimasi tRPC | Belajar tRPC