Skip to main content

Client-Side Usage

The @ops-ai/nextjs-toggly-client package provides React hooks and components for Client Components in Next.js.

Installation​

npm install @ops-ai/nextjs-toggly-core @ops-ai/nextjs-toggly-client

Setup​

Provider Setup​

Wrap your application with the TogglyProvider:

app/providers.tsx
'use client'

import { TogglyProvider } from '@ops-ai/nextjs-toggly-client'

export function Providers({ children }: { children: React.ReactNode }) {
return (
<TogglyProvider
config={{
appKey: process.env.NEXT_PUBLIC_TOGGLY_APP_KEY!,
environment: 'Production',
onError: (message, error) => {
// Report fetch/cache/refresh failures to your monitoring provider
console.warn('Toggly error:', message, error)
},
}}
autoInit={true}
>
{children}
</TogglyProvider>
)
}
app/layout.tsx
import { Providers } from './providers'

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}

Server-Side Hydration​

For optimal performance, pass server-rendered features to the client:

app/layout.tsx
import { initServerToggly, getServerToggly } from '@ops-ai/nextjs-toggly-server'
import { Providers } from './providers'

export default async function RootLayout({ children }) {
await initServerToggly({
appKey: process.env.TOGGLY_APP_KEY!,
})

const toggly = getServerToggly()
const features = toggly?.state.features ?? {}

return (
<html>
<body>
<Providers initialFeatures={features}>{children}</Providers>
</body>
</html>
)
}

Error reporting and refresh behavior​

Pass onError in the provider config to observe fetch, cache, parse, WebSocket, and refresh failures:

app/providers.tsx
'use client'

<TogglyProvider
config={{
appKey: process.env.NEXT_PUBLIC_TOGGLY_APP_KEY!,
environment: 'Production',
onError: (message, error) => {
monitoring.captureException(error, {
tags: { source: 'toggly' },
extra: { message }
});
},
}}
>
{children}
</TogglyProvider>

useToggly() exposes error from the underlying client state. After one successful load, refresh failures preserve the last-known-good flags instead of replacing rendered content with defaults or empty flags. TogglyProvider, hooks, and components subscribe to feature refreshes, so interval and WebSocket updates can re-render Client Components automatically.

For the shared reliability contract, see Reliability and Error Handling.

Hooks​

useFeatureFlag​

Check if a single feature is enabled:

'use client'

import { useFeatureFlag } from '@ops-ai/nextjs-toggly-client'

export function NewDashboard() {
const { isEnabled, isLoading, refresh } = useFeatureFlag('new-dashboard')

if (isLoading) {
return <LoadingSpinner />
}

if (!isEnabled) {
return <OldDashboard />
}

return (
<div>
<h1>New Dashboard</h1>
<button onClick={refresh}>Refresh Feature</button>
</div>
)
}

useFeatureOff​

Check if a feature is disabled:

'use client'

import { useFeatureOff } from '@ops-ai/nextjs-toggly-client'

export function MaintenanceBanner() {
const { isDisabled, isLoading } = useFeatureOff('maintenance-complete')

if (isLoading) return null

if (isDisabled) {
return <Banner>We're undergoing maintenance</Banner>
}

return null
}

useFeatureGate​

Evaluate multiple features together:

'use client'

import { useFeatureGate } from '@ops-ai/nextjs-toggly-client'

export function PremiumFeature() {
// All features must be enabled
const { isEnabled, isLoading } = useFeatureGate(
['premium-access', 'beta-tester'],
'all'
)

if (isLoading) return <LoadingSpinner />

return isEnabled ? <PremiumContent /> : <UpgradePrompt />
}

export function AnyFeatureAccess() {
// Any one feature enables access
const { isEnabled } = useFeatureGate(
['feature-a', 'feature-b', 'feature-c'],
'any'
)

return isEnabled ? <Content /> : <Locked />
}

useFeatures​

Get all feature states:

'use client'

import { useFeatures } from '@ops-ai/nextjs-toggly-client'

export function FeatureDebugger() {
const { features, isLoading } = useFeatures()

if (isLoading) return <div>Loading features...</div>

return (
<pre>
{JSON.stringify(features, null, 2)}
</pre>
)
}

useToggly​

Access the full Toggly context:

'use client'

import { useToggly } from '@ops-ai/nextjs-toggly-client'

export function UserFeatures() {
const {
features,
isReady,
isLoading,
error,
identity,
setIdentity,
refresh,
isFeatureOn,
evaluateFeatureGate,
} = useToggly()

const handleLogin = async (userId: string) => {
await setIdentity(userId)
}

const checkFeature = async () => {
const isEnabled = await isFeatureOn('my-feature')
console.log('Feature enabled:', isEnabled)
}

return (
<div>
<p>Identity: {identity ?? 'Anonymous'}</p>
<p>Status: {isReady ? 'Ready' : 'Initializing'}</p>
{error && <p>Error: {error.message}</p>}
</div>
)
}

useIdentity​

Manage user identity:

setIdentity assigns the new identity and then refreshes remote definitions. It is not transactional: refresh errors are recorded in client state and the previous feature snapshot may be retained; the identity is not rolled back and a resolved promise does not prove a successful definitions fetch. Do not rely on this API to isolate sensitive data between users.

For multi-tenant or entity-gated pages, prefer evaluating on the server with per-call context / contextKind and passing booleans into Client Components instead of switching browser identity.

'use client'

import { useIdentity } from '@ops-ai/nextjs-toggly-client'

