All Docs
FeaturesCalmony Sanctions MonitorUpdated March 12, 2026

Improving API Performance with Response Caching (PERF-12)

Improving API Performance with Response Caching (PERF-12)

Version: 0.1.80
Category: Performance — Server

Background

The sanctions screening platform exposes several API routes that serve data consumed by the monitoring dashboard and the OFSI list change feed. Until v0.1.80, every one of these routes carried export const dynamic = 'force-dynamic' — a Next.js directive that disables all framework-level caching — and none of them set Cache-Control headers on their HTTP responses.

The result: a new database query on every request, for every user, even when the underlying data hadn't changed in minutes or hours.

The Problem

force-dynamic applied universally

Next.js's force-dynamic is appropriate for routes that must always reflect real-time data (e.g. a live transaction feed). It is not appropriate for routes whose data changes on a predictable, infrequent schedule. Applying it globally adds unnecessary database load with no user-facing benefit.

No HTTP cache headers

Without Cache-Control headers, browsers, CDN edges, and reverse proxies cannot cache responses even for a few seconds. Every page load or component re-render that hits these endpoints results in a round-trip all the way to the database.

The Fix

/api/dashboard/stats

This endpoint returns aggregate counts (e.g. total screenings, alerts by status). The data is:

  • Per-user — each compliance user sees their own team's figures.
  • Infrequently changing — counts shift as screenings are processed, but stale-by-one-minute data is acceptable for a monitoring dashboard.
Cache-Control: private, max-age=60, stale-while-revalidate=300
DirectiveValueMeaning
privateResponse must not be stored by a shared cache (CDN, proxy). Prevents one user's stats leaking to another.
max-age60 sBrowser considers the response fresh for 1 minute.
stale-while-revalidate300 sBrowser may serve a stale response for up to 5 minutes while fetching a fresh one in the background.

/api/sanctions/changes

This endpoint lists changes to the OFSI consolidated sanctions list. The data is:

  • Not user-specific — all users see the same list.
  • Updated nightly — the automated sync runs once per day; intra-day staleness of a few minutes is inconsequential.
Cache-Control: public, max-age=300, stale-while-revalidate=3600
DirectiveValueMeaning
publicResponse may be stored by shared caches (CDN, reverse proxy). Safe because data is not user-specific.
max-age300 sResponse is considered fresh for 5 minutes.
stale-while-revalidate3600 sStale responses may be served for up to 1 hour while a background refresh is in flight.

Removing force-dynamic

Routes that do not require per-request dynamic behaviour have had export const dynamic = 'force-dynamic' removed, allowing Next.js's default caching strategy to apply.

What Stays the Same

  • Routes that genuinely serve real-time or write-path data retain force-dynamic as appropriate.
  • Per-user data is always marked private — no user's data is ever stored in a shared cache.
  • The nightly OFSI sync schedule is unchanged; /api/sanctions/changes will reflect the latest sync within the stale-while-revalidate window.

Summary

RouteCache ScopeFresh ForStale-While-Revalidate
/api/dashboard/statsPrivate (per-user)60 s300 s
/api/sanctions/changesPublic300 s3600 s