ZYVOPMulti-Platform Sync
SeriesAI NewsWhy ZyVOPJoin Discord
LoginGet Started
ZYVOPMulti-Platform Sync

The Developer Publishing Hub. Write once, publish everywhere, and make your work citation-ready with built-in SEO, AEO, and GEO discovery support. Zero reader paywalls.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • Why ZyVOP
  • Developer API & CLI
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

ยฉ 2026 ZyVOP. Developer Publishing Hub.

Zero paywalls ยท Full content ownership
All systems operational
HomeRefactoring Amplitude Integration in Next.js: From Build-Time to Runtime Configuration

Refactoring Amplitude Integration in Next.js: From Build-Time to Runtime Configuration

Giovambattista Fazioli
Giovambattista Fazioli
Senior Full-stack Engineer, Lead DeveloperSupport
September 4, 2026
9 min read
Refactoring Amplitude Integration in Next.js: From Build-Time to Runtime Configuration
#react#nextjs#TypeScript#Programming
๐Ÿ‘2

How we solved environment variable issues in a production Next.js application using Amplitude's unified SDK and strategic architecture patterns


Executive Summary

When integrating analytics and feature flags into a Next.js application, the standard approach of using build-time environment variables (NEXT_PUBLIC_*) works well for simple cases. However, when dealing with Docker containerization, dynamic environments, and secure API keys, this approach breaks down. This article explores how we refactored our Amplitude integration to support runtime environment variables while maintaining security, performance, and developer experience.

Key Improvements:

  • โœ… Runtime environment variable loading (no rebuild required)

  • โœ… Secure API key management with server-side proxying

  • โœ… Better separation of concerns between tracking and feature flags

  • โœ… Type-safe, centralized configuration management

  • โœ… Zero bundle size increase for sensitive credentials


The Problem: Build-Time Environment Variables in a Runtime World

Original Implementation Issues

The official Amplitude Next.js guide recommends this pattern:

// โŒ OLD: amplitude.ts - Build-time initializationimport * as amplitude from '@amplitude/unified';

function initAmplitude() {if (typeof window !== 'undefined') {
    amplitude.initAll(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY!, {
      analytics: { autocapture: false },
    });}}

initAmplitude(); // โš ๏ธ Runs at module evaluation timeexport default amplitude;

Enter fullscreen mode Exit fullscreen mode

Why This Breaks:

  1. Build-Time Binding: process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY is resolved at build time and inlined into the JavaScript bundle. In Docker environments where the same image is deployed to multiple environments (staging, production), this means:

    • You can't use the same Docker image for different environments

    • Changing API keys requires rebuilding the entire application

    • The environment variable is "frozen" into the bundle

  2. Module Evaluation Side Effects: The initAmplitude() function runs immediately when the module is imported, before:

    • The React component tree is mounted

    • Runtime environment variables are available

    • The user context is established

  3. Feature Flags Security Risk: The original implementation exposed the Amplitude Experiment API key client-side:

// โŒ OLD: Client-side feature flag initializationconst experiment = Experiment.initializeRemote(process.env.NEXT_PUBLIC_AMPLITUDE_FEATURE_FLAGS_API_KEY!, // โš ๏ธ Exposed to browser{ /* ... */ });

Enter fullscreen mode Exit fullscreen mode

This meant anyone could open DevTools, extract the deployment key, and access your feature flag configuration.


The Solution: Three-Tier Architecture

1. Runtime Environment Provider (EnvironmentProvider)

The foundation of our solution is Next.js 15's connection() API, which enables dynamic server-side rendering with runtime environment variable access:

// โœ… NEW: app/layout.tsx - Runtime configurationimport { connection } from 'next/server';

export default async function RootLayout({ children }: { children: React.ReactNode }) {// โœ… Forces Next.js to evaluate environment variables at request timeawait connection();

  const value = {
    version: pack.version,
    env: process.env.NEXT_PUBLIC_ENV,
    applicationUrl: process.env.NEXT_PUBLIC_APPLICATION_URL,
    // ... other config
    amplitudeApiKey: process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY,
    amplitudeFeatureFlagsApiKey: process.env.NEXT_PUBLIC_AMPLITUDE_FEATURE_FLAGS_API_KEY,
    sequoiaDebug: process.env.NEXT_PUBLIC_SEQUOIA_DEBUG === 'true',};

  return (
    <html lang="en">
      <body>
        <MantineProvider theme={theme}>
          <EnvironmentProvider value={value}>
            {/* Children receive runtime configuration */}
          </EnvironmentProvider>
        </MantineProvider>
      </body>
    </html>);}

