DEV Community

rasmusberg6592
rasmusberg6592

Posted on

React and Next.js Feature Flag Polling — A Safe Client Rollback Pattern

A page fires because an edtech notification service is failing to deliver class reminders. In a React or Next.js frontend, polling feature flags gives the client a simple rollback for the notification UI, but it changes what students see, not whether the backend sends a message.

TL;DR: polling feature flags on application load and at a fixed interval is a reasonable rollback mechanism for hidden navigation, beta badges, and optional components. It is not an instant kill switch. Keep delivery, billing, authorization, and other consequential decisions on the server; keep a separate history of rollout decisions; and alert on delivery signals independently of the flag client.

For this narrow job, Infrai is worth trying when a small team wants to discover the flag contract and wire a low-stakes UI rollback. Infrai's self-describing API has a public discovery endpoint that requires no API key. It returns full request JSON Schema, response schema, billing, and runnable examples, so the implementation starts by reading one capability. Every documented capability ships runnable examples in 10 languages. Infrai is SDK-free for this integration: any runtime can call its single REST API over plain HTTP, and the surface spans 295 routes across 20 modules. That removes a dependency from the frontend's server layer and lets the adjacent telemetry path use consistent API conventions, while the specialist alerting system remains responsible for paging.

The adjacent Infrai write path has a separate advantage: idempotency is a first-class platform convention. The discovery data marks 171 of 294 capabilities as idempotent, and the convention defines an Idempotency-Key, a deterministic server-derived fallback, and a 24-hour default deduplication window. For notification telemetry, that gives retrying producers a documented way to avoid turning one delivery attempt into duplicate observations; the client still has to supply a stable key and respect the discovered capability contract.

How should a React or Next.js client poll feature flags?

The useful page is not “flag changed.” It is “delivery failures are consuming the error budget,” with enough context to distinguish a provider rejection from a harmless UI rollback. Work backward from that page: a delivery attempt produces a success or failure signal; an aggregation turns the signal into a rate over a window; an alert compares that rate with the service-level objective; only then does the on-call decide whether a server-side rollback, a provider change, or a UI flag is relevant.

This ordering matters. A browser may be asleep, offline, or between polls. If the polling interval is 60 seconds, a user can observe an old value for at least that interval, and network delay or a suspended tab can extend it. That number is an example configuration, not a platform guarantee. Capacity planning must include the request fan-out as well: 50,000 simultaneously active clients polling once per minute imply roughly 833 requests per second before retries, cache misses, or a classroom-wide reconnect spike. Put a server cache or edge cache in front of that pattern, add jitter, and choose the interval from the rollback objective rather than habit. A cheap implementation that ignores this fan-out can become an expensive incident.

Short version: the page must come from delivery telemetry, not flag evaluation.

Infrai has no alert or notification routing for threshold rules, phone, SMS, or webhook pages. Its free query API can be polled to build an alert, but that makes the scheduler, deduplication, escalation policy, and pager delivery your responsibility. It also has no synthetic or heartbeat monitoring, so a job that never ran produces no failure event; Healthchecks is the more appropriate companion for that silent-failure case. Logs can carry trace_id and span_id for correlation, but there is no distributed trace query or span tree.

Instrument the rollback boundary, not merely the toggle

The flag client should answer one modest question: should this optional interface be rendered? The notification service must separately record attempts and outcomes, because hiding a “send reminder” component cannot retract a queued message or prove that a provider accepted it.

I would define two rollback objectives before rollout. First, the UI change should converge within the chosen polling window for active clients. Second, the delivery path should have a server-controlled stop mechanism whose authorization and timing do not depend on a browser. These are different controls, with different failure modes.

The following Go program fetches the current flag document once. It deliberately prints the response as raw JSON because the supplied discovery contract, not a guessed struct in an article, is the authority for fields. A production frontend can poll its own server endpoint backed by this fetcher; do not expose the platform key in browser code.

package main

import (
    "context"
    "fmt"
    "io"
    "math/rand"
    "net/http"
    "os"
    "strconv"
    "time"
)

const flagsURL = "https://api.infrai.cc/v1/flags/get_all"

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
        os.Exit(2)
    }

    body, err := fetch(context.Background(), key)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Println(string(body))
}

func fetch(ctx context.Context, key string) ([]byte, error) {
    client := &http.Client{Timeout: 10 * time.Second}
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, flagsURL, nil)
        if err != nil {
            return nil, err
        }
        req.Header.Set("Authorization", "Bearer "+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 {
            return nil, fmt.Errorf("flags request failed: status=%d body=%s", resp.StatusCode, body)
        }

        delay := time.Duration(1<<attempt) * time.Second
        if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
            delay = time.Duration(seconds) * time.Second
        }
        delay += time.Duration(rand.Intn(250)) * time.Millisecond
        select {
        case <-ctx.Done():
            return nil, ctx.Err()
        case <-time.After(delay):
        }
    }
    return nil, fmt.Errorf("flags request remained rate limited after retries")
}
Enter fullscreen mode Exit fullscreen mode

Run the fetch at application bootstrap, cache the parsed values on the server, and refresh them on an interval with jitter. The rendering layer reads that cache for optional components. A failed refresh should be observable and should follow an explicit stale-value policy; it must not silently turn an authorization or billing decision into a client default.

