[{"data":1,"prerenderedAt":192},["ShallowReactive",2],{"blog-blueprints/remix-supabase":3},{"id":4,"title":5,"body":6,"category":172,"date":173,"dateModified":173,"description":174,"draft":175,"extension":176,"faq":177,"featured":175,"headerVariant":178,"image":177,"keywords":177,"meta":179,"navigation":180,"ogDescription":181,"ogTitle":177,"path":182,"readTime":183,"schemaOrg":184,"schemaType":185,"seo":186,"sitemap":187,"stem":188,"tags":189,"twitterCard":190,"__hash__":191},"blog/blog/blueprints/remix-supabase.md","Remix + Supabase Security Blueprint",{"type":7,"value":8,"toc":161},"minimark",[9,20,23,29,34,49,53,62,66,75,79,84,87,90,93,96,99,113,149],[10,11,12],"blueprint-summary",{},[13,14,15,19],"p",{},[16,17,18],"strong",{},"To secure a Remix + Supabase stack,"," you need to: (1) create a Supabase client per request for proper cookie handling, (2) verify auth using getUser() in all loaders and actions, (3) enable RLS as defense-in-depth, (4) store sessions in cookies with proper Set-Cookie headers, and (5) use verified user IDs from auth (not form data). This blueprint covers Remix's server-first patterns with Supabase SSR.",[21,22],"blueprint-meta",{},[24,25,26],"tldr",{},[13,27,28],{},"Remix's loader/action pattern pairs well with Supabase. Key tasks: create Supabase client per request, use getUser() to verify auth in loaders and actions, enable RLS as defense-in-depth, and store session in cookies. Remix runs on the server, giving you more control than pure SPAs.",[30,31,33],"h2",{"id":32},"supabase-client-per-request-supabase-remix","Supabase Client Per Request Supabase Remix",[35,36,38],"code-block",{"label":37},"app/lib/supabase.server.ts",[39,40,45],"pre",{"className":41,"code":43,"language":44},[42],"language-text","import { createServerClient } from '@supabase/ssr'\n\nexport function createSupabaseClient(request: Request) {\n  const cookies = parse(request.headers.get('Cookie') ?? '')\n  const headers = new Headers()\n\n  const supabase = createServerClient(\n    process.env.SUPABASE_URL!,\n    process.env.SUPABASE_ANON_KEY!,\n    {\n      cookies: {\n        get(key) { return cookies[key] },\n        set(key, value, options) {\n          headers.append('Set-Cookie', serialize(key, value, options))\n        },\n        remove(key, options) {\n          headers.append('Set-Cookie', serialize(key, '', options))\n        },\n      },\n    }\n  )\n\n  return { supabase, headers }\n}\n","text",[46,47,43],"code",{"__ignoreMap":48},"",[30,50,52],{"id":51},"auth-in-loaders-remix","Auth in Loaders Remix",[35,54,56],{"label":55},"app/routes/dashboard.tsx",[39,57,60],{"className":58,"code":59,"language":44},[42],"export async function loader({ request }: LoaderFunctionArgs) {\n  const { supabase, headers } = createSupabaseClient(request)\n\n  const { data: { user }, error } = await supabase.auth.getUser()\n\n  if (error || !user) {\n    throw redirect('/login', { headers })\n  }\n\n  const { data: posts } = await supabase\n    .from('posts')\n    .select('*')\n    .eq('author_id', user.id)\n\n  return json({ posts }, { headers })\n}\n",[46,61,59],{"__ignoreMap":48},[30,63,65],{"id":64},"auth-in-actions-remix","Auth in Actions Remix",[35,67,69],{"label":68},"Secure action pattern",[39,70,73],{"className":71,"code":72,"language":44},[42],"export async function action({ request }: ActionFunctionArgs) {\n  const { supabase, headers } = createSupabaseClient(request)\n\n  const { data: { user } } = await supabase.auth.getUser()\n  if (!user) {\n    throw new Response('Unauthorized', { status: 401, headers })\n  }\n\n  const formData = await request.formData()\n\n  await supabase.from('posts').insert({\n    title: formData.get('title'),\n    author_id: user.id  // Use verified user ID\n  })\n\n  return redirect('/dashboard', { headers })\n}\n",[46,74,72],{"__ignoreMap":48},[30,76,78],{"id":77},"security-checklist","Security Checklist",[80,81,83],"h4",{"id":82},"pre-launch-checklist","Pre-Launch Checklist",[13,85,86],{},"RLS enabled on all tables",[13,88,89],{},"Auth verified in all loaders/actions",[13,91,92],{},"Supabase client created per request",[13,94,95],{},"Cookie headers properly returned",[13,97,98],{},"Environment variables configured",[100,101,102,108],"related-articles",{},[103,104],"related-card",{"description":105,"href":106,"title":107},"Alternative framework","/blog/blueprints/nextjs-supabase-vercel","Next.js + Supabase",[103,109],{"description":110,"href":111,"title":112},"Deep dive","/blog/guides/supabase","Supabase Security Guide",[114,115,116,121,124],"stack-comparison",{},[117,118,120],"h3",{"id":119},"alternative-stacks","Alternative Stacks",[13,122,123],{},"Consider these related blueprints:",[125,126,127,135,142],"ul",{},[128,129,130,134],"li",{},[131,132,133],"a",{"href":106},"Next.js + Supabase + Vercel"," - Next.js alternative",[128,136,137,141],{},[131,138,140],{"href":139},"/blog/blueprints/sveltekit-supabase","SvelteKit + Supabase"," - Svelte alternative",[128,143,144,148],{},[131,145,147],{"href":146},"/blog/blueprints/react-supabase","React + Supabase"," - Client-only SPA version",[150,151,154,158],"cta-box",{"href":152,"label":153},"/","Start Free Scan",[30,155,157],{"id":156},"building-with-this-stack","Building with this stack?",[13,159,160],{},"Scan for RLS and auth issues.",{"title":48,"searchDepth":162,"depth":162,"links":163},2,[164,165,166,167,171],{"id":32,"depth":162,"text":33},{"id":51,"depth":162,"text":52},{"id":64,"depth":162,"text":65},{"id":77,"depth":162,"text":78,"children":168},[169],{"id":119,"depth":170,"text":120},3,{"id":156,"depth":162,"text":157},"blueprints","2026-02-10","Security guide for Remix + Supabase stack. Configure RLS, handle auth in loaders and actions, use session cookies, and secure your full-stack Remix app.",false,"md",null,"purple",{},true,"Complete security configuration for Remix apps with Supabase.","/blog/blueprints/remix-supabase","10 min read","[object Object]","Article",{"title":5,"description":174},{"loc":182},"blog/blueprints/remix-supabase",[],"summary_large_image","Lp227hQIpHZkAKAExPYB-DZ_Tct7fLueOqhlIDG5ShE",1775843932128]