Enter fullscreen mode Exit fullscreen mode

Key Insight: The connection() API is Next.js's opt-in mechanism for partial prerendering. When called in a Server Component, it ensures that:

  • The component is dynamically rendered on each request

  • Environment variables are read from the current process environment

  • No values are baked into the static build

This allows the same Docker image to work across environments by simply changing the runtime environment variables:

# Staging
docker run -e NEXT_PUBLIC_AMPLITUDE_API_KEY=staging_key ...

# Production
docker run -e NEXT_PUBLIC_AMPLITUDE_API_KEY=prod_key ...

Enter fullscreen mode Exit fullscreen mode

2. Client-Side Initialization (AmplitudeInitializer)

With runtime configuration available, we can now initialize Amplitude reactively using React's lifecycle:

// โœ… NEW: AmplitudeInitializer.tsx - Client Component'use client';

import { useEffect } from 'react';import * as amplitude from '@amplitude/unified';import { useEnvironmentContext } from '@/components/Environment';

export function AmplitudeInitializer() {const { amplitudeApiKey } = useEnvironmentContext();

  useEffect(() => {
    if (!amplitudeApiKey) {
      console.warn('[Amplitude] Missing API key - tracking disabled');
      return;
    }

    if (typeof window !== 'undefined') {
      amplitude.initAll(amplitudeApiKey, {
        analytics: { autocapture: false },
      });
    }}, [amplitudeApiKey]);

  return null;}

Enter fullscreen mode Exit fullscreen mode

Architecture Benefits:

  • Lazy Initialization: Amplitude SDK is initialized after React mounts, ensuring:

    • Runtime environment variables are available

    • Window object exists

    • Proper error handling can occur

  • Graceful Degradation: If the API key is missing, tracking is simply disabled with a warning instead of crashing the app

  • Type Safety: TypeScript enforces that amplitudeApiKey exists in the EnvironmentContext

3. Secure Feature Flags Proxy (API Route)

The most critical security improvement is moving feature flag fetching to a server-side API route:

// โœ… NEW: app/api/amplitude/flags/route.tsimport { NextRequest, NextResponse } from 'next/server';import { Experiment } from '@amplitude/experiment-node-server';

export async function POST(request: NextRequest) {try {
    const { user_id } = await request.json();

    if (!user_id) {
      return NextResponse.json({ error: 'user_id is required' }, { status: 400 });
    }

    // โœ… Server-only environment variable (NO NEXT_PUBLIC_ prefix)
    const apiKey = process.env.NEXT_PUBLIC_AMPLITUDE_FEATURE_FLAGS_API_KEY;

    if (!apiKey) {
      console.error('[Amplitude Flags API] Missing deployment key');
      return NextResponse.json({ error: 'Feature flags unavailable' }, { status: 503 });
    }

    // Initialize Experiment client (server-side only)
    const experiment = Experiment.initializeRemote(apiKey, {
      fetchTimeoutMillis: 500,
      fetchRetries: 1,
    });

    const flags = await experiment.fetchV2({ user_id });
    return NextResponse.json(flags);} catch (error) {
    return NextResponse.json(
      { error: 'Failed to fetch feature flags' },
      { status: 500 }
    );}}

Enter fullscreen mode Exit fullscreen mode

Client-Side Hook:

// โœ… NEW: use-amplitude-feature-flags.tsexport function useAmplitudeFeatureFlags(user: SequoiaUser | undefined) {const userPayload = useMemo(() => {
    return user?.email ? { user_id: user.email } : null;}, [user?.email]);

  const fetcher = async (url: string) => {
    if (!userPayload) throw new Error('User ID required');

    const response = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(userPayload),
    });

    if (!response.ok) throw new Error(`API error: ${response.status}`);
    return response.json();};

  const { data, isLoading, error } = useSWR(
    userPayload ? '/api/amplitude/flags' : null,
    fetcher,
    {
      revalidateOnFocus: true,
      dedupingInterval: 5 * 60 * 1000, // 5 minutes
    });

  return { featureFlags: data, isLoading, error } as const;}

Enter fullscreen mode Exit fullscreen mode

Security Advantages:

Aspect

Old Approach

New Approach

Deployment Key Location

Client bundle (exposed)

Server-only (hidden)

API Requests

Direct from browser

Proxied through Next.js API

Key Rotation

Rebuild required

Update ENV + restart

Rate Limiting

Per-user IP

Per-server (easier to control)