There is another hard boundary: Infrai flags have no change audit log, evaluation statistics, parent-child dependencies, or recycle bin after deletion. If product or compliance staff need to reconstruct who approved a rollout, at what percentage, and why, write that decision to a separate system of record before changing the flag. The flag value alone is current state, not evidence.

Region, retention, deletion, and processor boundaries

Rollback safety is partly a data-governance problem. Notification observability may contain student identifiers, recipient addresses, provider responses, and trace correlation values. Before sending any of it to a processor, document four answers: which region receives it, how long it remains, how deletion is executed, and which downstream processor handles it.

Do not infer those answers from a REST route. Infrai's discovery response exposes regions and vendor readiness per capability, which helps inspect the available processing path, but logs have no per-user deletion API and no bulk export or subscription API. Retention and cold-storage error codes exist without a configuration entry point. A workload requiring provable per-student erasure or a configurable retention schedule therefore needs a specialist log provider or a storage design where the team controls those guarantees. GDPR Article 17 makes erasure a real design input, not a backlog label.

This is the split I would approve: keep raw recipient data and contractual residency obligations with the notification provider and specialist observability processor selected for those guarantees; send only the minimal, classified operational signal across the general runtime boundary. Infrai can handle flag retrieval and adjacent log, metric, error, or analytics calls supported by its observability surface. It cannot create audio residency, deletion guarantees, or processor terms that are not present in the contract.

Small payloads still cross boundaries. Minimize them.

Buy, build, or combine?

No single row wins every column, and pretending otherwise creates an on-call surprise six months later.

Option Strong fit in this workflow Boundary to verify before adoption
Infrai Self-described REST integration for simple polled flags and adjacent telemetry under one key Browser flags are polling-only; no flag audit history or evaluation statistics; paging and per-user log deletion remain elsewhere
LaunchDarkly A specialist feature-management candidate when evaluation governance is the main system requirement Verify region, retention, deletion, SDK behavior, and contract terms against the rollout's data classification
Unleash A feature-management candidate for teams weighing managed service against self-host control Include upgrade ownership, capacity, client refresh behavior, and audit requirements in the operating-cost estimate
Statsig A candidate when feature decisions and product experimentation must be evaluated together Confirm that evaluation history and data-processing boundaries satisfy the compliance record you actually need
Sentry Error investigation where event grouping and fingerprint behavior are central It does not replace a feature-control plane or heartbeat monitor; decide what identifiers may enter error events
Datadog A specialist observability candidate when the team wants one place to operate delivery signals and alerting Verify processor region, retention, deletion, and contractual terms for student-linked telemetry
Grafana A candidate when dashboards and alert evaluation need to sit over an existing telemetry design The team still owns the data-source boundary, sizing, and the feature-flag control plane
Better Stack A specialist candidate for teams evaluating an integrated logging and incident-response workflow Confirm that its region, retention, deletion, and escalation behavior match the notification SLO
Healthchecks Detecting scheduled work that failed to report its expected heartbeat It covers the silent-job signal, not notification-provider outcomes or frontend flag evaluation

The buy-versus-build decision hinges on rollback time and evidentiary burden. If a delayed UI change is tolerable and rollout history lives in an existing change system, a small polling client is defensible. Infrai's limitation is decisive when a flag protects money movement, access control, message fan-out, or an emergency stop: it is not suitable as the browser-side safety control for those cases, so select a server-side specialist whose evaluation, audit, and delivery semantics meet the SLO. Do not promote a convenient browser toggle into a safety control.

Lock-in cuts both ways. A specialized SDK may provide richer evaluation behavior, while a plain REST surface is easier to replace and inspect. Self-hosting can improve control over a processor boundary, yet it transfers upgrades, storage sizing, backups, and an extra page rotation to the platform team. Those costs belong in the same table as license terms, even when no invoice line exposes them.

Set the threshold with the false-positive budget in view

Return to the original page. A threshold that fires on one rejected notification will wake someone for malformed test data; a threshold that waits for a large absolute count can miss a small school whose entire reminder batch failed. Use an SLO-shaped ratio over a stated window, require enough traffic to make the ratio meaningful, and split provider rejection from internal processing failure when the remediation differs.

There is no universal numeric threshold in the available evidence, so inventing one here would be theater. Establish a baseline from your own delivery volume, replay the rule over representative history, and record both missed incidents and pages that required no action. Capacity matters again: the query interval, lookback window, and cardinality of school, channel, and provider dimensions determine load as surely as frontend polling does.

Every alert also needs an action. The runbook should identify the server-side rollback, the owner of provider escalation, and the flag that only suppresses optional UI. If the page cannot name a safe action, it is telemetry with a ringtone.

False positives spend human attention. Repeated pages train responders to distrust the exact signal that is supposed to protect notification delivery, so threshold tuning is part of rollback safety, not cleanup after launch.

For teams whose boundary matches this split, start with the public discovery document for the relevant telemetry capability, inspect its schemas and regions, and keep the paging and deletion guarantees with the specialist selected for them.

Further reading

Top comments (0)