npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@savvagent/nextjs

v1.0.1

Published

Next.js SDK for Savvagent feature flags with App Router support

Readme

@savvagent/nextjs

Next.js SDK for Savvagent with full App Router support, including Server Components, Client Components, Middleware, and Server Actions.

Installation

npm install @savvagent/nextjs
# or
pnpm add @savvagent/nextjs
# or
yarn add @savvagent/nextjs

Quick Start

1. Initialize Server Client

// app/layout.tsx
import { initServerClient } from '@savvagent/nextjs/server';

initServerClient({
  apiKey: process.env.SAVVAGENT_API_KEY!,
  applicationId: process.env.SAVVAGENT_APP_ID,
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

2. Client Components

// app/components/feature.tsx
'use client';

import { useFlag } from '@savvagent/nextjs/client';

export function Feature() {
  const { value, loading } = useFlag('new-feature');

  if (loading) return <div>Loading...</div>;

  return value ? <NewFeature /> : <OldFeature />;
}

3. Server Components

// app/page.tsx
import { isEnabled } from '@savvagent/nextjs/server';

export default async function Page() {
  const enabled = await isEnabled('new-layout');

  return enabled ? <NewLayout /> : <OldLayout />;
}

4. Middleware

// middleware.ts
import { initMiddlewareClient, createMiddleware } from '@savvagent/nextjs/middleware';

initMiddlewareClient({
  apiKey: process.env.SAVVAGENT_API_KEY!,
});

export default createMiddleware({
  async onRequest(request, client) {
    const context = { user_id: request.cookies.get('user_id')?.value };
    const maintenance = await client.isEnabled('maintenance-mode', context);

    if (maintenance) {
      return NextResponse.rewrite(new URL('/maintenance', request.url));
    }
  },
});

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

API Reference

Server Components

initServerClient(config)

Initialize the server-side client. Call once in your root layout.

import { initServerClient } from '@savvagent/nextjs/server';

initServerClient({
  apiKey: process.env.SAVVAGENT_API_KEY!,
  applicationId: process.env.SAVVAGENT_APP_ID,
  baseUrl: process.env.SAVVAGENT_BASE_URL,
});

isEnabled(flagKey, context?)

Check if a flag is enabled in a Server Component.

import { isEnabled } from '@savvagent/nextjs/server';

export default async function Page() {
  const enabled = await isEnabled('premium-features', {
    user_id: 'user123',
    attributes: { plan: 'pro' },
  });

  return <div>{enabled ? 'Premium' : 'Standard'}</div>;
}

evaluate(flagKey, context?)

Get detailed flag evaluation result.

import { evaluate } from '@savvagent/nextjs/server';

export default async function Page() {
  const result = await evaluate('beta-feature');

  console.log({
    value: result.value,
    reason: result.reason, // 'cached' | 'evaluated' | 'default' | 'error'
    metadata: result.metadata,
  });

  return <Component enabled={result.value} />;
}

withFlag(flagKey, callback, context?)

Execute code conditionally based on flag value.

import { withFlag } from '@savvagent/nextjs/server';

export default async function Page() {
  const data = await withFlag('use-new-api', async () => {
    return await fetchFromNewAPI();
  });

  // data is null if flag is disabled
  return data ? <NewView data={data} /> : <OldView />;
}

createServerContext(overrides?)

Create a context object from Next.js request (automatically extracts cookies and headers).

import { createServerContext, isEnabled, getServerClient } from '@savvagent/nextjs/server';

export default async function Page() {
  const context = await createServerContext({
    attributes: { plan: 'enterprise' },
  });

  const client = getServerClient();
  const enabled = await client.isEnabled('enterprise-features', context);

  return <div>...</div>;
}

Client Components

Use the 'use client' directive and import from @savvagent/nextjs/client:

'use client';

import {
  SavvagentProvider,
  useFlag,
  useSavvagent,
  useUser,
  useTrackError,
} from '@savvagent/nextjs/client';

All client-side APIs are the same as @savvagent/react. See the @savvagent/react documentation for details.

Example: Wrap Client-Side App

// app/providers.tsx
'use client';

import { SavvagentProvider } from '@savvagent/nextjs/client';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <SavvagentProvider
      config={{
        apiKey: process.env.NEXT_PUBLIC_SAVVAGENT_API_KEY!,
        enableRealtime: true,
      }}
    >
      {children}
    </SavvagentProvider>
  );
}
// app/layout.tsx
import { Providers } from './providers';

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

Route Handlers

evaluateForRequest(request, flagKey, context?)

Evaluate flags in API routes with request context.

// app/api/data/route.ts
import { evaluateForRequest } from '@savvagent/nextjs/server';
import { NextRequest } from 'next/server';

export async function GET(request: NextRequest) {
  const useNewAPI = await evaluateForRequest(request, 'new-api-version');

  const data = useNewAPI
    ? await fetchFromNewAPI()
    : await fetchFromOldAPI();

  return Response.json(data);
}

Server Actions

// app/actions.ts
'use server';

import { isEnabled, trackError } from '@savvagent/nextjs/server';