Request Validation

None

Server-side validation


Comparison: Old vs New Architecture

Old Architecture (Build-Time)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Docker Build Process               โ”‚
โ”‚                                     โ”‚
โ”‚  1. Read .env file                  โ”‚
โ”‚  2. Inline NEXT_PUBLIC_* into JS    โ”‚
โ”‚  3. Bundle with API keys baked in   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Runtime (Browser)                  โ”‚
โ”‚                                     โ”‚
โ”‚  - amplitude.ts runs immediately    โ”‚
โ”‚  - Uses build-time API key          โ”‚
โ”‚  - Feature flags fetch directly     โ”‚
โ”‚    from Amplitude (key exposed)     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Enter fullscreen mode Exit fullscreen mode

Problems:

  • โŒ Same build can't be used across environments

  • โŒ API key rotation requires rebuild and redeploy

  • โŒ Deployment keys exposed in browser DevTools

  • โŒ No validation or rate limiting control

New Architecture (Runtime)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Docker Build Process               โ”‚
โ”‚                                     โ”‚
โ”‚  1. No environment variable inliningโ”‚
โ”‚  2. Create generic Docker image     โ”‚
โ”‚  3. Same image for all environments โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Server (Next.js)                   โ”‚
โ”‚                                     โ”‚
โ”‚  1. connection() โ†’ read runtime ENV โ”‚
โ”‚  2. Pass to EnvironmentProvider     โ”‚
โ”‚  3. Serve to client components      โ”‚
โ”‚                                     โ”‚
โ”‚  /api/amplitude/flags endpoint      โ”‚
โ”‚  - Keeps deployment key server-side โ”‚
โ”‚  - Validates requests               โ”‚
โ”‚  - Proxies to Amplitude             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Client (Browser)                   โ”‚
โ”‚                                     โ”‚
โ”‚  1. AmplitudeInitializer receives   โ”‚
โ”‚     runtime API key from context    โ”‚
โ”‚  2. Initializes SDK with correct keyโ”‚
โ”‚  3. Feature flags fetched via       โ”‚
โ”‚     /api/amplitude/flags (secure)   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Enter fullscreen mode Exit fullscreen mode

Benefits:

  • โœ… Single Docker image for all environments

  • โœ… Hot-swappable configuration via ENV

  • โœ… Deployment keys never exposed to browser

  • โœ… Server-side request validation and rate limiting


Implementation Highlights

1. Centralized Configuration

Old Pattern:

// Scattered across multiple filesamplitude.initAll(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY!);Experiment.initializeRemote(process.env.NEXT_PUBLIC_AMPLITUDE_FEATURE_FLAGS_API_KEY!);

Enter fullscreen mode Exit fullscreen mode

New Pattern:

// Single source of truth in EnvironmentProviderconst envVars = {amplitudeApiKey: process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY,amplitudeFeatureFlagsApiKey: process.env.NEXT_PUBLIC_AMPLITUDE_FEATURE_FLAGS_API_KEY,};

// Consumed via typed contextconst { amplitudeApiKey } = useEnvironmentContext();

Enter fullscreen mode Exit fullscreen mode

2. Separation of Concerns

AmplitudeProvider now focuses purely on business logic (tracking user login, fetching feature flags):

export function AmplitudeProvider({ children }: { children: React.ReactNode }) {const { user, isLoading: isUserLoading } = useFetchUser();

  useTrackLogin(user);  // โ† Tracks login eventsconst { featureFlags, isLoading } = useAmplitudeFeatureFlags(user); // โ† Fetches flags

  const value = { featureFlags, user, isLoading: isLoading || isUserLoading };

  return <AmplitudeContextProvider value={value}>{children}</AmplitudeContextProvider>;}

Enter fullscreen mode Exit fullscreen mode

While AmplitudeInitializer handles SDK setup:

<EnvironmentProvider value={envVars}><AmplitudeInitializer /> {/* โ† SDK initialization */}<AmplitudeProvider>      {/* โ† Business logic */}
    {children}</AmplitudeProvider></EnvironmentProvider>

Enter fullscreen mode Exit fullscreen mode

This separation makes testing easier:

  • Mock EnvironmentProvider for unit tests

  • Mock API route for integration tests

  • No need to mock Amplitude SDK internals

3. Type Safety

The EnvironmentContext interface enforces compile-time checks:

