# High latency with the Supabase client

> Find where slow supabase-js requests spend their time (network distance, the query, RLS, or extra round trips) and the fixes that help most on a SupaHost project.

Source: https://supahost.app/kb/high-latency-client · Updated 2026-11-11

Slow requests usually come from one of three places: the distance between caller and instance, the query itself, or too many round trips. Measure before changing anything, because the fixes are different.

## What you see

Requests that take hundreds of milliseconds or several seconds, even for small results. Sometimes only from production, only from serverless functions, or only for some tables.

## Why it happens

- **Distance.** Every request travels to the region your instance runs in. A user in Sydney calling an instance in Frankfurt pays that round trip each time, and a TLS handshake on new connections.
- **Server code far from the instance.** A Vercel or Lambda function in one region calling an instance in another adds a long hop to every query.
- **The query.** A missing index, a large `select('*')` with big JSON columns, or `count: 'exact'` on a big table.
- **RLS policies that do work per row**, such as calling `auth.uid()` or a lookup function without wrapping it in a `select`.
- **Many small requests** where one would do: fetching a list, then one request per item.
- **Auth checks on every request.** `supabase.auth.getUser()` calls the Auth server each time.

## How to fix it

1. **Split network time from server time** with curl:

   ```bash
   curl -o /dev/null -s \
     -w "connect %{time_connect}s  tls %{time_appconnect}s  first byte %{time_starttransfer}s  total %{time_total}s\n" \
     "https://app-one.acme.supahost.space/rest/v1/messages?select=id,body&channel_id=eq.7&limit=50" \
     -H "apikey: $SUPABASE_ANON_KEY"
   ```

   If `connect` is already large, it's distance. If most of the time is between `tls` and `first byte`, it's the query.

2. **Time the query in the database.** In Studio's SQL editor:

   ```sql
   explain (analyze, buffers)
   select id, body from public.messages where channel_id = 7 order by created_at desc limit 50;
   ```

   A `Seq Scan` on a large table usually means a missing index:

   ```sql
   create index on public.messages (channel_id, created_at desc);
   ```

3. **Make RLS cheap.** Wrap auth calls so Postgres evaluates them once per query, and index the columns your policies filter on:

   ```sql
   create policy "Members read channel messages"
   on public.messages for select to authenticated
   using (channel_id in (select channel_id from public.channel_members where user_id = (select auth.uid())));
   ```

4. **Fetch less, in fewer requests.** Select only the columns you show, page with `.range()`, and embed related rows instead of looping:

   ```js
   const { data } = await supabase
     .from('channels')
     .select('id, name, messages(id, body, created_at)')
     .order('created_at', { referencedTable: 'messages', ascending: false })
     .limit(20, { referencedTable: 'messages' })
   ```

   For counts on big tables, use `{ count: 'estimated' }` instead of `'exact'`.

5. **Reuse the session.** On the client, read the session you already have instead of calling `getUser()` before every query. On the server you still need to verify the user, but do it once per request, not once per query.

6. **Put server code next to the instance.** Set your serverless functions' region to the one closest to your SupaHost region.

## On SupaHost

- An instance's region is fixed at creation. If most of your users are far from it, see [changing an instance's region](/kb/change-region).
- Projects on an instance share its CPU, memory and Postgres. A heavy import or a runaway query in one project slows the others. Measured read throughput is about 300 requests/s on Builder, 540 on Pro and 950 on Business. Sustained traffic close to that will raise latency; [plans and limits](/docs/platform/plans) has the numbers.
- Projects only have legacy HS256 keys, so `supabase.auth.getClaims()` can't verify tokens locally and makes a network call to Auth, just like `getUser()`.
- There's no connection pooler. Server code that connects with an ORM should keep a small, long-lived pool rather than connecting per request.

## Related

- [Speeding up slow queries with indexes](/kb/improve-query-performance)
- [RLS performance best practices](/kb/rls-performance)
- [Query performance and EXPLAIN](/docs/database/query-performance)
- [Regions](/docs/platform/regions)