export async function submitForm(formData: FormData) {
  const useNewValidation = await isEnabled('new-validation');

  try {
    if (useNewValidation) {
      await validateWithNewLogic(formData);
    } else {
      await validateWithOldLogic(formData);
    }
  } catch (error) {
    await trackError('new-validation', error as Error);
    throw error;
  }
}

Middleware

initMiddlewareClient(config)

Initialize the middleware client.

import { initMiddlewareClient } from '@savvagent/nextjs/middleware';

initMiddlewareClient({
  apiKey: process.env.SAVVAGENT_API_KEY!,
});

createMiddleware(config)

Create a middleware function with custom logic.

import { createMiddleware } from '@savvagent/nextjs/middleware';
import { NextResponse } from 'next/server';

export default createMiddleware({
  async onRequest(request, client) {
    const context = {
      user_id: request.cookies.get('user_id')?.value,
    };

    const maintenance = await client.isEnabled('maintenance-mode', context);
    if (maintenance) {
      return NextResponse.rewrite(new URL('/maintenance', request.url));
    }
  },
});

redirectIfEnabled(request, flagKey, redirectUrl, context?)

Redirect users when a flag is enabled.

import { redirectIfEnabled } from '@savvagent/nextjs/middleware';

export async function middleware(request: NextRequest) {
  const redirect = await redirectIfEnabled(
    request,
    'force-upgrade',
    '/upgrade'
  );
  if (redirect) return redirect;
}

rewriteIfEnabled(request, flagKey, rewriteUrl, context?)

Rewrite requests when a flag is enabled (useful for A/B testing).

import { rewriteIfEnabled } from '@savvagent/nextjs/middleware';

export async function middleware(request: NextRequest) {
  const rewrite = await rewriteIfEnabled(
    request,
    'beta-ui',
    '/beta' + request.nextUrl.pathname
  );
  if (rewrite) return rewrite;
}

Usage Patterns

SSR with User Context

// app/dashboard/page.tsx
import { cookies } from 'next/headers';
import { isEnabled } from '@savvagent/nextjs/server';

export default async function DashboardPage() {
  const cookieStore = await cookies();
  const userId = cookieStore.get('user_id')?.value;

  const showNewDashboard = await isEnabled('new-dashboard', {
    user_id: userId,
  });

  return showNewDashboard ? <NewDashboard /> : <OldDashboard />;
}

Mixed Server + Client

// app/page.tsx (Server Component)
import { isEnabled } from '@savvagent/nextjs/server';
import { ClientFeature } from './client-feature';

export default async function Page() {
  const serverFlag = await isEnabled('server-feature');

  return (
    <div>
      {serverFlag && <ServerComponent />}
      <ClientFeature /> {/* Client component with its own flags */}
    </div>
  );
}
// app/client-feature.tsx (Client Component)
'use client';

import { useFlag } from '@savvagent/nextjs/client';

export function ClientFeature() {
  const { value } = useFlag('client-feature');
  return value ? <NewUI /> : <OldUI />;
}

Middleware-Based A/B Testing

// middleware.ts
import { initMiddlewareClient, getMiddlewareClient, getRequestContext } from '@savvagent/nextjs/middleware';
import { NextRequest, NextResponse } from 'next/server';

initMiddlewareClient({
  apiKey: process.env.SAVVAGENT_API_KEY!,
});

export async function middleware(request: NextRequest) {
  const client = getMiddlewareClient();
  const context = getRequestContext(request);

  // A/B test: 50% of users see variant B
  const variantB = await client.isEnabled('checkout-variant-b', context);

  if (variantB && request.nextUrl.pathname === '/checkout') {
    return NextResponse.rewrite(new URL('/checkout-b', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/checkout'],
};

Progressive Migration

// Gradually migrate to new implementation
import { isEnabled } from '@savvagent/nextjs/server';

export default async function Page() {
  const useNewComponent = await isEnabled('use-new-component', {
    attributes: {
      rolloutPercentage: 10, // Start with 10% of users
    },
  });

  return useNewComponent ? <NewComponent /> : <LegacyComponent />;
}

Environment Variables

# Server-side API key (for Server Components, Route Handlers, Middleware)
SAVVAGENT_API_KEY=sdk_...

# Client-side API key (for Client Components with real-time updates)
NEXT_PUBLIC_SAVVAGENT_API_KEY=sdk_...

# Optional
SAVVAGENT_APP_ID=your-app-id
SAVVAGENT_BASE_URL=https://api.savvagent.com

TypeScript Support

Full TypeScript support with type definitions for all APIs.

import type {
  FlagClientConfig,
  FlagContext,
  FlagEvaluationResult,
} from '@savvagent/nextjs';

Best Practices

  1. Use Server Components when possible - They're faster and reduce client bundle size
  2. Initialize server client in root layout - Ensures it's ready for all server components
  3. Use middleware for route-level flags - Great for redirects, rewrites, and A/B tests
  4. Separate client/server API keys - Use NEXT_PUBLIC_ prefix only for client-side keys
  5. Leverage automatic context - Server helpers auto-extract user data from cookies/headers
  6. Track errors in new features - Use trackError() to correlate errors with flag changes

License

MIT