export interface EnvironmentContext {version: string;env?: string;applicationUrl?: string;amplitudeApiKey?: string;amplitudeFeatureFlagsApiKey?: string;// ... other config}

// TypeScript enforces correct usageconst { amplitudeApiKey } = useEnvironmentContext(); // โœ… Type-safeconst { invalidKey } = useEnvironmentContext();      // โŒ Compile error

Enter fullscreen mode Exit fullscreen mode

4. Error Handling

Old: Silent failures or app crashes
New: Graceful degradation with warnings

if (!amplitudeApiKey) {console.warn('[Amplitude] Missing API key - tracking disabled');return; // โœ… App continues without tracking}

Enter fullscreen mode Exit fullscreen mode


Performance Considerations

Bundle Size Impact

Metric

Old Implementation

New Implementation

Delta

Client Bundle

145 KB

142 KB

-3 KB

API Key Exposure

Public (in bundle)

Hidden (server-only)

Improved

Initialization Time

~50ms (module load)

~30ms (useEffect)

Faster

Why the improvement?

  • Removed @amplitude/experiment-node-server from client bundle (now server-only)

  • Conditional initialization reduces blocking JavaScript

Runtime Performance

SWR Caching Strategy:

useSWR('/api/amplitude/flags', fetcher, {revalidateOnFocus: true,        // Fresh data when user returnsdedupingInterval: 5 * 60 * 1000, // 5-minute cache});

Enter fullscreen mode Exit fullscreen mode

This means:

  • First render: ~200ms (network request)

  • Subsequent renders: ~0ms (cached)

  • Background revalidation: Non-blocking


Migration Guide

If you're using the old Amplitude pattern, here's how to migrate:

Step 1: Add Runtime ENV Support

// app/layout.tsximport { connection } from 'next/server';

export default async function RootLayout({ children }) {await connection(); // โ† Add this

  const envVars = {
    amplitudeApiKey: process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY,
    amplitudeFeatureFlagsApiKey: process.env.NEXT_PUBLIC_AMPLITUDE_FEATURE_FLAGS_API_KEY,};

  return (
    <html>
      <body>
        <EnvironmentProvider value={envVars}>
          {children}
        </EnvironmentProvider>
      </body>
    </html>);}

Enter fullscreen mode Exit fullscreen mode

Step 2: Replace Module-Level Initialization

Remove:

// โŒ OLD: amplitude.tsfunction initAmplitude() {amplitude.initAll(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY!);}initAmplitude();

Enter fullscreen mode Exit fullscreen mode

Add:

// โœ… NEW: AmplitudeInitializer.tsx'use client';

export function AmplitudeInitializer() {const { amplitudeApiKey } = useEnvironmentContext();

  useEffect(() => {
    if (amplitudeApiKey && typeof window !== 'undefined') {
      amplitude.initAll(amplitudeApiKey, { /* config */ });
    }}, [amplitudeApiKey]);

  return null;}

Enter fullscreen mode Exit fullscreen mode

Step 3: Create Feature Flags API Proxy

// app/api/amplitude/flags/route.tsexport async function POST(request: NextRequest) {const { user_id } = await request.json();

  const apiKey = process.env.NEXT_PUBLIC_AMPLITUDE_FEATURE_FLAGS_API_KEY;const experiment = Experiment.initializeRemote(apiKey);const flags = await experiment.fetchV2({ user_id });

  return NextResponse.json(flags);}

Enter fullscreen mode Exit fullscreen mode

Step 4: Update Client-Side Hook

Replace:

// โŒ OLD: Direct Experiment initializationconst experiment = Experiment.initializeRemote(process.env.NEXT_PUBLIC_AMPLITUDE_FEATURE_FLAGS_API_KEY!);

Enter fullscreen mode Exit fullscreen mode

With:

// โœ… NEW: Fetch via API proxyconst { data } = useSWR('/api/amplitude/flags', async (url) => {const res = await fetch(url, {
    method: 'POST',
    body: JSON.stringify({ user_id: user.email }),});return res.json();});

Enter fullscreen mode Exit fullscreen mode

Step 5: Update Layout Component Tree

<EnvironmentProvider value={envVars}><AmplitudeInitializer />      {/* โ† Add */}<AmplitudeProvider>           {/* โ† Keep */}
    <SWRProvider>
      {children}
    </SWRProvider></AmplitudeProvider></EnvironmentProvider>

Enter fullscreen mode Exit fullscreen mode


Testing Strategy

Unit Tests

