@luanpdd/kit-mcp 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/gates/agent-no-recursive-dispatch.md +48 -0
  3. package/gates/budget-description.md +68 -0
  4. package/gates/no-personal-uuid.md +72 -0
  5. package/gates/skill-must-include.md +69 -0
  6. package/gates/sync-idempotent.md +62 -0
  7. package/kit/agents/codebase-mapper.md +1 -1
  8. package/kit/agents/executor.md +17 -0
  9. package/kit/agents/planner.md +35 -0
  10. package/kit/agents/project-researcher.md +1 -1
  11. package/kit/agents/schema-checker.md +4 -4
  12. package/kit/agents/supabase-architect.md +153 -0
  13. package/kit/agents/supabase-auth-bootstrapper.md +298 -0
  14. package/kit/agents/supabase-edge-fn-writer.md +185 -0
  15. package/kit/agents/supabase-migration-writer.md +156 -0
  16. package/kit/agents/supabase-realtime-implementer.md +252 -0
  17. package/kit/agents/supabase-rls-writer.md +218 -0
  18. package/kit/agents/supabase-storage-implementer.md +240 -0
  19. package/kit/agents/user-profiler.md +1 -1
  20. package/kit/agents/verifier.md +1 -1
  21. package/kit/commands/depurar.md +17 -0
  22. package/kit/commands/fazer.md +15 -0
  23. package/kit/commands/supabase.md +148 -0
  24. package/kit/framework/workflows/discuss-phase.md +19 -0
  25. package/kit/framework/workflows/plan-phase.md +25 -0
  26. package/kit/skills/_shared-supabase/glossary.md +180 -0
  27. package/kit/skills/supabase-auth-ssr/SKILL.md +260 -0
  28. package/kit/skills/supabase-cron-queues/SKILL.md +266 -0
  29. package/kit/skills/supabase-database-functions/SKILL.md +247 -0
  30. package/kit/skills/supabase-declarative-schema/SKILL.md +183 -0
  31. package/kit/skills/supabase-edge-functions/SKILL.md +242 -0
  32. package/kit/skills/supabase-migrations/SKILL.md +175 -0
  33. package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -0
  34. package/kit/skills/supabase-postgres-style/SKILL.md +138 -0
  35. package/kit/skills/supabase-realtime/SKILL.md +236 -0
  36. package/kit/skills/supabase-rls-policies/SKILL.md +185 -0
  37. package/kit/skills/supabase-storage/SKILL.md +234 -0
  38. package/package.json +1 -1