export function UserProfile() {
const { identity, setIdentity, clearIdentity, isUpdating } = useIdentity()

const handleLogin = async (userId: string) => {
await setIdentity(userId)
}

const handleLogout = async () => {
await clearIdentity()
}

return (
<div>
{identity ? (
<button onClick={handleLogout} disabled={isUpdating}>
Logout ({identity})
</button>
) : (
<button onClick={() => handleLogin('user-123')} disabled={isUpdating}>
Login
</button>
)}
</div>
)
}

Groups, claims, live updates, and signing​

Pass groups and claims at provider init for User Claims and group targeting. The client also exposes setContext({ identity, groups, claims }) through useToggly() and useIdentity(). Await the update and inspect errors before relying on refreshed targeting; this is not an authentication API.

<TogglyProvider
config={{
appKey: process.env.NEXT_PUBLIC_TOGGLY_APP_KEY!,
environment: 'Production',
identity: 'user-123',
groups: ['beta', 'enterprise'],
claims: { role: 'admin', plan: 'premium' },
enableLiveUpdates: true, // default — WebSocket sync
verifySignatures: true, // optional ES256 envelope verification
}}
>
{children}
</TogglyProvider>

See Feature filters, Evaluated-signed, and WebSocket sync.

Components​

Feature Component​

Declarative feature flag rendering:

'use client'

import { Feature } from '@ops-ai/nextjs-toggly-client'

export function Dashboard() {
return (
<>
<Feature
featureKey="analytics-dashboard"
loading={<DashboardSkeleton />}
>
<AnalyticsDashboard />
</Feature>
<Feature featureKey="analytics-dashboard" negate>
<BasicDashboard />
</Feature>
</>
)
}

Negate​

Render when a feature is disabled:

'use client'

import { Feature } from '@ops-ai/nextjs-toggly-client'

export function DeprecationNotice() {
return (
<Feature featureKey="new-api" negate>
<Notice>
The legacy API will be deprecated soon. Please migrate to the new API.
</Notice>
</Feature>
)
}

FeatureGate Component​

Multiple feature evaluation:

'use client'

import { FeatureGate } from '@ops-ai/nextjs-toggly-client'

export function AdminPanel() {
return (
<>
<FeatureGate
featureKeys={['admin-access', 'beta-tester']}
requirement="all"
>
<AdminContent />
</FeatureGate>
<FeatureGate
featureKeys={['admin-access', 'beta-tester']}
requirement="all"
negate
>
<AccessDenied />
</FeatureGate>
</>
)
}

FeatureSwitch Component​

Switch between multiple variants:

'use client'

import { FeatureSwitch } from '@ops-ai/nextjs-toggly-client'

export function Pricing() {
return (
<FeatureSwitch
cases={[
{ featureKey: 'pricing-v3', element: <PricingV3 /> },
{ featureKey: 'pricing-v2', element: <PricingV2 /> },
]}
fallback={<PricingV1 />}
/>
)
}

Configuration Options​

<TogglyProvider
config={{
// Required
appKey: string,

// Optional
environment: string, // Default: 'Production'
baseUri: string, // Custom API endpoint
identity: string, // Initial user identity
groups: string[], // Init-time groups for targeting
claims: Record<string, string>, // Init-time claims for targeting
featureDefaults: Record<string, boolean>,
refreshInterval: number, // Auto-refresh interval (ms)
showFeatureDuringEvaluation: boolean, // Show content while checking
enableLiveUpdates: boolean, // WebSocket live updates (default: true)
verifySignatures: boolean, // Verify signed envelopes (default: false)
allowedKeyIds: string[], // Optional JWKS kid allow-list
maxSignatureAgeSeconds: number,

// Client-specific
persistIdentity: boolean, // Save identity to localStorage
identityStorageKey: string, // localStorage key for identity
persistFeatures: boolean, // Cache features in localStorage
featuresStorageKey: string, // localStorage key for features
}}
initialFeatures={Record<string, boolean>} // Server-rendered features
autoInit={boolean} // Auto-initialize on mount (default: true)
>
{children}
</TogglyProvider>

TypeScript​

All hooks and components are fully typed:

import type {
UseFeatureFlagReturn,
TogglyContextValue,
FeatureProps,
} from '@ops-ai/nextjs-toggly-client'

// Hook return types
const featureFlag: UseFeatureFlagReturn = useFeatureFlag('my-feature')

// Context type
const context: TogglyContextValue = useToggly()

Entity context​

Evaluate flags that depend on an entity (an order, a page, an account) in a Server Component, then pass the result into Client Components.

app/orders/page.tsx
import { isServerFeatureOn } from '@ops-ai/nextjs-toggly-server'
import { OrderRow } from './order-row'

export default async function OrdersPage({ order }: { order: Order }) {
const express = await isServerFeatureOn('ExpressCheckout', {
context: order,
contextKind: 'Order',
})

return <OrderRow order={order} express={express} />
}
app/orders/order-row.tsx
'use client'

export function OrderRow({
order,
express,
}: {
order: Order
express: boolean
}) {
return express ? <ExpressButton order={order} /> : null
}

See Entity & page context.

See also​

Best Practices​

  1. Use Server Components First: Prefer Server Components for static feature checks
  2. Hydrate from Server: Pass server features to avoid loading states
  3. Handle Loading: Always handle isLoading state for better UX
  4. Persist Identity carefully: persistIdentity keeps targeting sticky; identity changes are not transactional with definitions refresh in the core client
  5. Use Components: Prefer declarative components over hooks when possible
  6. Refresh Strategically: Use refresh() sparingly to avoid API spam
  7. Multi-tenant / entity gates: Evaluate on the server and pass booleans to the client rather than relying on browser setIdentity