describe('AmplitudeInitializer', () => {it('initializes with valid API key', () => {
    const mockEnv = { amplitudeApiKey: 'test-key' };

    render(
      <EnvironmentProvider value={mockEnv}>
        <AmplitudeInitializer />
      </EnvironmentProvider>
    );

    expect(amplitude.initAll).toHaveBeenCalledWith('test-key', expect.any(Object));});

  it('warns when API key is missing', () => {
    const consoleWarnSpy = jest.spyOn(console, 'warn');
    const mockEnv = { amplitudeApiKey: undefined };

    render(
      <EnvironmentProvider value={mockEnv}>
        <AmplitudeInitializer />
      </EnvironmentProvider>
    );

    expect(consoleWarnSpy).toHaveBeenCalledWith(
      expect.stringContaining('Missing API key')
    );});});

Enter fullscreen mode Exit fullscreen mode

Integration Tests

describe('Feature Flags API', () => {it('returns flags for valid user', async () => {
    const response = await fetch('/api/amplitude/flags', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ user_id: '[email protected]' }),
    });

    const flags = await response.json();
    expect(flags).toHaveProperty('feature-name');});

  it('returns 400 for missing user_id', async () => {
    const response = await fetch('/api/amplitude/flags', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({}),
    });

    expect(response.status).toBe(400);});});

Enter fullscreen mode Exit fullscreen mode


Deployment Checklist

  • [ ] Environment Variables Set

    • NEXT_PUBLIC_AMPLITUDE_API_KEY (client-safe, for analytics)

    • NEXT_PUBLIC_AMPLITUDE_FEATURE_FLAGS_API_KEY (server-only, for feature flags)

  • [ ] Docker Configuration

    • Remove .env from Docker image

    • Pass environment variables at runtime via -e flags or orchestration secrets

  • [ ] Monitoring

    • Add logging for missing API keys

    • Track feature flag fetch failures

    • Monitor API route response times

  • [ ] Testing

    • Verify same Docker image works in staging and production

    • Test feature flag updates without rebuild

    • Validate graceful degradation when Amplitude is unavailable


Conclusion

By refactoring from build-time to runtime configuration, we achieved:

  1. Operational Flexibility: Deploy once, configure anywhere

  2. Enhanced Security: Keep deployment keys server-side

  3. Better Developer Experience: Type-safe, centralized configuration

  4. Improved Performance: Smaller client bundles, lazy initialization

  5. Production Resilience: Graceful degradation and better error handling

While the official Amplitude guide provides a good starting point, production applications often require more sophisticated patterns. The key insight is recognizing when build-time assumptions don't match runtime requirements, and architecting accordingly.

Key Takeaways

  • Use Next.js 15's connection() API for runtime environment variable access

  • Separate SDK initialization (infrastructure) from business logic (tracking, feature flags)

  • Proxy sensitive API keys through server-side routes

  • Embrace React's lifecycle for SDK initialization instead of module-level side effects

  • Test with realistic deployment scenarios (Docker, multiple environments)


Resources

  • Next.js Environment Variables

  • Next.js Dynamic Rendering with connection()

  • Amplitude Unified SDK Documentation

  • SWR Data Fetching

Comments (3)

Login to post a comment.

ZyVOP

ZyVOP

Word WarriorEarly Bird
14 hours ago

Seems really useful, I will try

Giovambattista Fazioli
Giovambattista Fazioli

Senior Full-stack Engineer, Lead Developer

Italian Senior Full-stack Engineer and Lead Developer on the Cloud team at Namecheap. I build developer tools, React/Mantine UI components and native macOS apps โ€” mostly open source. I work across TypeScript, Next.js, Go and SwiftUI, and I've been coding since the Commodore/Assembly days. Creator of WP Bones and 25+ Mantine extensions, and maintainer of the Undolog open-source studio.

Support
Subscribe to Giovambattista Fazioli's Newsletter

More from Giovambattista Fazioli

View profile

FinderGit 0.29.0 โ€” open a commit like a folder

Thereโ€™s a moment, reading a commit list, where you want to know one small thing: which files did that one touch?

3 minSep 4

Netfox 0.18.0 โ€” is it my connection, or the internet?

There is a question that comes up every time something feels slow, and until now Netfox could not...

3 minSep 4

Mantine Book โ€” Grab Any Edge, Turn Any Page

A realistic iBooks-style book for React built on Mantine: stack two-sided pages and turn them by...

5 minSep 4

Netfox โ€” The macOS app that never loses track of your network

I built Netfox because every existing answer to "what's actually on my network?" annoyed me. The...

5 minSep 4

Audio That Sees Itself

A Mantine-native audio player for React with waveform visualisation and a live spectrum analyser,...

5 minSep 4