20 Jun 2026 · 2 min read
Surviving signup spikes with React Query
When hundreds of people hit the same page at once, caching is a feature. How stale-while-revalidate keeps your server calm and the UI instant.
Tournament registration opens, a link goes out in a group chat, and suddenly a few hundred people refresh the same page. Every refresh is a request. If each one hits the database, the server falls over exactly when it matters.
You can't stop the stampede, but you can make it cheap.
Cache on the client first
React Query's core idea is stale-while-revalidate: show what you already have instantly, then refresh quietly in the background. Tell it how long data stays "fresh":
const { data: event } = useQuery({
queryKey: ["event", slug],
queryFn: () => fetch(`/api/events/${slug}`).then((r) => r.json()),
staleTime: 30_000, // no refetch for 30s, even across component remounts
gcTime: 5 * 60_000, // keep unused data for 5 minutes
});Navigating back and forth between pages now costs nothing. Previously every visit was a fresh request.
Keep the previous page on screen while filters change
const { data } = useQuery({
queryKey: ["players", { page, search }],
queryFn: () => fetchPlayers({ page, search }),
placeholderData: keepPreviousData,
});Users see the old list until the new one arrives, so the UI never flashes empty.
Optimistic registration
Registering should feel instant. Update the cache first, roll back if the server says no:
const register = useMutation({
mutationFn: registerForEvent,
onMutate: async () => {
await qc.cancelQueries({ queryKey: ["event", slug] });
const prev = qc.getQueryData(["event", slug]);
qc.setQueryData(["event", slug], (e: any) => ({ ...e, joined: true }));
return { prev };
},
onError: (_e, _v, ctx) => qc.setQueryData(["event", slug], ctx?.prev),
onSettled: () => qc.invalidateQueries({ queryKey: ["event", slug] }),
});Don't forget the server
Client caching reduces repeat visits, but first visits still land on your API. Pair it with:
- HTTP cache headers or CDN caching for read-only event data
- Rate limiting on the write endpoint
- A unique constraint on (event, player) so double-clicks can't double-register
The mindset
Treat a spike as a read-heavy workload with a tiny write path. Make reads nearly free, make writes safe, and the stampede becomes just another Tuesday.