Performance & Scalability serimizin bir parçası
Tam kılavuzu okuyunReact Query (TanStack): Veri Alma Modelleri
React'ta veri getirme şaşırtıcı derecede geniş bir yüzey alanına sahiptir: yükleme durumları, hata durumları, arka planı yeniden getirme, mutasyonlardan sonra önbelleği geçersiz kılma, iyimser güncellemeler, sonsuz kaydırma, önceden getirme ve tekilleştirme. Bu mantığın useEffect + useState ile elle yazılması yarış koşullarına, bellek sızıntılarına ve tutarsız kullanıcı arayüzü durumlarına yol açar. TanStack Query (eski adıyla React Query) v5, tüm bunları temiz, şekillendirilebilir bir API ile çözüyor.
Bu kılavuz, temel sorgulardan karmaşık mutasyon akışlarına, iyimser güncellemelere, sonsuz kaydırmaya ve Next.js Uygulama Yönlendirici entegrasyonuna kadar üretim React uygulamalarına yönelik TanStack Query v5 modellerini kapsar. Her model TypeScript ve hata işlemeyi içerir.
Önemli Çıkarımlar
- TanStack Query bir sunucu durumu kitaplığıdır — yalnızca istemci kullanıcı arayüzü durumu için kullanmayın (bunun için Zustand veya useState kullanın)
- Sorgu anahtarları önbellek adresidir — sorgu sonucunu etkileyen tüm değişkenleri içerir
staleTimearka planda yeniden getirmenin ne zaman tetikleneceğini kontrol eder;gcTimekullanılmayan verilerin ne zaman çöp olarak toplandığını kontrol eder- Mutasyonlar,
onSuccessiçindekiqueryClient.invalidateQueries()yoluyla ilgili sorguları geçersiz kılmalıdır- İyimser güncellemeler algılanan performansı artırır ancak
onError'da geri alma mantığı gerektirir- Sayfalandırılmış listeler için
useInfiniteQuerykullanın;useQuery+ manuel sayfa durumunu asla birleştirmeyin- Anında gezinme hissi için fareyle üzerine gelindiğinde önceden getirme:
queryClient.prefetchQuery()- Next.js Uygulama Yönlendiricisinde, sunucu tarafından getirilen verileri istemciye aktarmak için
HydrationBoundarykullanın
Kurulum ve Yapılandırma
pnpm add @tanstack/react-query @tanstack/react-query-devtools
// src/providers/query-provider.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useState } from 'react';
export function QueryProvider({ children }: { children: React.ReactNode }) {
// Create QueryClient inside component to avoid shared state between requests (SSR safety)
const [queryClient] = useState(() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute — data is fresh for 1 minute
gcTime: 5 * 60 * 1000, // 5 minutes — keep unused cache for 5 minutes
retry: 2, // Retry failed requests twice
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30_000), // Exponential backoff
refetchOnWindowFocus: true, // Refetch when user returns to tab
refetchOnReconnect: true, // Refetch after network reconnect
},
mutations: {
retry: 0, // Do not retry mutations (non-idempotent)
},
},
})
);
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
// src/app/[locale]/layout.tsx
import { QueryProvider } from '@/providers/query-provider';
export default function LocaleLayout({ children }: { children: React.ReactNode }) {
return (
<QueryProvider>
{children}
</QueryProvider>
);
}
Sorgu Anahtarları — Temel
Sorgu anahtarları verilerinizin önbellek adresidir. Sorgu sonucunu etkileyen her değişkenin anahtarda olması gerekir.
// src/lib/query-keys.ts
// Centralize query keys to avoid typos and enable easy invalidation
export const queryKeys = {
contacts: {
all: () => ['contacts'] as const,
lists: () => [...queryKeys.contacts.all(), 'list'] as const,
list: (filters: ContactFilters) =>
[...queryKeys.contacts.lists(), filters] as const,
details: () => [...queryKeys.contacts.all(), 'detail'] as const,
detail: (id: string) =>
[...queryKeys.contacts.details(), id] as const,
},
orders: {
all: () => ['orders'] as const,
list: (orgId: string, page: number) => ['orders', orgId, page] as const,
detail: (id: string) => ['orders', id] as const,
},
} as const;
// Usage in queries and invalidations:
// useQuery({ queryKey: queryKeys.contacts.list({ page: 1, limit: 20 }) })
// queryClient.invalidateQueries({ queryKey: queryKeys.contacts.lists() })
// — invalidates ALL list queries regardless of filter parameters
Temel Sorgular
// src/hooks/use-contacts.ts
import { useQuery } from '@tanstack/react-query';
import { apiFetch } from '@/lib/api';
import { queryKeys } from '@/lib/query-keys';
interface ContactFilters {
page: number;
limit: number;
search?: string;
status?: string;
}
interface ContactsResponse {
data: Contact[];
total: number;
page: number;
limit: number;
}
export function useContacts(filters: ContactFilters) {
return useQuery({
queryKey: queryKeys.contacts.list(filters),
queryFn: async () => {
const params = new URLSearchParams({
page: String(filters.page),
limit: String(filters.limit),
...(filters.search && { search: filters.search }),
...(filters.status && { status: filters.status }),
});
return apiFetch<ContactsResponse>(`/contacts?${params}`);
},
placeholderData: (previousData) => previousData, // Keep previous page data while fetching
staleTime: 30_000, // Contacts data: 30 second stale time
});
}
export function useContact(id: string) {
return useQuery({
queryKey: queryKeys.contacts.detail(id),
queryFn: () => apiFetch<Contact>(`/contacts/${id}`),
enabled: !!id, // Only fetch if id is provided
});
}
// Usage in a component
'use client';
export default function ContactsPage() {
const [page, setPage] = useState(1);
const [search, setSearch] = useState('');
const { data, isLoading, isError, error, isFetching } = useContacts({
page,
limit: 20,
search: search || undefined,
});
if (isLoading) return <ContactsSkeleton />;
if (isError) return <ErrorState message={error.message} />;
return (
<div>
{isFetching && <RefetchIndicator />} {/* Shows during background refetch */}
<ContactsTable data={data.data} />
<Pagination
page={page}
total={data.total}
limit={20}
onChange={setPage}
/>
</div>
);
}
Önbellek Geçersiz Kılma ile Mutasyonlar
// src/hooks/use-contact-mutations.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { apiFetch } from '@/lib/api';
import { queryKeys } from '@/lib/query-keys';
import { toast } from '@/components/ui/toast';
export function useCreateContact() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateContactDto) =>
apiFetch<Contact>('/contacts', {
method: 'POST',
body: JSON.stringify(data),
}),
onSuccess: (newContact) => {
// Invalidate all contact list queries — they are now stale
queryClient.invalidateQueries({
queryKey: queryKeys.contacts.lists(),
});
// Seed the detail cache immediately — no need to refetch
queryClient.setQueryData(
queryKeys.contacts.detail(newContact.id),
newContact
);
toast({ title: 'Contact created', variant: 'success' });
},
onError: (error: Error) => {
toast({ title: 'Failed to create contact', description: error.message, variant: 'destructive' });
},
});
}
export function useDeleteContact() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) =>
apiFetch(`/contacts/${id}`, { method: 'DELETE' }),
onSuccess: (_data, deletedId) => {
// Remove from all list queries
queryClient.invalidateQueries({ queryKey: queryKeys.contacts.lists() });
// Remove detail cache
queryClient.removeQueries({ queryKey: queryKeys.contacts.detail(deletedId) });
toast({ title: 'Contact deleted', variant: 'success' });
},
});
}
İyimser Güncellemeler
İyimser güncellemeler, sunucu onaylamadan önce kullanıcı arayüzünü güncelleyerek mutasyonların anında hissedilmesini sağlar:
export function useUpdateContact() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateContactDto }) =>
apiFetch<Contact>(`/contacts/${id}`, {
method: 'PATCH',
body: JSON.stringify(data),
}),
// Called immediately before mutationFn
onMutate: async ({ id, data }) => {
// Cancel any in-flight refetches for this contact
await queryClient.cancelQueries({ queryKey: queryKeys.contacts.detail(id) });
// Snapshot the previous value for rollback
const previousContact = queryClient.getQueryData<Contact>(
queryKeys.contacts.detail(id)
);
// Optimistically update the UI
queryClient.setQueryData<Contact>(
queryKeys.contacts.detail(id),
(old) => old ? { ...old, ...data } : old
);
return { previousContact }; // Passed to onError context
},
onError: (_error, { id }, context) => {
// Roll back to the snapshot on error
if (context?.previousContact) {
queryClient.setQueryData(
queryKeys.contacts.detail(id),
context.previousContact
);
}
toast({ title: 'Update failed', variant: 'destructive' });
},
onSettled: (_data, _error, { id }) => {
// Always refetch after success or error to sync with server truth
queryClient.invalidateQueries({ queryKey: queryKeys.contacts.detail(id) });
},
});
}
Sonsuz Kaydırma / Sayfalandırma
// src/hooks/use-infinite-contacts.ts
import { useInfiniteQuery } from '@tanstack/react-query';
export function useInfiniteContacts(search?: string) {
return useInfiniteQuery({
queryKey: ['contacts', 'infinite', search],
queryFn: ({ pageParam = 1 }) =>
apiFetch<ContactsResponse>(`/contacts?page=${pageParam}&limit=20${search ? `&search=${search}` : ''}`),
initialPageParam: 1,
getNextPageParam: (lastPage) => {
const nextPage = lastPage.page + 1;
const maxPage = Math.ceil(lastPage.total / lastPage.limit);
return nextPage <= maxPage ? nextPage : undefined;
},
staleTime: 30_000,
});
}
// Infinite scroll component
'use client';
import { useInfiniteContacts } from '@/hooks/use-infinite-contacts';
import { useInView } from 'react-intersection-observer';
import { useEffect } from 'react';
export function InfiniteContactsList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
} = useInfiniteContacts();
const { ref, inView } = useInView({ threshold: 0.5 });
// Fetch next page when the sentinel element enters the viewport
useEffect(() => {
if (inView && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
if (isLoading) return <ContactsSkeleton />;
const contacts = data?.pages.flatMap((page) => page.data) ?? [];
return (
<div>
{contacts.map((contact) => (
<ContactCard key={contact.id} contact={contact} />
))}
{/* Sentinel element — triggers next page load when visible */}
<div ref={ref} className="h-4">
{isFetchingNextPage && <LoadingSpinner />}
</div>
</div>
);
}
Anında Gezinme için Ön Getirme
// Prefetch on hover — data is ready before the user clicks
'use client';
import { useQueryClient } from '@tanstack/react-query';
import { Link } from '@/i18n/navigation';
import { queryKeys } from '@/lib/query-keys';
import { apiFetch } from '@/lib/api';
export function ContactRow({ contact }: { contact: Contact }) {
const queryClient = useQueryClient();
const handleMouseEnter = () => {
queryClient.prefetchQuery({
queryKey: queryKeys.contacts.detail(contact.id),
queryFn: () => apiFetch<Contact>(`/contacts/${contact.id}`),
staleTime: 60_000, // Use cached if less than 1 minute old
});
};
return (
<tr onMouseEnter={handleMouseEnter}>
<td>
<Link href={`/dashboard/contacts/${contact.id}`}>{contact.name}</Link>
</td>
<td>{contact.email}</td>
</tr>
);
}
Next.js Uygulama Yönlendiricisi SSR + TanStack Sorgusu
// src/app/[locale]/dashboard/contacts/page.tsx (Server Component)
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
import { queryKeys } from '@/lib/query-keys';
import { getServerContacts } from '@/lib/server-api';
import { ContactsClient } from './contacts-client';
export default async function ContactsPage({ params }: Props) {
const { locale } = await params;
const queryClient = new QueryClient();
// Prefetch on the server — data is embedded in the HTML
await queryClient.prefetchQuery({
queryKey: queryKeys.contacts.list({ page: 1, limit: 20 }),
queryFn: () => getServerContacts({ page: 1, limit: 20 }),
});
return (
// HydrationBoundary serializes the server QueryClient state
// into the HTML, which the client QueryClient then picks up
<HydrationBoundary state={dehydrate(queryClient)}>
<ContactsClient />
</HydrationBoundary>
);
}
// src/app/[locale]/dashboard/contacts/contacts-client.tsx ('use client')
'use client';
import { useContacts } from '@/hooks/use-contacts';
export function ContactsClient() {
const { data } = useContacts({ page: 1, limit: 20 });
// data is immediately available from the server-dehydrated state
// No loading state on first render — SSR data is used instantly
return <ContactsTable data={data?.data ?? []} />;
}
Sıkça Sorulan Sorular
TanStack Sorgusunu, SWR'yi ve düz getirmeyi ne zaman kullanmalıyım?
İhtiyacınız olduğunda TanStack Sorgusunu kullanın: mutasyonlardan sonra önbellek geçersiz kılma, iyimser güncellemeler, sonsuz kaydırma, önceden getirme, arka planda yeniden getirme veya tekilleştirme isteği. Daha küçük bir pakete ve yalnızca basit kullanım senaryolarına ihtiyacınız varsa SWR'yi kullanın. React'in yerel veri önbelleğe almasını istediğiniz Next.js Sunucu Bileşenlerinde düz fetch kullanın (cache() işlevi ve revalidatePath() modeli). TanStack Sorgusu, kullanıcı eylemlerinin sunucu verilerini etkilediği istemci tarafı etkileşimi içindir.
staleTime ile gcTime arasındaki fark nedir?
staleTime verilerin ne kadar süreyle taze kabul edildiğini gösterir; bu süre zarfında arka planda yeniden getirme gerçekleşmez ve önbelleğe alınan veriler hemen döndürülür. staleTime'in süresi dolduktan sonra veriler "eski" hale gelir ve bir sonraki kullanımda arka planda yeniden getirilir. gcTime (önceden cacheTime), kullanılmayan (aktif abone olmayan) verilerin çöp toplamadan önce bellekte ne kadar süre kalacağını belirtir. Verilerinizin ne kadar hızlı değiştiğine bağlı olarak staleTime değerini ayarlayın; Anında hissetmek için ne kadar süre uzaklaşıp geri dönmek istediğinize bağlı olarak gcTime değerini ayarlayın.
Kimlik doğrulama hatalarını (401) genel olarak nasıl ele alırım?
QueryClient varsayılan seçeneklerinize genel bir hata işleyicisi ekleyin. 401'de oturum açmaya yönlendirin ve sorgu önbelleğini temizleyin. TanStack Sorgu v5'te: defaultOptions: { queries: { throwOnError: (error) => { if (error.status === 401) { router.push('/auth/login'); queryClient.clear(); return false; } return true; } } }. Alternatif olarak, apiFetch yardımcınızda logout() işlevini çağırarak 401'i işleyin.
Sorgu durumunu kardeş bileşenler arasında pervane ayrıntısına girmeden nasıl paylaşırım?
Aynı sorgu anahtarıyla useQuery çağıran herhangi bir bileşen, önbelleğe alınmış aynı verileri paylaşır; TanStack Sorgusu, istekleri otomatik olarak tekilleştirir. İki ContactsTable bileşeni ve aynı filtrelerle useContacts({ page: 1, limit: 20 }) çağıran bir ContactCount rozetinin tümü, önbelleğe alınmış tek bir istekten okunacaktır. Pervane sondajı yok, bağlama gerek yok.
Sorgu anahtarları kullanıcının oturumunu veya kuruluş kimliğini içermeli mi?
Evet — verilerin kapsamı bir kullanıcı veya kuruluşa göre belirlenmişse sorgu anahtarına kapsam tanımlayıcıyı ekleyin. Bu, bir kullanıcının verilerinin bir başkası için görünmesini engeller (çok kiracılı uygulamalar için kritik öneme sahiptir). Ayrıca oturum kapatıldığında sorgu önbelleğinin tamamını temizleyin: queryClient.clear() veya queryClient.removeQueries() — aksi takdirde önceki oturumdaki eski veriler bir sonraki kullanıcı için kısa süreliğine yanıp sönebilir.
Sonraki Adımlar
TanStack Query, bir durum yönetimi probleminden alınan React verilerini bildirim temelli, önbellek farkındalığına sahip bir sisteme dönüştürür. İyimser güncellemeler, önceden getirme ve SSR hidrasyonu, kullanıcılarınıza yavaş bağlantılarda bile anında hissedilen bir deneyim sunar.
ECOSIRE, tüm sunucu durumları için TanStack Query ile yalnızca istemci kullanıcı arayüzü durumu için Zustand ve veri ağırlıklı görünümler için TanStack Table ile birlikte Next.js yönetici panelleri ve müşteri portalları oluşturur. Performanslı, üretime hazır React uygulamalarını nasıl oluşturduğumuzu öğrenmek için Ön uç mühendislik hizmetlerimizi keşfedin.
Yazan
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
ECOSIRE ile İşinizi Büyütün
ERP, e-Ticaret, yapay zeka, analitik ve otomasyon genelinde kurumsal çözümler.
İlgili Makaleler
Next.js 16 Uygulama Yönlendiricisi: Üretim Modelleri ve Tuzaklar
Üretime hazır Next.js 16 Uygulama Yönlendirici modeli: sunucu bileşenleri, önbelleğe alma stratejileri, meta veri API'si, hata sınırları ve kaçınılması gereken performans tuzakları.
Nginx Üretim Yapılandırması: SSL, Önbelleğe Alma ve Güvenlik
Nginx üretim yapılandırma kılavuzu: SSL sonlandırma, HTTP/2, önbelleğe alma başlıkları, güvenlik başlıkları, hız sınırlama, ters proxy kurulumu ve Cloudflare entegrasyon modelleri.
Yapay Zeka Aracısı Maliyetlerini Optimize Etme: Belirteç Kullanımı ve Önbelleğe Alma
Belirteç optimizasyonu, önbelleğe alma, model yönlendirme ve kullanım izleme yoluyla AI aracısının operasyonel maliyetlerini azaltmaya yönelik pratik stratejiler. Üretim OpenClaw dağıtımlarından gerçek tasarruf.
Performance & Scalability serisinden daha fazlası
Web Kancası Hata Ayıklama ve İzleme: Eksiksiz Sorun Giderme Kılavuzu
Arıza modellerini, hata ayıklama araçlarını, yeniden deneme stratejilerini, izleme kontrol panellerini ve en iyi güvenlik uygulamalarını kapsayan bu eksiksiz kılavuzla webhook hata ayıklama konusunda uzmanlaşın.
k6 Yük Testi: Lansmandan Önce API'lerinize Stres Testi Yapın
Node.js API'leri için k6 yük testinde uzmanlaşın. Sanal kullanıcı artışlarını, eşikleri, senaryoları, HTTP/2, WebSocket testini, Grafana kontrol panellerini ve CI entegrasyon modellerini kapsar.
Nginx Üretim Yapılandırması: SSL, Önbelleğe Alma ve Güvenlik
Nginx üretim yapılandırma kılavuzu: SSL sonlandırma, HTTP/2, önbelleğe alma başlıkları, güvenlik başlıkları, hız sınırlama, ters proxy kurulumu ve Cloudflare entegrasyon modelleri.
Odoo Performans Ayarlama: PostgreSQL ve Sunucu Optimizasyonu
Odoo 19 performans ayarlaması için uzman kılavuzu. Kurumsal dağıtımlar için PostgreSQL yapılandırmasını, indekslemeyi, sorgu optimizasyonunu, Nginx önbelleğe almayı ve sunucu boyutlandırmayı kapsar.
Odoo ve Acumatica: Büyüyen İşletmeler için Bulut ERP
Odoo ve Acumatica'nın 2026 karşılaştırması: benzersiz fiyatlandırma modelleri, ölçeklenebilirlik, üretim derinliği ve hangi bulut ERP'nin büyüme yörüngenize uyduğu.
Üretimde Yapay Zeka Aracılarını Test Etme ve İzleme
Üretim ortamlarında yapay zeka aracılarını test etmeye ve izlemeye yönelik eksiksiz bir kılavuz. OpenClaw dağıtımları için değerlendirme çerçevelerini, gözlemlenebilirliği, sapma tespitini ve olay müdahalesini kapsar.