Skip to main content
Contafy uses a hybrid data fetching strategy that combines Server Components for initial data loads with TanStack Query for client-side interactivity.

Data fetching strategy

Server Components for initial data

Server Components fetch data directly on the server before rendering: Benefits:
  • No client-side loading states on initial render
  • Better SEO (content in HTML)
  • Reduced client bundle size
  • Direct access to backend API
Example from app/dashboard/components/DashboardContent.tsx:50-57:
Key points:
  • Use Promise.all() to fetch data in parallel
  • No loading states needed (data ready before render)
  • Pass data as props to child components
  • Great for SEO and initial page load

TanStack Query for client-side data

Client Components use TanStack Query for interactive data: Benefits:
  • Automatic caching and revalidation
  • Background refetching
  • Optimistic updates
  • Request deduplication
  • Built-in loading/error states
Example from app/dashboard/expenses/components/ExpensesListContent.tsx:
Key points:
  • Must be in a Client Component ('use client')
  • Provides loading and error states
  • Automatically caches results
  • Revalidates on window focus (configurable)

API client architecture

Contafy has two API clients: one for Server Components and one for Client Components.

Server API client

Location: lib/api/server-client.ts Used in: Server Components only Implementation:
Key characteristics:
  • Reads cookies directly via next/headers
  • Cannot refresh tokens (can’t modify cookies)
  • Redirects to login on 401
  • Throws errors for bad responses
Example usage:

Client API client

Location: lib/api/client.ts Used in: Client Components only Implementation:
Key characteristics:
  • Uses /backend proxy to avoid CORS
  • Automatically refreshes tokens on 401
  • Retries failed requests with new token
  • Redirects to login if refresh fails
Example usage:

Token refresh mechanism

Contafy implements automatic token refresh to keep users logged in.

How it works

  1. User makes authenticated request
  2. Backend responds with 401 (token expired)
  3. Client automatically calls /api/auth/refresh
  4. Backend validates refresh token, returns new access token
  5. Client retries original request with new token
  6. User stays logged in without interruption

Implementation details

Refresh function (lib/api/client.ts:49-95):
Key features:
  • Prevents duplicate refresh requests with flag
  • Shares single refresh promise across requests
  • Redirects to login if refresh fails
  • Transparent to calling code

Token storage

Tokens are stored in httpOnly cookies for security:
  • Access token: Short-lived (15 minutes)
  • Refresh token: Long-lived (7 days)
Security benefits:
  • Cannot be accessed by JavaScript (XSS protection)
  • Automatically sent with requests
  • Secure flag in production (HTTPS only)

API proxy configuration

To avoid CORS issues, Contafy proxies API requests through Next.js. Configuration (next.config.ts:5-12):
How it works:
  1. Client requests /backend/api/invoices
  2. Next.js rewrites to http://localhost:3001/api/invoices
  3. Backend processes request
  4. Next.js forwards response to client
Benefits:
  • No CORS headers needed
  • Simplified client code
  • Works in all environments

TanStack Query configuration

Setup (components/providers/ReactQueryProvider.tsx:11-24):

Query keys

Query keys uniquely identify cached data:
Best practices:
  • Include all parameters that affect the data
  • Use consistent ordering
  • Use arrays for hierarchical keys

Query functions

Query functions fetch the actual data:
Requirements:
  • Must return a Promise
  • Should use API client functions
  • Can access query key parameters

Cache invalidation

Invalidate queries when data changes:

Mutations

Mutations modify server data:

Data flow examples

Server Component data flow

  1. User navigates to /dashboard
  2. Server Component renders
  3. Server fetches data via serverApiClient
  4. Server renders HTML with data
  5. Client receives fully rendered page
  6. No loading state on client

Client Component data flow

  1. User clicks “Ver más” button
  2. Client Component renders
  3. Shows loading spinner
  4. Client fetches data via apiClient + TanStack Query
  5. Data cached by TanStack Query
  6. Component re-renders with data
  7. Cache reused on subsequent renders

Error handling

API errors

Both API clients throw ApiError/ServerApiError:

TanStack Query error handling

Performance optimizations

Parallel data fetching

Fetch multiple resources in parallel:

Request deduplication

TanStack Query automatically deduplicates identical requests:

Prefetching

Prefetch data before it’s needed:
See Architecture for how this fits into the overall system.