Anonymous (logged-out) PostgREST queries return stale public data while authenticated queries are current #48772
Replies: 4 comments
|
Supabase does not cache. But something like next.js can and does depending on your settings. Check in the API Gateway log and see if the request even makes it to the Supabase server. If a request was blocked by RLS you would get no data back and not stale data. |
|
Building on Gary's point — the split you're describing (authenticated always fresh, anonymous stale, across browsers, surviving a redeploy) is the classic signature of an HTTP/framework cache layer in front of Supabase, not of PostgREST itself. Authenticated requests carry an A quick way to prove where the staleness lives — query PostgREST directly, bypassing your app entirely: curl 'https://<project-ref>.supabase.co/rest/v1/<table>?select=*&order=created_at.desc&limit=5' \
-H "apikey: <anon-key>" \
-H "Authorization: Bearer <anon-key>"If that returns the new record (it almost certainly will), Supabase's anonymous path is fine and the snapshot is being served by your frontend stack. Then, assuming a Next.js/Vercel-style setup (adjust for your framework):
Gary's suggestion is the corresponding server-side check: if the anonymous page load doesn't even show up in the API Gateway logs, no request reached Supabase at all, which confirms the cached-frontend theory. |
|
Before digging into the anonymous connection pool or PostgREST session state, it's worth isolating whether PostgREST is actually the thing serving stale data. In reports shaped like this one it usually isn't. The reason is that "logged out" and "logged in" almost always differ by more than the database role. On the client, an anonymous request carries no per-user Two checks that separate the layers1. Hit PostgREST directly, bypassing your app entirely. curl -s \
-H "apikey: <ANON_KEY>" \
-H "Authorization: Bearer <ANON_KEY>" \
"https://<project-ref>.supabase.co/rest/v1/<table>?select=id,created_at&order=created_at.desc&limit=5"Run it immediately after creating a record that isn't showing up. This uses the
2. Look at the response headers on the request your app makes when logged out. Check One RLS detail to rule out while you're there
That said, I'd expect an RLS gap to produce "anonymous users never see these rows" rather than "anonymous users see an older snapshot". If older public records do appear anonymously and only the newest ones are missing, caching fits the evidence better than RLS does. If instead the anonymous view is missing rows regardless of age, look at the policy first. If check 1 does reproduceThat's the case worth escalating, and it'd be genuinely unusual. Include in the report:
Two |
|
The cache explanations above are the most likely answer and the Gary's point that "if a request was blocked by RLS you would get no data back and not stale data" holds for a policy that denies everything. It does not hold for a policy that denies some rows — and there the symptom is indistinguishable from a stale cache: an anonymous reader sees a set of rows that stops at some point in the past and never advances. Your own wording is what makes me raise it. You write "Create a new Community/Public observation", which suggests a record can be one of at least two visibility states. If the anonymous policy admits only one of them and new records are being written as the other, then:
Every line of your report is satisfied by that, including surviving a redeploy and reproducing across browsers and incognito — which is exactly the evidence being read as "it must be a cache". The discriminator, with no HTTP layer involved at all: begin;
select set_config('request.jwt.claims', '{"role":"anon"}', true);
set local role anon;
select count(*) as rows_anon_sees,
max(created_at) as newest_row_anon_sees
from public.observations; -- your table name here
rollback;Then compare against the unrestricted view: select count(*) as rows_total, max(created_at) as newest_row_total
from public.observations;
Note the If it does turn out to be the policy, this is the query that shows you why in one pass: select p.polname,
case p.polcmd when 'r' then 'SELECT' when '*' then 'ALL' else p.polcmd::text end as cmd,
array(select rolname from pg_roles where oid = any(p.polroles)) as roles,
pg_get_expr(p.polqual, p.polrelid) as using_expr
from pg_policy p
join pg_class c on c.oid = p.polrelid
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relname = 'observations'
order by p.polcmd, p.polname;An empty |
Uh oh!
There was an error while loading. Please reload this page.
Hi Supabase team,
I'm seeing what appears to be a project-specific issue affecting anonymous (logged-out) reads.
Project
Project Reference: yvqicujzfkeyymwdvnbf
Expected behavior
A newly created Community/Public record should become visible immediately to both authenticated and anonymous users.
Actual behavior
Authenticated users immediately see newly created public records.
Anonymous (logged-out) users continue seeing an older snapshot of the public data even after repeated refreshes and waiting several minutes.
Reproduction
Sign in.
Create a new Community/Public observation.
Confirm it appears immediately while authenticated.
Open a completely logged-out/private browser session.
Visit the public feed.
The new public record does not appear.
What we've already verified
The insert succeeds.
The record appears on the author's profile.
The record appears in the authenticated Recent Observations feed.
RLS policies have been reviewed and appear correct.
Authenticated users consistently receive fresh data.
The issue only affects anonymous requests.
The issue reproduces across multiple browsers and private/incognito sessions.
The behavior persisted after deploying a new production build.
Question
Has anyone seen anonymous PostgREST reads become "stuck" or serve stale data for a single project while authenticated reads remain current?
Is there anything we should inspect regarding the anonymous connection pool, PostgREST session, or project configuration?
If additional diagnostics or request IDs would help, I'm happy to provide them.
Thank you!
All reactions