perf: use getSession() instead of getUser() in client components

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) <noreply@anthropic.com>
This commit is contained in:
Arnas Donauskas 2026-04-16 20:05:00 +03:00
parent 658d070811
commit 2e7d7a6282
No known key found for this signature in database
11 changed files with 30 additions and 22 deletions

View File

@ -25,8 +25,9 @@ export default function InboxPage() {
const checkConnection = async () => { const checkConnection = async () => {
const supabase = createClient(); const supabase = createClient();
const { const {
data: { user }, data: { session },
} = await supabase.auth.getUser(); } = await supabase.auth.getSession();
const user = session?.user;
if (!user) return; if (!user) return;

View File

@ -85,8 +85,9 @@ export default function PipelinesPage() {
setCreating(true); setCreating(true);
const { const {
data: { user }, data: { session },
} = await supabase.auth.getUser(); } = await supabase.auth.getSession();
const user = session?.user;
if (!user) { if (!user) {
setCreating(false); setCreating(false);
return; return;

View File

@ -223,8 +223,9 @@ export function ContactDetailView({
setSavingNote(true); setSavingNote(true);
const { const {
data: { user }, data: { session },
} = await supabase.auth.getUser(); } = await supabase.auth.getSession();
const user = session?.user;
if (!user) { if (!user) {
toast.error('Not authenticated'); toast.error('Not authenticated');
setSavingNote(false); setSavingNote(false);

View File

@ -87,8 +87,9 @@ export function ContactForm({
try { try {
const { const {
data: { user }, data: { session },
} = await supabase.auth.getUser(); } = await supabase.auth.getSession();
const user = session?.user;
if (!user) throw new Error('Not authenticated'); if (!user) throw new Error('Not authenticated');
let contactId = contact?.id; let contactId = contact?.id;

View File

@ -123,8 +123,9 @@ export function ImportModal({ open, onOpenChange, onImported }: ImportModalProps
try { try {
const { const {
data: { user }, data: { session },
} = await supabase.auth.getUser(); } = await supabase.auth.getSession();
const user = session?.user;
if (!user) throw new Error('Not authenticated'); if (!user) throw new Error('Not authenticated');
let imported = 0; let imported = 0;

View File

@ -84,8 +84,9 @@ export function ContactSidebar({ contact }: ContactSidebarProps) {
const supabase = createClient(); const supabase = createClient();
const { const {
data: { user }, data: { session },
} = await supabase.auth.getUser(); } = await supabase.auth.getSession();
const user = session?.user;
const { data, error } = await supabase const { data, error } = await supabase
.from("contact_notes") .from("contact_notes")

View File

@ -96,8 +96,9 @@ export function DealForm({
await supabase.from("deals").update(payload).eq("id", deal.id); await supabase.from("deals").update(payload).eq("id", deal.id);
} else { } else {
const { const {
data: { user }, data: { session },
} = await supabase.auth.getUser(); } = await supabase.auth.getSession();
const user = session?.user;
if (user) { if (user) {
await supabase await supabase
.from("deals") .from("deals")

View File

@ -92,8 +92,9 @@ export function PipelineSettings({
async function handleAddStage() { async function handleAddStage() {
if (!newStageName.trim()) return; if (!newStageName.trim()) return;
const { const {
data: { user }, data: { session },
} = await supabase.auth.getUser(); } = await supabase.auth.getSession();
const user = session?.user;
if (!user) return; if (!user) return;
const { data } = await supabase const { data } = await supabase

View File

@ -49,7 +49,7 @@ export function TagManager() {
async function fetchTags() { async function fetchTags() {
try { try {
setLoading(true); setLoading(true);
const { data: { user } } = await supabase.auth.getUser(); const { data: { session } } = await supabase.auth.getSession(); const user = session?.user;
if (!user) return; if (!user) return;
const { data, error } = await supabase const { data, error } = await supabase
@ -76,7 +76,7 @@ export function TagManager() {
try { try {
setSaving(true); setSaving(true);
const { data: { user } } = await supabase.auth.getUser(); const { data: { session } } = await supabase.auth.getSession(); const user = session?.user;
if (!user) { if (!user) {
toast.error('Not authenticated'); toast.error('Not authenticated');
return; return;

View File

@ -77,7 +77,7 @@ export function TemplateManager() {
async function fetchTemplates() { async function fetchTemplates() {
try { try {
setLoading(true); setLoading(true);
const { data: { user } } = await supabase.auth.getUser(); const { data: { session } } = await supabase.auth.getSession(); const user = session?.user;
if (!user) return; if (!user) return;
const { data, error } = await supabase const { data, error } = await supabase
@ -108,7 +108,7 @@ export function TemplateManager() {
try { try {
setSaving(true); setSaving(true);
const { data: { user } } = await supabase.auth.getUser(); const { data: { session } } = await supabase.auth.getSession(); const user = session?.user;
if (!user) { if (!user) {
toast.error('Not authenticated'); toast.error('Not authenticated');
return; return;

View File

@ -56,7 +56,7 @@ export function WhatsAppConfig() {
async function fetchConfig() { async function fetchConfig() {
try { try {
setLoading(true); setLoading(true);
const { data: { user } } = await supabase.auth.getUser(); const { data: { session } } = await supabase.auth.getSession(); const user = session?.user;
if (!user) return; if (!user) return;
const { data, error } = await supabase const { data, error } = await supabase
@ -97,7 +97,7 @@ export function WhatsAppConfig() {
try { try {
setSaving(true); setSaving(true);
const { data: { user } } = await supabase.auth.getUser(); const { data: { session } } = await supabase.auth.getSession(); const user = session?.user;
if (!user) { if (!user) {
toast.error('Not authenticated'); toast.error('Not authenticated');
return; return;