firebase / firebase/firebase-js-sdk

Remote Config A/B Test Doesn't Receive Data for NextJS Web

Open
#10,189 5 comments 1 reaction 0 assignees View on GitHub
api: remoteconfig bug internal-bug-filed needs-attention reproducible stack:NextJS
Dominant language
TypeScript
Stars
5.1k
Forks
1k
Avg merge
2d 21h
Merged PRs (30d)
37

Description

### Operating System

Web

### Environment (if applicable)

NextJS

### Firebase SDK Version

12.16.0

### Firebase SDK Product(s)

Remote-Config

### Project Tooling

We are using NextJS with Amplify hosting.

### Detailed Problem Description

We are running A/B tests consistently for mobile and we would like to start using A/B testing for web NextJS app too. We setup Firebase and Google Analytics. Google Analytics able to receive the events and integrated to Firebase. However, when we start an A/B test for web, it receives the total number of users participated but triggered event counts per A/B test condition is not calculated and always shows 0.

Image

As you see in the screenshot, total participated user count is received, but purchases are not received. We have purchases happening and we are sure it is not supposed to be 0.

### Steps and code to reproduce issue

> // Firebase Analytics + Remote Config. Kept separate from the auth module and
> // only loaded via dynamic import (see analytics.ts) so these SDK pieces stay
> // out of the initial page bundles.
> import {
> getAnalytics,
> isSupported,
> logEvent,
> type Analytics,
> } from "firebase/analytics";
> import {
> getRemoteConfig,
> fetchAndActivate,
> getString,
> isSupported as isRemoteConfigSupported,
> type RemoteConfig,
> } from "firebase/remote-config";
> import app from "./firebaseApp";
>
> let analyticsInstance: Analytics | null = null;
>
> // Call inside useEffect; returns analytics instance or null if unsupported/server.
> export const initAnalytics = async (): Promise => {
> if (typeof window === "undefined") return null;
> if (analyticsInstance) return analyticsInstance;
> const supported = await isSupported();
> if (supported) {
> analyticsInstance = getAnalytics(app);
> }
> return analyticsInstance;
> };
>
> export const logAnalyticsEvent = async (
> eventName: string,
> properties?: Record,
> ) => {
> const analytics = await initAnalytics();
> if (!analytics) return;
> logEvent(analytics, eventName, properties);
> };
>
> export const trackPageView = async (pagePath: string) => {
> const analytics = await initAnalytics();
> if (!analytics) return;
> logEvent(analytics, "page_view", { page_path: pagePath });
> };
>
> // Every Remote Config key the app uses must be listed here. The SDK only resolves
> // keys declared in defaultConfig — it does not enumerate all server parameters.
> export const REMOTE_CONFIG_DEFAULTS = {
> web_onboarding_pricing: "single-annual",
> web_paywall_highlight_total: "true",
> annual_pricing_id: "yearly",
> } as const;
>
> export type RemoteConfigKey = keyof typeof REMOTE_CONFIG_DEFAULTS;
>
> let remoteConfigInstance: RemoteConfig | null = null;
> let remoteConfigActivation: Promise | null = null;
>
> const resolveRemoteConfigValues = (
> config: RemoteConfig | null,
> ): Record => {
> const values = {} as Record;
> for (const key of Object.keys(REMOTE_CONFIG_DEFAULTS) as RemoteConfigKey[]) {
> values[key] =
> config != null ? getString(config, key) : REMOTE_CONFIG_DEFAULTS[key];
> }
> return values;
> };
>
> const initRemoteConfig = async (): Promise => {
> if (typeof window === "undefined") return null;
> if (remoteConfigInstance) return remoteConfigInstance;
> const supported = await isRemoteConfigSupported();
> if (!supported) return null;
> const config = getRemoteConfig(app);
> // Always fetch the latest values from the Remote Config backend; the defaults
> // are only used as a fallback when the fetch hasn't completed or fails.
> config.settings.minimumFetchIntervalMillis = 0;
> config.defaultConfig = REMOTE_CONFIG_DEFAULTS;
> remoteConfigInstance = config;
> return remoteConfigInstance;
> };
>
> // Fetches and activates the latest Remote Config. Safe to call multiple times;
> // the underlying fetch+activate only runs once per page load.
> export const loadRemoteConfig = async (): Promise => {
> if (remoteConfigActivation) return remoteConfigActivation;
> remoteConfigActivation = (async () => {
> const config = await initRemoteConfig();
> if (!config) return null;
> try {
> const activated = await fetchAndActivate(config);
> const values = resolveRemoteConfigValues(config);
> console.log("remote config values:", values, { activated });
> } catch (error) {
> console.error("Failed to fetch remote config:", error);
> console.log(
> "remote config values (defaults):",
> resolveRemoteConfigValues(null),
> );
> }
> return config;
> })();
> return remoteConfigActivation;
> };
>
> export const getRemoteConfigValues = async (): Promise<
> Record
> > => {
> const config = await loadRemoteConfig();
> return resolveRemoteConfigValues(config);
> };
>
> // Returns the string value for a Remote Config key, falling back to the default.
> export const getRemoteConfigString = async (
> key: RemoteConfigKey,
> ): Promise => {
> const values = await getRemoteConfigValues();
> return values[key];
> };
>

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the NextJS web A/B test with the setup shown in analytics.ts and firebaseApp, then inspect how Remote Config activation and Analytics purchase events are connected. Compare the web experiment's received users and condition event counts with Firebase's expected event setup; done means purchase events are attributed to the A/B test conditions instead of remaining at zero.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, firebase, nextjs, typescript
Domain
analytics, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.