@@ -0,0 +1,180 @@
1
+ # Glossário Supabase — Termos, Comandos e Patterns Canônicos
2
+
3
+ > Arquivo de referência compartilhado pelas skills `supabase-*`. **NÃO é skill** — não tem `description:` triggerável; não aparece em `listKit`. Cross-referenciado pelas 11 skills via Markdown link relativo.
4
+
5
+ ---
6
+
7
+ ## (a) Termos PT-BR ↔ EN
8
+
9
+ ### Authorization e Auth
10
+
11
+ | EN | PT-BR / Significado |
12
+ |---|---|
13
+ | **RLS** | Row Level Security — segurança em nível de linha. Filtra automaticamente quais linhas de uma tabela cada usuário vê/modifica baseado em policies. |
14
+ | **policy** | Política de RLS — regra `create policy ... on <table> for <op> ...`. Sempre granular por operação (SELECT/INSERT/UPDATE/DELETE). |
15
+ | **`auth.uid()`** | Função que retorna o UUID do usuário autenticado da sessão atual. **Sempre** usar em `(select auth.uid())` em policies. |
16
+ | **`auth.jwt()`** | Função que retorna o JWT decodificado da sessão. Acesso via `auth.jwt()->'app_metadata'` ou `auth.jwt()->>'aal'`. |
17
+ | **`app_metadata`** | Metadata controlado pelo backend (apenas service_role pode mutar). **Use para roles/permissions** em RLS. |
18
+ | **`user_metadata`** | Metadata controlado pelo cliente (`auth.updateUser({data: ...})`). **NUNCA** use em policy de autorização — privilege escalation. |
19
+ | **service_role** | Role do Postgres com bypass total de RLS. **NUNCA** expor ao cliente — vazamento = acesso total ao DB. |
20
+ | **anon** | Role para requests sem autenticação. RLS aplicado normalmente. |
21
+ | **authenticated** | Role para usuário autenticado. RLS aplicado normalmente. |
22
+ | **public** | Role default — equivale a anon + authenticated juntos. Evite — sempre use `to authenticated` ou `to anon` explícito. |
23
+ | **AAL** | Authentication Assurance Level. `aal1` = senha apenas; `aal2` = senha + 2FA. Verifica via `(auth.jwt()->>'aal')::text`. |
24
+
25
+ ### Database e Schema
26
+
27
+ | EN | PT-BR / Significado |
28
+ |---|---|
29
+ | **`schemas/`** | Pasta `supabase/schemas/` — fonte da verdade declarative do schema. Editar aqui, depois `db diff` gera migration. |
30
+ | **`migrations/`** | Pasta `supabase/migrations/` — arquivos `YYYYMMDDHHmmss_<name>.sql` versionados em git. |
31
+ | **`db diff`** | `supabase db diff -f <name>` — gera migration a partir do diff entre schemas/ declarado e DB local atual. |
32
+ | **`db reset`** | `supabase db reset` — recria DB local do zero + reaplica todas as migrations + seeds. |
33
+ | **`search_path`** | Caminho de busca de schema do Postgres. **Sempre** `set search_path = ''` em funções. |
34
+ | **schema-qualified** | Referência a objeto com schema explícito: `public.tasks` em vez de só `tasks`. Obrigatório quando `search_path = ''`. |
35
+ | **`SECURITY INVOKER`** | Função executa com permissões de quem chamou. Default obrigatório. |
36
+ | **`SECURITY DEFINER`** | Função executa com permissões do owner. Apenas com justificativa documentada. |
37
+ | **`IMMUTABLE`** | Função sempre retorna o mesmo para os mesmos inputs (sem consultar DB). |
38
+ | **`STABLE`** | Função consulta DB mas não modifica — mesmo resultado dentro de uma transação. |
39
+ | **`VOLATILE`** | Default. Função pode retornar valores diferentes ou ter side effects. |
40
+
41
+ ### Realtime
42
+
43
+ | EN | PT-BR / Significado |
44
+ |---|---|
45
+ | **broadcast** | Mensagens custom via WebSocket — tipo recomendado em 2026 (substitui `postgres_changes` em apps novos). |
46
+ | **postgres_changes** | Pattern legado de receber mudanças do DB via stream. Single-threaded — não escala. **Migrar para broadcast.** |
47
+ | **presence** | Tracking de "quem está online". Usar **com moderação** — só para presence real (online status, cursor de colaboração). |
48
+ | **channel** | Canal de comunicação — naming canônico `scope:entity:id` (ex: `room:123:messages`). |
49
+ | **private channel** | Canal autenticado — `private: true` + RLS sobre `realtime.messages`. **Default em produção.** |
50
+ | **`realtime.broadcast_changes`** | Função SQL para emitir broadcast de dentro do Postgres (de trigger). |
51
+ | **`realtime.send`** | Função SQL para emitir mensagem custom (não amarrada a tabela). |
52
+
53
+ ### Edge Functions
54
+
55
+ | EN | PT-BR / Significado |
56
+ |---|---|
57
+ | **Edge Function** | Função serverless Deno hospedada por Supabase. Roda perto do usuário. |
58
+ | **`Deno.serve`** | Built-in para HTTP server em Edge Functions (NÃO usar `serve` de `deno.land/std`). |
59
+ | **`EdgeRuntime.waitUntil`** | Permite tarefa background continuar após response retornar. |
60
+ | **`npm:` / `jsr:`** | Specifiers de import obrigatórios (sem bare specifiers). Ex: `import x from "npm:hono@4.6.7"`. |
61
+
62
+ ### Storage e Vector
63
+
64
+ | EN | PT-BR / Significado |
65
+ |---|---|
66
+ | **bucket** | Container de arquivos — público ou privado. Privado por default. |
67
+ | **signed URL** | URL temporária com expiration para download de arquivo privado. |
68
+ | **`storage.objects`** | Tabela onde Storage grava metadados — RLS aplicado aqui controla acesso. |
69
+ | **multi-tenant path isolation** | Pattern: prefixar path do arquivo com `auth.uid()` (`{user_id}/file.png`) para isolar por tenant via RLS. |
70
+ | **TUS** | Tus Resumable Upload protocol — upload em chunks resumable. |
71
+ | **pgvector** | Extensão Postgres para embeddings/similarity search. |
72
+ | **HNSW** | Hierarchical Navigable Small World — index para vector. **Recall melhor.** Default em 2026. |
73
+ | **IVFFlat** | Inverted File Flat — index alternativo. Mais rápido com volumes grandes mas recall menor. |
74
+ | **`<=>`** | Operador cosine distance em pgvector. |
75
+ | **`<#>`** | Operador inner product em pgvector. |
76
+ | **`<->`** | Operador L2 (euclidean) distance em pgvector. |
77
+
78
+ ### Background Jobs
79
+
80
+ | EN | PT-BR / Significado |
81
+ |---|---|
82
+ | **`pg_cron`** | Extensão para jobs cron dentro do Postgres. Schedule SQL/funções. |
83
+ | **`pgmq`** | Postgres Message Queue — extensão de queues. Requer Postgres 15.6.1.143+. |
84
+ | **`pg_net`** | Extensão para requests HTTP de dentro do Postgres. v0.10.0+. |
85
+
86
+ ### Branching
87
+
88
+ | EN | PT-BR / Significado |
89
+ |---|---|
90
+ | **branch database** | Cópia preview do DB de produção para feature branches. |
91
+ | **persistent branch** | Branch que sobrevive entre PRs (staging long-lived). |
92
+ | **preview branch** | Branch criado para PR específico — destruído ao merge. |
93
+
94
+ ---
95
+
96
+ ## (b) Comandos CLI canônicos
97
+
98
+ ```bash
99
+ # Schema declarative
100
+ supabase stop # parar containers (necessário antes de db diff)
101
+ supabase db diff -f <name> # gera migration de schemas/ → migrations/
102
+ supabase db reset # reset local + reaplica migrations + seeds
103
+ supabase db push # aplica migrations não aplicadas no DB remote
104
+ supabase db pull # pulla mudanças remote → cria migration local
105
+
106
+ # Migrations
107
+ supabase migration new <name> # cria migration vazia com timestamp UTC
108
+
109
+ # Edge Functions
110
+ supabase functions new <name> # cria boilerplate de Edge Function
111
+ supabase functions deploy <name> # deploy para Supabase
112
+ supabase functions invoke <name> --body '{}' # invoca localmente
113
+
114
+ # Tipos
115
+ supabase gen types typescript --local > types/db.ts # gera tipos do schema local
116
+ supabase gen types typescript --linked > types/db.ts # gera tipos do remote linked
117
+
118
+ # Project lifecycle
119
+ supabase init # inicializa supabase/ no projeto
120
+ supabase start # sobe stack local (Postgres + Studio + Auth + ...)
121
+ supabase stop # derruba stack local
122
+ supabase status # status dos containers locais
123
+ supabase link --project-ref <ref> # linka projeto local com remote
124
+
125
+ # Branching
126
+ supabase branches create <name> # cria preview branch
127
+ supabase branches list # lista branches
128
+ supabase branches delete <name> # deleta branch (importante para custo!)
129
+
130
+ # Secrets
131
+ supabase secrets set --env-file .env.production # setar secrets em remote
132
+ supabase secrets list # listar (sem revelar valores)
133
+ ```
134
+
135
+ ---
136
+
137
+ ## (c) Patterns canônicos consolidados
138
+
139
+ ### Pattern: `(select auth.uid())` wrapper em RLS
140
+ - Sem `(select)`: degradação até **1000×** em queries com filtro RLS
141
+ - Detalhes: [supabase-rls-policies](../supabase-rls-policies/SKILL.md)
142
+
143
+ ### Pattern: `set search_path = ''` em funções
144
+ - Sem isso: vulnerável a hijack de schema via `search_path` manipulation
145
+ - Detalhes: [supabase-database-functions](../supabase-database-functions/SKILL.md)
146
+
147
+ ### Pattern: `getAll`/`setAll` cookies em SSR (Next.js)
148
+ - Pacote `@supabase/ssr` — **NUNCA** `@supabase/auth-helpers-nextjs` (deprecated)
149
+ - Detalhes: [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md)
150
+
151
+ ### Pattern: `cron → pgmq → Edge Function` (background jobs)
152
+ - Schedule via `pg_cron` → enqueue em `pgmq` → consumir e disparar `pg_net.http_post()` para Edge Function
153
+ - Sem dep externa (Inngest/Trigger.dev) — tudo dentro de Supabase
154
+ - Detalhes: [supabase-cron-queues](../supabase-cron-queues/SKILL.md)
155
+
156
+ ### Pattern: RAG with permissions (similarity + RLS)
157
+ - Embeddings em coluna vector + RLS policy filtrando por `user_id` ou `org_id`
158
+ - Sem RLS, qualquer cliente vê embeddings de todos os tenants
159
+ - Detalhes: [supabase-pgvector-rag](../supabase-pgvector-rag/SKILL.md)
160
+
161
+ ### Pattern: multi-tenant path isolation em Storage
162
+ - Path do arquivo prefixado com `auth.uid()` ou `org_id`: `{user_id}/avatar.png`, `{org_id}/docs/file.pdf`
163
+ - RLS sobre `storage.objects` valida que o cliente acessa apenas o próprio prefixo
164
+ - Detalhes: [supabase-storage](../supabase-storage/SKILL.md)
165
+
166
+ ### Pattern: declarative-first → diff → migration
167
+ - Editar schemas em `supabase/schemas/*.sql`
168
+ - Rodar `supabase stop && supabase db diff -f <name>` para gerar migration em `supabase/migrations/`
169
+ - Revisar migration manualmente antes de aplicar
170
+ - Detalhes: [supabase-declarative-schema](../supabase-declarative-schema/SKILL.md)
171
+
172
+ ### Pattern: `private: true` em Realtime channels
173
+ - Default em produção (2026) — desabilita acesso anônimo
174
+ - Requer RLS sobre `realtime.messages` para SELECT (read) e INSERT (write)
175
+ - Detalhes: [supabase-realtime](../supabase-realtime/SKILL.md)
176
+
177
+ ### Pattern: schema-qualified em Edge Functions chamando Supabase
178
+ - Function consulta `public.tasks` (não `tasks`) quando usar service-role client
179
+ - Combina com `set search_path = ''` em DB functions chamadas via RPC
180
+ - Detalhes: [supabase-edge-functions](../supabase-edge-functions/SKILL.md)
@@ -0,0 +1,260 @@
1
+ ---
2
+ name: supabase-auth-ssr
3
+ description: Use ao bootstrap Next.js v16 + Supabase Auth — @supabase/ssr, getAll/setAll APENAS, NUNCA auth-helpers-nextjs, proxy completo com getUser e redirects.
4
+ ---
5
+
6
+ # Supabase — Auth SSR (Next.js v16+)
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando bootstrap ou auditar autenticação Supabase em Next.js v16+ (App Router) com SSR. Trigger phrases:
11
+
12
+ - "Next.js + Supabase auth"
13
+ - "@supabase/ssr"
14
+ - "createServerClient", "createBrowserClient"
15
+ - "middleware.ts auth", "proxy auth"
16
+ - "cookies getAll setAll"
17
+ - "Supabase auth Next.js v16"
18
+
19
+ ## Regras absolutas
20
+
21
+ **WARNING — NEVER use auth-helpers-nextjs.** O pacote `@supabase/auth-helpers-nextjs` está **DEPRECATED** e **quebra em Next.js v16+** (cookies API mudou). **SEMPRE** use `@supabase/ssr`.
22
+
23
+ **Outras regras:**
24
+
25
+ - **Padrão exclusivo `getAll`/`setAll`** para cookies — **NUNCA** `get`/`set`/`remove` individuais. Os métodos individuais não funcionam corretamente com middleware/Server Actions em Next.js v16+.
26
+ - **Browser client e Server client são distintos:**
27
+ - Browser (`createBrowserClient`) → para Client Components ("use client")
28
+ - Server (`createServerClient`) → para Server Components, Route Handlers, Server Actions
29
+ - **Middleware (`middleware.ts`) obrigatório** para refresh de sessão SSR. Deve chamar `supabase.auth.getUser()` em cada request.
30
+ - **Auth method order** — após `createServerClient` mas **ANTES** de `getUser()`, NÃO chamar nada que produza response intermediário. Os cookies precisam fluir corretamente.
31
+ - **`NEXT_PUBLIC_*` apenas para anon key** (`NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`). **NUNCA** `NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY` — service_role bypassa RLS e seria exposto ao cliente (anti-pitfall B6).
32
+ - **Single serverClient factory** — não criar múltiplos clients em layouts (race condition na refresh de token — B13).
33
+
34
+ ## Patterns canônicos
35
+
36
+ ### Browser client — Client Components
37
+
38
+ ```ts
39
+ // utils/supabase/client.ts — PT-BR: client para Client Components
40
+ import { createBrowserClient } from '@supabase/ssr'
41
+
42
+ export function createClient() {
43
+ return createBrowserClient(
44
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
45
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
46
+ )
47
+ }
48
+ ```
49
+
50
+ ```tsx
51
+ // PT-BR: uso em Client Component
52
+ 'use client'
53
+ import { createClient } from '@/utils/supabase/client'
54
+
55
+ export function LogoutButton() {
56
+ const supabase = createClient()
57
+ return <button onClick={() => supabase.auth.signOut()}>Sair</button>
58
+ }
59
+ ```
60
+
61
+ ### Server client — Server Components / Server Actions
62
+
63
+ ```ts
64
+ // utils/supabase/server.ts — PT-BR: client para Server Components/Actions/Route Handlers
65
+ import { createServerClient } from '@supabase/ssr'
66
+ import { cookies } from 'next/headers'
67
+
68
+ export async function createClient() {
69
+ const cookieStore = await cookies()
70
+
71
+ return createServerClient(
72
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
73
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
74
+ {
75
+ cookies: {
76
+ getAll() {
77
+ return cookieStore.getAll()
78
+ },
79
+ setAll(cookiesToSet) {
80
+ try {
81
+ cookiesToSet.forEach(({ name, value, options }) =>
82
+ cookieStore.set(name, value, options)
83
+ )
84
+ } catch {
85
+ // PT-BR: ok ignorar — chamado em Server Component (sem permissão de set)
86
+ // se proxy faz refresh, sessão fica saudável mesmo sem set aqui
87
+ }
88
+ },
89
+ },
90
+ }
91
+ )
92
+ }
93
+ ```
94
+
95
+ ```tsx
96
+ // PT-BR: uso em Server Component
97
+ import { createClient } from '@/utils/supabase/server'
98
+
99
+ export default async function Dashboard() {
100
+ const supabase = await createClient()
101
+ const { data: { user } } = await supabase.auth.getUser()
102
+ if (!user) return <p>Não autenticado</p>
103
+ return <p>Olá, {user.email}</p>
104
+ }
105
+ ```
106
+
107
+ ### Middleware — refresh de sessão (obrigatório)
108
+
109
+ ```ts
110
+ // middleware.ts — PT-BR: proxy obrigatório para refresh de sessão SSR
111
+ import { createServerClient } from '@supabase/ssr'
112
+ import { NextResponse, type NextRequest } from 'next/server'
113
+
114
+ export async function middleware(request: NextRequest) {
115
+ let supabaseResponse = NextResponse.next({ request })
116
+
117
+ const supabase = createServerClient(
118
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
119
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
120
+ {
121
+ cookies: {
122
+ getAll() {
123
+ return request.cookies.getAll()
124
+ },
125
+ setAll(cookiesToSet) {
126
+ cookiesToSet.forEach(({ name, value }) =>
127
+ request.cookies.set(name, value)
128
+ )
129
+ supabaseResponse = NextResponse.next({ request })
130
+ cookiesToSet.forEach(({ name, value, options }) =>
131
+ supabaseResponse.cookies.set(name, value, options)
132
+ )
133
+ },
134
+ },
135
+ }
136
+ )
137
+
138
+ // PT-BR: ATENÇÃO — não execute código entre createServerClient e getUser()
139
+ // (qualquer cookie set/get fora desse path quebra refresh silencioso)
140
+ const { data: { user } } = await supabase.auth.getUser()
141
+
142
+ // PT-BR: redirect para /login se sem user
143
+ if (!user && !request.nextUrl.pathname.startsWith('/login')) {
144
+ const url = request.nextUrl.clone()
145
+ url.pathname = '/login'
146
+ return NextResponse.redirect(url)
147
+ }
148
+
149
+ // PT-BR: IMPORTANTE — sempre retornar supabaseResponse (cookies precisam fluir)
150
+ return supabaseResponse
151
+ }
152
+
153
+ export const config = {
154
+ matcher: [
155
+ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
156
+ ],
157
+ }
158
+ ```
159
+
160
+ ### Login com email/senha (Server Action)
161
+
162
+ ```ts
163
+ // app/login/actions.ts
164
+ 'use server'
165
+ import { createClient } from '@/utils/supabase/server'
166
+ import { redirect } from 'next/navigation'
167
+
168
+ export async function loginAction(formData: FormData) {
169
+ const supabase = await createClient()
170
+ const { error } = await supabase.auth.signInWithPassword({
171
+ email: formData.get('email') as string,
172
+ password: formData.get('password') as string,
173
+ })
174
+ if (error) return { error: error.message }
175
+ redirect('/dashboard')
176
+ }
177
+ ```
178
+
179
+ ## Anti-patterns
180
+
181
+ ### Anti-pattern 1: Importar de `@supabase/auth-helpers-nextjs`
182
+
183
+ **Errado:**
184
+ ```ts
185
+ import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
186
+ import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
187
+ ```
188
+
189
+ **Por quê:** `@supabase/auth-helpers-nextjs` está **DEPRECATED**. Quebra em Next.js v16+ (cookies API mudou). Não recebe mais updates de segurança.
190
+
191
+ **Certo:**
192
+ ```ts
193
+ import { createServerClient, createBrowserClient } from '@supabase/ssr'
194
+ ```
195
+
196
+ ### Anti-pattern 2: `cookies: { get, set, remove }` (individual)
197
+
198
+ **Errado:**
199
+ ```ts
200
+ {
201
+ cookies: {
202
+ get(name: string) { return cookieStore.get(name) },
203
+ set(name: string, value: string) { cookieStore.set(name, value) },
204
+ remove(name: string) { cookieStore.remove(name) },
205
+ }
206
+ }
207
+ ```
208
+
209
+ **Por quê:** cookie methods individuais quebram em middleware quando há múltiplos cookies sendo set/get em uma única request. `getAll`/`setAll` são chamados em batch e preservam ordem.
210
+
211
+ **Certo:** ver pattern "Server client" e "Middleware" acima.
212
+
213
+ ### Anti-pattern 3: `NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY`
214
+
215
+ **Errado:**
216
+ ```bash
217
+ # .env.local
218
+ NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJhbG...
219
+ ```
220
+
221
+ **Por quê:** `NEXT_PUBLIC_*` é **público** no client bundle. `service_role` bypassa RLS — vazamento = banco totalmente exposto.
222
+
223
+ **Certo:**
224
+ ```bash
225
+ # .env.local
226
+ NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
227
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbG... # ok público
228
+ SUPABASE_SERVICE_ROLE_KEY=eyJhbG... # privado — sem NEXT_PUBLIC_
229
+ ```
230
+
231
+ Use `service_role` apenas em código server-side que NUNCA é embarcado no bundle client (Route Handlers, Server Actions com `'use server'`, Edge Functions).
232
+
233
+ ### Anti-pattern 4: Múltiplos serverClient em layouts (race condition)
234
+
235
+ **Errado:**
236
+ ```tsx
237
+ // app/layout.tsx
238
+ const supabase1 = await createClient()
239
+ const { user } = await supabase1.auth.getUser()
240
+ // ...
241
+ // app/(dashboard)/layout.tsx
242
+ const supabase2 = await createClient() // ⚠ outro client na mesma request
243
+ const { user } = await supabase2.auth.getUser()
244
+ ```
245
+
246
+ **Por quê:** múltiplos `createServerClient` na mesma request podem corromper cookies de refresh de token. Issue [supabase/ssr#68](https://github.com/supabase/ssr/issues/68) — race condition documentada.
247
+
248
+ **Certo:** middleware faz o refresh **uma vez por request**. Layouts apenas leem o user via `getUser()` que retorna cached:
249
+ ```tsx
250
+ // app/layout.tsx — middleware já fez o refresh
251
+ const supabase = await createClient()
252
+ const { data: { user } } = await supabase.auth.getUser()
253
+ ```
254
+
255
+ ## Ver também
256
+
257
+ - [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — RLS aplicado quando user autenticado consulta tabelas
258
+ - [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions usando service_role server-side
259
+ - [supabase-realtime](../supabase-realtime/SKILL.md) — Realtime exige usuário autenticado para canais privados
260
+ - [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN + comandos CLI