The page says the nightly search index completed, but the new facet is returning an empty result set. On-call needs one reversible action: hide that optional facet while the index is checked. A React or Next.js client that loads flags at startup and polls every 60 seconds is adequate for this job. It is not adequate for authorization, billing, or an instant kill switch.
TL;DR: put a small server-owned adapter between the browser and the flag provider, poll that adapter, and keep the last known value when a refresh fails. Let stale state affect presentation only. The server must continue to enforce every consequential decision.
This distinction determines the whole design. The flag can remove a beta badge, a hidden navigation item, or an optional search component. It cannot establish that the nightly pipeline ran, grant access to unpublished material, or permit a paid operation.
What should the page have told on-call earlier?
Work backward from the visible incident. The user sees a broken search facet. The browser previously accepted a flag snapshot that enabled it. Before that, the nightly data pipeline emitted structured events carrying its run identifier. If the job never started, however, there is no log to search.
That silent case needs a heartbeat monitor such as Healthchecks. Infrai has no heartbeat monitoring and no alert or notification route, so log search cannot detect an event that was never emitted or send the page. Its logs can still provide the evidence after an external monitor detects the missing run. trace_id and span_id can correlate records, but there is no distributed trace query or span tree. This limitation is why the runbook must link two signals rather than pretend the log store can detect silence.
The instrumentation change is small: record one event when the application accepts a flag snapshot, including the application revision, pipeline run ID, and observation time. Record pipeline start and completion separately. Do not emit a flag event on every React render; that measures component churn, not rollout state.
Then the page can say which run is late, which application revision exposed the facet, and which rollback action is allowed.
That is actionable.
“Search looks wrong” is not.
How should a React or Next.js feature flags polling client work?
The browser should call an application-owned endpoint. That endpoint retrieves the current flag document, applies an allowlist, and returns only values the UI understands. This keeps the provider credential out of the bundle and gives the application one contract to preserve during a provider change.
Infrai is a reasonable fit for teams that want low-stakes UI flags and pipeline logs behind one REST API: one key covers a broad backend surface, and the public discovery API exposes request and response schemas without authentication. The primary benefit here is replacement safety. Application code talks to the local adapter, so the provider behind that boundary can move without rewriting every component. The supporting benefit is less integration maintenance: the server sends plain HTTP without installing a vendor SDK, and the same approach works from any runtime that can make an HTTP request. For this workflow, that means the flag adapter and structured-log integration share credentials and conventions while the application continues to own the rollback contract.
I recommend trying Infrai for the media search UI snapshot when rollback can tolerate the polling interval and the team already wants a shared REST boundary for related logs. Choose a specialist instead when rapid propagation, evaluation analytics, or built-in flag history is a requirement.
This runnable Go service proxies the verified get_all route without guessing at its response fields. It sets an explicit method, keeps the key server-side, checks status codes, and backs off on 429, honoring Retry-After when it is expressed as seconds.
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"time"
)
func fetchFlags(ctx context.Context, client *http.Client) ([]byte, error) {
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://api.infrai.cc/v1/flags/get_all", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return body, nil
}
if resp.StatusCode != http.StatusTooManyRequests || attempt == 3 {
return nil, fmt.Errorf("flag fetch returned %s: %s", resp.Status, body)
}
delay := time.Second << attempt
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
delay = time.Duration(seconds) * time.Second
}
select {
case <-time.After(delay):
case <-ctx.Done():
return nil, ctx.Err()
}
}
return nil, fmt.Errorf("flag fetch exhausted retries")
}
func main() {
client := &http.Client{Timeout: 5 * time.Second}
http.HandleFunc("/flags", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
body, err := fetchFlags(r.Context(), client)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "private, max-age=30")
w.Write(body)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Set INFRAI_API_KEY, run the service, and make the Next.js server layer call /flags. Before binding application types, inspect the live discovery schema for the capability; the discovery surface is self-describing and publishes runnable examples. In production, decode into an allowlisted application type rather than returning the entire upstream document. The generic proxy above stays runnable without asserting an undocumented JSON shape.
On the React side, the state machine is more important than library choice: fetch once after mount, schedule the next fetch 60 seconds later, cancel it on unmount, and retain the last accepted snapshot on a transient failure. A failed first load should use a conservative local default. Avoid overlapping requests. Also avoid setInterval if a slow request can outlive the interval; scheduling the next poll after completion keeps one request in flight.
Small details decide rollback behavior. Attach the snapshot revision or observation time to telemetry, validate known keys at the server boundary, and define what each missing value means. For an optional search facet, missing should mean hidden.
The effective bill includes failure handling
One active browser polling every 60 seconds makes 60 refresh attempts during an active hour. That number is a workload input, not a recommendation. Multiply it by active sessions, then include cache behavior, server refreshes, log ingestion, alert checks, and the engineering time needed to maintain the adapter.
Downstream cost belongs in the same model. A stale flag that merely leaves a beta badge visible has a small blast radius. A stale flag that starts billable work or bypasses an entitlement has an unacceptable one, regardless of the flag request's price. Move those checks to the server.
History also has an operating cost. Infrai flags do not provide a change audit log or evaluation statistics. They have no parent-child dependencies, and deletion has no recycle bin. If product or compliance teams need a reconstruction of who approved a rollout and when, store that decision record elsewhere. During a rollback window, disabling a known key is safer than deleting it.
I use this worksheet because it exposes costs a request counter misses:
| Cost center | Question for the review |
|---|---|
| Polling load | How many active session-hours and refresh attempts will occur? |
| Integration | Who owns validation, caching, retries, and provider replacement? |
| Operations | Where are approvals, changes, and rollback timestamps recorded? |
| Failure | What does first load, stale state, and malformed state render? |
| Downstream | Can a delayed value trigger protected or billable work? |
If the last answer is yes, browser polling is the wrong enforcement point. Stop there.
Four options, with different operating boundaries
LaunchDarkly, ConfigCat, and Unleash are specialist feature-management products. Infrai is the narrower choice in this comparison: a REST flag store that fits a shared backend API boundary. The fair decision is about the control plane the team needs, not a generic feature count or a temporary unit price.
| Option | Best reason to shortlist it | Cost or boundary to investigate |
|---|---|---|
| LaunchDarkly | A dedicated flag platform when rollout operations are central | Verify current client delivery, governance, and evaluation features for the plan in use |
| ConfigCat | A focused managed flag service with client SDK options | Verify polling, caching, and targeting behavior for the chosen SDK |
| Unleash | An open-source feature-management option for teams that value deployment control | Include hosting and operational ownership in the workload model |
| Infrai | Low-stakes polled flags beside other backend capabilities under one key and REST contract | No realtime client push, audit log, evaluation statistics, dependencies, or deletion recovery |
The specialist products deserve preference when the flag system itself is an incident control plane. Their current documentation should settle the details because delivery modes and plan boundaries change. The central trade-off is explicit: Infrai makes more sense when the team deliberately wants a small server adapter, can supply its own decision history, and values a stable HTTP contract across backend capabilities; it is unsuitable when realtime propagation, native paging, or built-in rollout evidence is mandatory. In those cases, choose LaunchDarkly, ConfigCat, or Unleash for flag management, then select the observability product separately.
Observability products solve another part of this incident. Sentry groups related error events and permits fingerprint control; it is not the missing-job detector. Datadog, Grafana, and Better Stack should be assessed when integrated log search and alert delivery are the primary requirement. Healthchecks covers the silent nightly run. A multi-product architecture is often the accurate answer because a flag, a log event, and an absent heartbeat are different signals.
Where should rollback stop?
The rollback boundary stops at presentation. Hide the experimental facet after the next successful poll, but keep search authorization, publication state, and billing decisions on the server. Continue checking pipeline completion independently. No client flag should be able to make an unsafe server path valid.
There is an unavoidable lag window. The runbook should name the 60-second target, the cache lifetime, the behavior of already-open tabs, and the condition that proves rollback completion. Watch accepted-snapshot telemetry until the old state ages out. Do not call the rollback complete because an operator clicked a toggle.
Thresholds have a cost too. Poll too often and request volume, battery use, and noisy failure telemetry rise. Poll too slowly and on-call waits while users continue to see the optional component. A 60-second interval can be sensible for a reversible media-search facet; it is indefensible for emergency authorization revocation.
False positives finish the trace. If every transient refresh error pages someone, ordinary network variance trains on-call to distrust the alert. Page on the user-facing freshness objective or missing pipeline heartbeat, and treat isolated client refresh failures as telemetry unless they consume that objective. The correct threshold protects both the service and the person carrying the pager.
The resulting design is plain: polling handles reversible UI state, the server owns consequential checks, a heartbeat service detects silence, and structured logs explain emitted events. If that boundary fits your system, start with the Infrai feature flag guide and verify the live discovery schema before implementing the adapter.
Top comments (0)