From 2e7d7a628222ab839336011a3e836f634303e941 Mon Sep 17 00:00:00 2001 From: Arnas Donauskas Date: Thu, 16 Apr 2026 20:05:00 +0300 Subject: [PATCH] perf: use getSession() instead of getUser() in client components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major performance improvement. Every client component that needed the current user was calling supabase.auth.getUser(), which makes a network round-trip to Supabase to validate the JWT. On a page that fetches data conditional on the user, the flow was: Page mount -> getUser() [network ~150-400ms] -> query data [network ~150-400ms] -> render That's 2 serial round-trips before the page renders. supabase.auth.getSession() reads the session from localStorage with NO network call. Using it in client code cuts per-page load by one full round-trip (~150-400ms depending on region latency). Replaced in 10 client files: - src/app/(dashboard)/inbox/page.tsx - src/app/(dashboard)/pipelines/page.tsx - src/components/contacts/contact-detail-view.tsx - src/components/contacts/contact-form.tsx - src/components/contacts/import-modal.tsx - src/components/inbox/contact-sidebar.tsx - src/components/pipelines/deal-form.tsx - src/components/pipelines/pipeline-settings.tsx - src/components/settings/tag-manager.tsx - src/components/settings/template-manager.tsx - src/components/settings/whatsapp-config.tsx NOT changed (correctly continue using getUser): - src/middleware.ts (server-side, needs JWT validation) - src/app/api/whatsapp/*/route.ts (server-side, security-critical) - src/hooks/use-auth.ts (PR #4 handles this file) Security note: getSession() trusts the client-stored session. For API routes and middleware that make authorization decisions, we still use getUser() which verifies with Supabase. Client components only use the session's user ID to filter RLS-protected queries — the RLS on the database is the actual authorization boundary, so a forged user ID in the browser wouldn't bypass server-side policies. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/(dashboard)/inbox/page.tsx | 5 +++-- src/app/(dashboard)/pipelines/page.tsx | 5 +++-- src/components/contacts/contact-detail-view.tsx | 5 +++-- src/components/contacts/contact-form.tsx | 5 +++-- src/components/contacts/import-modal.tsx | 5 +++-- src/components/inbox/contact-sidebar.tsx | 5 +++-- src/components/pipelines/deal-form.tsx | 5 +++-- src/components/pipelines/pipeline-settings.tsx | 5 +++-- src/components/settings/tag-manager.tsx | 4 ++-- src/components/settings/template-manager.tsx | 4 ++-- src/components/settings/whatsapp-config.tsx | 4 ++-- 11 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/app/(dashboard)/inbox/page.tsx b/src/app/(dashboard)/inbox/page.tsx index a826b11..8488883 100644 --- a/src/app/(dashboard)/inbox/page.tsx +++ b/src/app/(dashboard)/inbox/page.tsx @@ -25,8 +25,9 @@ export default function InboxPage() { const checkConnection = async () => { const supabase = createClient(); const { - data: { user }, - } = await supabase.auth.getUser(); + data: { session }, + } = await supabase.auth.getSession(); + const user = session?.user; if (!user) return; diff --git a/src/app/(dashboard)/pipelines/page.tsx b/src/app/(dashboard)/pipelines/page.tsx index ba2d69c..f249825 100644 --- a/src/app/(dashboard)/pipelines/page.tsx +++ b/src/app/(dashboard)/pipelines/page.tsx @@ -85,8 +85,9 @@ export default function PipelinesPage() { setCreating(true); const { - data: { user }, - } = await supabase.auth.getUser(); + data: { session }, + } = await supabase.auth.getSession(); + const user = session?.user; if (!user) { setCreating(false); return; diff --git a/src/components/contacts/contact-detail-view.tsx b/src/components/contacts/contact-detail-view.tsx index 382a27d..f63e6b5 100644 --- a/src/components/contacts/contact-detail-view.tsx +++ b/src/components/contacts/contact-detail-view.tsx @@ -223,8 +223,9 @@ export function ContactDetailView({ setSavingNote(true); const { - data: { user }, - } = await supabase.auth.getUser(); + data: { session }, + } = await supabase.auth.getSession(); + const user = session?.user; if (!user) { toast.error('Not authenticated'); setSavingNote(false); diff --git a/src/components/contacts/contact-form.tsx b/src/components/contacts/contact-form.tsx index d7b8925..f1465b5 100644 --- a/src/components/contacts/contact-form.tsx +++ b/src/components/contacts/contact-form.tsx @@ -87,8 +87,9 @@ export function ContactForm({ try { const { - data: { user }, - } = await supabase.auth.getUser(); + data: { session }, + } = await supabase.auth.getSession(); + const user = session?.user; if (!user) throw new Error('Not authenticated'); let contactId = contact?.id; diff --git a/src/components/contacts/import-modal.tsx b/src/components/contacts/import-modal.tsx index 945245e..899b7f7 100644 --- a/src/components/contacts/import-modal.tsx +++ b/src/components/contacts/import-modal.tsx @@ -123,8 +123,9 @@ export function ImportModal({ open, onOpenChange, onImported }: ImportModalProps try { const { - data: { user }, - } = await supabase.auth.getUser(); + data: { session }, + } = await supabase.auth.getSession(); + const user = session?.user; if (!user) throw new Error('Not authenticated'); let imported = 0; diff --git a/src/components/inbox/contact-sidebar.tsx b/src/components/inbox/contact-sidebar.tsx index b1b2daf..65518a8 100644 --- a/src/components/inbox/contact-sidebar.tsx +++ b/src/components/inbox/contact-sidebar.tsx @@ -84,8 +84,9 @@ export function ContactSidebar({ contact }: ContactSidebarProps) { const supabase = createClient(); const { - data: { user }, - } = await supabase.auth.getUser(); + data: { session }, + } = await supabase.auth.getSession(); + const user = session?.user; const { data, error } = await supabase .from("contact_notes") diff --git a/src/components/pipelines/deal-form.tsx b/src/components/pipelines/deal-form.tsx index eb5e1d8..20df6a4 100644 --- a/src/components/pipelines/deal-form.tsx +++ b/src/components/pipelines/deal-form.tsx @@ -96,8 +96,9 @@ export function DealForm({ await supabase.from("deals").update(payload).eq("id", deal.id); } else { const { - data: { user }, - } = await supabase.auth.getUser(); + data: { session }, + } = await supabase.auth.getSession(); + const user = session?.user; if (user) { await supabase .from("deals") diff --git a/src/components/pipelines/pipeline-settings.tsx b/src/components/pipelines/pipeline-settings.tsx index 1ec78fd..0cac7ca 100644 --- a/src/components/pipelines/pipeline-settings.tsx +++ b/src/components/pipelines/pipeline-settings.tsx @@ -92,8 +92,9 @@ export function PipelineSettings({ async function handleAddStage() { if (!newStageName.trim()) return; const { - data: { user }, - } = await supabase.auth.getUser(); + data: { session }, + } = await supabase.auth.getSession(); + const user = session?.user; if (!user) return; const { data } = await supabase diff --git a/src/components/settings/tag-manager.tsx b/src/components/settings/tag-manager.tsx index 88ae173..9318775 100644 --- a/src/components/settings/tag-manager.tsx +++ b/src/components/settings/tag-manager.tsx @@ -49,7 +49,7 @@ export function TagManager() { async function fetchTags() { try { setLoading(true); - const { data: { user } } = await supabase.auth.getUser(); + const { data: { session } } = await supabase.auth.getSession(); const user = session?.user; if (!user) return; const { data, error } = await supabase @@ -76,7 +76,7 @@ export function TagManager() { try { setSaving(true); - const { data: { user } } = await supabase.auth.getUser(); + const { data: { session } } = await supabase.auth.getSession(); const user = session?.user; if (!user) { toast.error('Not authenticated'); return; diff --git a/src/components/settings/template-manager.tsx b/src/components/settings/template-manager.tsx index 47544de..89945a8 100644 --- a/src/components/settings/template-manager.tsx +++ b/src/components/settings/template-manager.tsx @@ -77,7 +77,7 @@ export function TemplateManager() { async function fetchTemplates() { try { setLoading(true); - const { data: { user } } = await supabase.auth.getUser(); + const { data: { session } } = await supabase.auth.getSession(); const user = session?.user; if (!user) return; const { data, error } = await supabase @@ -108,7 +108,7 @@ export function TemplateManager() { try { setSaving(true); - const { data: { user } } = await supabase.auth.getUser(); + const { data: { session } } = await supabase.auth.getSession(); const user = session?.user; if (!user) { toast.error('Not authenticated'); return; diff --git a/src/components/settings/whatsapp-config.tsx b/src/components/settings/whatsapp-config.tsx index 81cc2fc..9b7a8ad 100644 --- a/src/components/settings/whatsapp-config.tsx +++ b/src/components/settings/whatsapp-config.tsx @@ -56,7 +56,7 @@ export function WhatsAppConfig() { async function fetchConfig() { try { setLoading(true); - const { data: { user } } = await supabase.auth.getUser(); + const { data: { session } } = await supabase.auth.getSession(); const user = session?.user; if (!user) return; const { data, error } = await supabase @@ -97,7 +97,7 @@ export function WhatsAppConfig() { try { setSaving(true); - const { data: { user } } = await supabase.auth.getUser(); + const { data: { session } } = await supabase.auth.getSession(); const user = session?.user; if (!user) { toast.error('Not authenticated'); return;