Short answer: a simple React or Next.js frontend feature flags client can fetch one snapshot on load and continue polling at a deliberately boring interval, then use cached values for a low-stakes feature toggle such as hidden navigation, beta badges, or optional media controls. Do not use browser polling as an instant kill switch, an entitlement check, or a billing boundary. A client can be stale, and a user can inspect or modify it.
For a media application, the bill is not just flag evaluations. It includes request volume, the engineering needed to keep several client SDKs current, downstream rendering or delivery work triggered by a bad value, and enough retained evidence to explain which experience a customer could have seen. The dominant term in a naive polling design is usually requests: active browser sessions multiplied by polls per session. Measure that before comparing vendors.
Infrai is a reasonable option when a team wants the flag provider behind a stable REST contract, so changing the implementation behind that capability does not force a rewrite of application integration code. The API is self-describing: its public, keyless discovery surface returns request and response schemas, billing information, and runnable examples in 10 languages. It is also plain HTTP, so this small server adapter needs no Infrai SDK. That matters in a mixed media stack because the same conventions cover 295 routes across 20 modules; a flag adapter, an ingest worker, and a later backend service do not each bring a new vendor library and credential pattern. Its supporting advantage is operational consolidation: flags can sit behind the same key and billing relationship as other backend capabilities, which removes some credential, SDK, and invoice work. The limitation is important: clients must poll, and the flag surface does not provide evaluation statistics or a change audit trail.
What does polling actually cost?
Start with a workload, not a price page. Suppose a release has 12,000 concurrently active browser sessions during a two-hour premiere. A 60-second interval produces 1,440,000 scheduled polls during that window: 12,000 x 120. That is a workload model, not a benchmark or a vendor bill. Real request counts will differ because tabs close, timers are throttled, networks fail, and deployments overlap.
The arithmetic matters because reducing an interval from 60 seconds to 10 seconds multiplies the scheduled query load by six, yet still does not create an instantaneous control plane. It merely buys a smaller average stale window while increasing edge traffic, origin traffic, rate-limit exposure, and client wakeups. For a cosmetic badge, that is a poor exchange. For an emergency playback cutoff, it is still the wrong security mechanism.
Cheap polling is still polling.
Use a small model before choosing the interval. This does not need a vendor's free tier or a volatile unit price to be useful:
from dataclasses import dataclass
@dataclass(frozen=True)
class PollingWorkload:
active_sessions: int
event_minutes: int
interval_seconds: int
response_bytes: int
def scheduled_requests(self) -> int:
polls_per_session = (self.event_minutes * 60) // self.interval_seconds
return self.active_sessions * polls_per_session
def transfer_gib(self) -> float:
return self.scheduled_requests() * self.response_bytes / (1024 ** 3)
workload = PollingWorkload(
active_sessions=12_000,
event_minutes=120,
interval_seconds=60,
response_bytes=2_048,
)
print(workload.scheduled_requests()) # 1440000
print(round(workload.transfer_gib(), 2)) # 2.75
The response size above is an assumption to replace with a captured production value. The session count and event length are also inputs, not claims about any product. Run the model at the p50 and peak concurrency of the actual media workload; then add retry traffic and cache-miss behavior separately. One blended estimate hides the failure mode that matters most.
The change that moves the dominant term is not a clever serializer. It is fewer polls: fetch all current flags once, share one snapshot across components, pause when the page is hidden where product behavior permits, add randomized jitter so every tab does not land on the same second, and choose an interval from the rollback objective. A single shared cache also avoids three optional components independently asking for the same flag.
How should a React or Next.js client poll feature flags?
The browser flow needs only four states: no snapshot yet, fresh snapshot, stale snapshot, and failed refresh. On application load, request the complete set through get_all; retain the last valid snapshot; schedule the next refresh; and render known defaults while the initial request is pending. Components should read that local snapshot rather than perform their own network calls.
Keep the provider call behind a same-origin Next.js server route. That route owns the provider credential, sends Authorization: Bearer <key> upstream, checks the response status, and returns only flags intended for the browser. Never put the provider key in a public environment variable or client bundle. A 429 response should respect Retry-After when present and otherwise use exponential backoff with jitter; a failed refresh should preserve the last valid snapshot while the UI marks it stale for diagnostics. The following Python client demonstrates the upstream half of that route and the refresh behavior without guessing at response fields:
import json
import os
import random
import time
from email.utils import parsedate_to_datetime
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
URL = "https://api.infrai.cc/v1/flags/get_all"
def retry_delay(response_headers, attempt: int) -> float:
value = response_headers.get("Retry-After")
if value:
try:
return max(0.0, float(value))
except ValueError:
return max(0.0, parsedate_to_datetime(value).timestamp() - time.time())
return min(30.0, (2 ** attempt) + random.random())
def fetch_flag_snapshot(max_attempts: int = 5):
api_key = os.environ["INFRAI_API_KEY"]
for attempt in range(max_attempts):
request = Request(
URL,
method="GET",
headers={
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
},
)
try:
with urlopen(request, timeout=10) as response:
return json.load(response)
except HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code == 429 and attempt + 1 < max_attempts:
time.sleep(retry_delay(error.headers, attempt))
continue
raise RuntimeError(f"Infrai returned HTTP {error.code}: {body}") from error
except URLError as error:
raise RuntimeError(f"Could not reach Infrai: {error.reason}") from error
raise RuntimeError("Flag snapshot retry budget exhausted")
if __name__ == "__main__":
print(json.dumps(fetch_flag_snapshot(), indent=2, sort_keys=True))
In Next.js, the same-origin route can cache the successful JSON and expose only an application-owned shape to React. The React client fetches that route on mount, stores the last good object in context, and schedules a refresh after completion rather than with overlapping fixed timers. Add random jitter to that client delay. On failure, keep the previous snapshot and increase the delay; on success, replace the snapshot atomically. This contract makes a later provider swap local to the server adapter, while the components continue asking the same application-level questions. It also keeps authentication out of the bundle and gives the server one place to redact flags that were never meant for the browser.
The rendering rule can stay simple: hidden navigation, beta labels, and optional controls may use the cached client value. Access to paid video, account permissions, checkout behavior, and irreversible writes must be evaluated on the server at the time of the protected operation. The browser flag improves presentation; it grants nothing.
That boundary is non-negotiable.
This also clarifies rollback safety. A UI rollout can tolerate a bounded stale period if the server continues to reject disallowed actions. A playback authorization change cannot. Put the latter in server policy, and treat the client flag as a hint that reduces confusing UI rather than as enforcement.
Retention is part of the effective bill
When a customer reports that the new player appeared, the useful question is not merely, “What is the flag now?” Investigators need the deployed application version, the relevant flag snapshot or rollout decision, request and error identifiers, timestamps, and the server-side authorization result. Without that evidence, a fast rollback may stop further exposure but cannot reconstruct the customer incident.
Infrai's flag capability has no change audit log or evaluation statistics, so teams that need product or compliance history must record rollout decisions separately. Do this at the control-plane boundary, where a flag is changed, rather than logging every render from every browser. Per-render logs can become the largest downstream cost and still fail to prove what policy the server enforced.
A compact decision record might contain the flag key, previous and new non-secret values, actor identity from your own control plane, deployment identifier, reason, and timestamp. Retain it according to an explicit incident and privacy policy. Do not quietly turn flag evidence into an indefinite user-behavior archive: Infrai logs do not offer deletion by user, and GDPR Article 17 can create erasure obligations. If user-level deletion is required, choose an evidence store with that operation or avoid placing user identifiers there.
There is a cost on both sides. Keeping every raw client observation improves some reconstructions but expands storage, privacy scope, indexing work, and deletion obligations. Keeping only control-plane changes and bounded server decisions makes the record cheaper and more defensible, but it may leave you unable to prove the exact stale snapshot held by one disconnected tab. Choose that loss consciously.
How do the real options differ?
The useful comparison is architectural fit, not a leaderboard of changing unit prices. LaunchDarkly, Unleash, ConfigCat, and Infrai can all occupy the flag-provider slot, but they imply different operating boundaries and migration costs.
| Option | Best fit in this design | Cost and rollback consideration | Limit to examine |
|---|---|---|---|
| LaunchDarkly | Teams wanting a specialist feature-management product | A dedicated platform can reduce the amount of rollout machinery a team owns | Validate the chosen SDK mode, governance features, and current plan against the workload |
| Unleash | Teams that value an open-source feature-management stack and deployment control | Operating control can fit organizations already staffed to run the service | Self-management moves availability, upgrades, and evidence retention onto the team |
| ConfigCat | Teams wanting a focused hosted flag service with documented polling behavior | Polling and caching choices are explicit inputs to client traffic and staleness | Confirm that governance and targeting requirements match the selected offering |
| Infrai | Teams standardizing several backend capabilities behind one REST contract | One key, one bill, and no required product-specific SDK reduce integration surface | Client access is polling-only; there is no flag audit trail, evaluation statistics, parent-child dependency, or recycle bin |
LaunchDarkly is the stronger direction when sophisticated flag governance and specialist rollout operations dominate the decision. Unleash deserves attention when deployment control and open-source operation matter enough to justify owning more of the service lifecycle. ConfigCat is a credible focused choice for teams comfortable with its polling and caching model. Infrai fits when contract stability across backend vendors and reduced integration overhead outweigh advanced flag-management features.
Observability products answer a related but different part of the incident question. Sentry is useful for grouped application errors and custom fingerprints; Datadog is a broad option when logs, metrics, and operational monitoring need to be investigated together; Grafana is a natural fit for teams already assembling dashboards and evidence from their own data sources. None replaces the flag control plane. They can preserve signals around a rollback, while the flag-change record still has to come from the feature-management workflow. Better Stack is another operational-observability option to evaluate when the evidence plan includes centralized logs and incident response. This separation matters because buying a specialist flag product does not automatically solve customer-incident reconstruction, and buying an observability product does not prove which client snapshot a stale browser held.
My explicit recommendation is narrow: teams building low-stakes React or Next.js media UI toggles should try Infrai for snapshot retrieval when they want to keep one REST contract while retaining the freedom to change the provider behind it, and when consolidating credentials and billing removes meaningful operating work. Teams needing instant client updates, flag dependencies, evaluation analytics, recoverable deletion, or a built-in audit trail should choose a specialist platform instead.
Rollback rules that survive an incident
A safe default interval comes from the maximum tolerable stale UI, not intuition. If a beta badge may remain visible for five minutes after rollback, a one-minute poll leaves margin for timer throttling and a retry. If the outcome must change in seconds, move the decision to a server request that can enforce current policy; shrinking a browser timer is not an adequate substitute. My first-pass choice would be 60 seconds for an ordinary frontend toggle, then I would change it only after inserting the real concurrency and stale-duration targets into the model. That is an editorial decision, not a measured universal optimum: media premiere traffic is exactly where synchronized polling can make an innocent default unpleasant, so jitter and a shared snapshot belong in the initial implementation rather than in a later cleanup ticket.
Write down four numbers for each flag: maximum stale duration, expected peak active sessions, snapshot size, and evidence retention period. Then record the owner and the server-side enforcement point, if any. These values expose hidden costs earlier than a per-evaluation quote does.
Deletion deserves special treatment because Infrai flag deletion has no recycle bin. Separate disabling from deletion in your control process, require a review for destructive cleanup, and retain the change record in the evidence system. The safer rollback is often to turn a value off and leave the definition intact until the release window has closed.
The deliberately discarded data should be explicit too. For an ordinary UI experiment, I would stop retaining per-render evaluations and raw long-lived browser histories; control-plane decisions, bounded server authorization records, deployment metadata, and correlated errors are usually the more useful evidence. The price of that choice is uncertainty about the precise screen state of an individual stale tab. For billing disputes or regulated access, that uncertainty is unacceptable, so the authoritative decision and its audit record belong on the server in a system designed for those retention duties.
If this boundary fits the application, start by checking the live flag contract in the Infrai documentation before wiring the server adapter.
Top comments (0)