firebase / firebase/firebase-js-sdk
Remote Config A/B Test Doesn't Receive Data for NextJS Web
- 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.
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
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