perf: cross-system snappiness batch (middleware, loading states, bundle) (#909)

* perf: cross-system snappiness batch (middleware, loading states, bundle)

Middleware: resolve the active company at most once per request and run
the user_preferences + first-membership queries in parallel, cutting 1-2
sequential DB round trips from every authenticated page load.

Loading states: add loading.tsx skeletons for the six highest-traffic
dashboard routes, render the real salary page header during load instead
of a full-page skeleton, replace the blank fallback={null} Suspense
flashes on customers/articles, and reshape the settings skeleton to
match the actual form layout.

Bundle: defer recharts chart components via next/dynamic on the KPI page
and report views, replace the import page's framer-motion marching-ants
border with a CSS keyframe, and enable optimizePackageImports for
recharts/date-fns/framer-motion.

Transactions: extract the potential-match lookups into a shared parallel
helper; a single-query PostgREST embed is blocked until the
potential_supplier_invoice_id FK exists in prod (see DECISIONS.md).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(transactions): log potential-match query failures instead of dropping them

A DB error in the invoice/supplier-invoice hint lookups previously
surfaced as "no potential match"; log it so failures are diagnosable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-06 16:16:33 +02:00
committed by GitHub
co-authored by Claude Opus 4.7
parent cdac1808c9
commit 573feea890
18 changed files with 361 additions and 86 deletions
+34 -18
View File
@@ -171,6 +171,13 @@ export async function updateSession(request: NextRequest) {
return supabaseResponse
}
// Resolve the active company at most once per request: both the MFA
// enrollment gate and the company-context block below need it, and the
// resolution costs DB round trips.
let resolvedCompany: { companyId: string | null; locale: string | null } | null = null
const resolveCompanyOnce = async () =>
(resolvedCompany ??= await resolveCompanyForMiddleware(supabase, user.id, request))
// MFA enforcement (application-side only, not RLS)
if (shouldEnforceMfa(user)) {
const { data: aal } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
@@ -182,7 +189,7 @@ export async function updateSession(request: NextRequest) {
// MFA required but user has no factor enrolled yet → force enrollment
// Skip for users with no companies (still setting up)
const { companyId: companyIdForMfa } = await resolveCompanyForMiddleware(supabase, user.id, request)
const { companyId: companyIdForMfa } = await resolveCompanyOnce()
if (companyIdForMfa) {
const { data: factors } = await supabase.auth.mfa.listFactors()
const hasVerifiedFactor = factors?.totp?.some(f => f.status === 'verified')
@@ -199,7 +206,7 @@ export async function updateSession(request: NextRequest) {
// Company context resolution
const cookieCompanyId = request.cookies.get('gnubok-company-id')?.value
const { companyId, locale: dbLocale } = await resolveCompanyForMiddleware(supabase, user.id, request)
const { companyId, locale: dbLocale } = await resolveCompanyOnce()
// If the cookie pointed at a company we can no longer resolve (e.g.
// archived), clear it so the browser stops sending it.
@@ -293,16 +300,34 @@ async function resolveCompanyForMiddleware(
userId: string,
_request: NextRequest
): Promise<{ companyId: string | null; locale: string | null }> {
// 1. user_preferences (authoritative)
const { data: prefs } = await supabase
.from('user_preferences')
.select('active_company_id, locale')
.eq('user_id', userId)
.maybeSingle()
// 1. user_preferences (authoritative) + first membership, fetched in
// parallel: the fallback query result doubles as validation when the
// preferred company happens to be the first membership, which is the
// common single-company case, so most requests pay one round trip
// instead of two sequential ones.
const [{ data: prefs }, { data: firstCompany }] = await Promise.all([
supabase
.from('user_preferences')
.select('active_company_id, locale')
.eq('user_id', userId)
.maybeSingle(),
supabase
.from('company_members')
.select('company_id, companies!inner(archived_at)')
.eq('user_id', userId)
.is('companies.archived_at', null)
.order('created_at', { ascending: true })
.limit(1)
.maybeSingle(),
])
const locale = (prefs?.locale as string | undefined) ?? null
if (prefs?.active_company_id) {
if (prefs.active_company_id === firstCompany?.company_id) {
return { companyId: firstCompany.company_id, locale }
}
const { data: membership } = await supabase
.from('company_members')
.select('company_id, companies!inner(archived_at)')
@@ -314,16 +339,7 @@ async function resolveCompanyForMiddleware(
if (membership) return { companyId: membership.company_id, locale }
}
// 2. Fallback: first non-archived membership by created_at
const { data: firstCompany } = await supabase
.from('company_members')
.select('company_id, companies!inner(archived_at)')
.eq('user_id', userId)
.is('companies.archived_at', null)
.order('created_at', { ascending: true })
.limit(1)
.maybeSingle()
// 2. Fallback: first non-archived membership (already fetched above)
if (!firstCompany) return { companyId: null, locale }
// Write the fallback back to user_preferences so future RLS lookups