Coder Social home page Coder Social logo

unleash / unleash-client-nextjs Goto Github PK

View Code? Open in Web Editor NEW
24.0 5.0 7.0 491 KB

Unleash SDK for Next.js

JavaScript 1.33% TypeScript 98.67%
continuous-delivery continuous-deployment javascript nextjs node node-js sdk typescript unleash vercel

unleash-client-nextjs's Introduction

Unleash Next.js SDK

This package allows easy integration of Unleash feature flags in a Next.js application.

Setup

Installation

To install, simply run:

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

Environment variables

This package will attempt to load configuration from Next.js Environment variables.

When using Unleash client-side, with <FlagProvider /> or getFrontendFlags() configure:

If using server-side (SSR, SSG, API), using getDefinitions() and evaluateFlags(), set:

Detailed explanation

Prefixable Variable Default
NEXT_PUBLIC_ UNLEASH_SERVER_API_URL http://localhost:4242/api
NEXT_PUBLIC_ UNLEASH_FRONTEND_API_URL <(NEXT_PUBLIC_)UNLEASH_SERVER_API_URL>/frontend
No UNLEASH_SERVER_API_TOKEN default:development.unleash-insecure-api-token
No UNLEASH_SERVER_INSTANCE_ID undefined
NEXT_PUBLIC_ UNLEASH_FRONTEND_API_TOKEN default:development.unleash-insecure-frontend-api-token
NEXT_PUBLIC_ UNLEASH_APP_NAME nextjs

If you plan to use configuration in the browser, add NEXT_PUBLIC_ prefix. If both are defined and available, private variable takes priority. You can use both to have different values on client-side and server-side.


๐Ÿ’ก Usage with GitLab's feature flags: To use this SDK with GitLab Feature Flags, use UNLEASH_SERVER_INSTANCE_ID instead of UNLEASH_SERVER_API_TOKEN to authorize with GitLab's service.


Usage

A). ๐ŸŒŸ App directory (new)

This package is ready for server-side use with App Router.

Refer to ./example/README.md#App-router for an implementation example.

import { cookies } from "next/headers";
import { evaluateFlags, flagsClient, getDefinitions } from "@unleash/nextjs";

const getFlag = async () => {
  const cookieStore = cookies();
  const sessionId =
    cookieStore.get("unleash-session-id")?.value ||
    `${Math.floor(Math.random() * 1_000_000_000)}`;

  const definitions = await getDefinitions({
    fetchOptions: {
      next: { revalidate: 15 }, // Cache layer like Unleash Proxy!
    },
  });

  const { toggles } = await evaluateFlags(definitions, {
    sessionId,
  });
  const flags = flagsClient(toggles);

  return flags.isEnabled("nextjs-example");
};

export default async function Page() {
  const isEnabled = await getFlag();

  return (
    <p>
      Feature flag is{" "}
      <strong>
        <code>{isEnabled ? "ENABLED" : "DISABLED"}</code>
      </strong>
      .
    </p>
  );
}

B). Middleware

It's possible to run this SDK in Next.js Edge Middleware. This is a great use case for A/B testing, where you can transparently redirect users to different pages based on a feature flag. Target pages can be statically generated, improving performance.

Refer to ./example/README.md#Middleware for an implementation example.

C). Client-side only - simple use case and for development purposes (CSR)

Fastest way to get started is to connect frontend directly to Unleash. You can find out more about direct Front-end API access in our documentation, including a guide on how to setup a client-side SDK key.

Important: Hooks and provider are only available in @unleash/nextjs/client.

import type { AppProps } from "next/app";
import { FlagProvider } from "@unleash/nextjs/client";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <FlagProvider>
      <Component {...pageProps} />
    </FlagProvider>
  );
}

With <FlagProvider /> in place you can now use hooks like: useFlag, useVariant, or useFlagsStatus to block rendering until flags are ready.

import { useFlag } from "@unleash/nextjs/client";

const YourComponent = () => {
  const isEnabled = useFlag("nextjs-example");

  return <>{isEnabled ? "ENABLED" : "DISABLED"}</>;
};

Optionally, you can configure FlagProvider with the config prop. It will take priority over environment variables.

<FlagProvider
  config={{
    url: "http://localhost:4242/api/frontend", // replaces NEXT_PUBLIC_UNLEASH_FRONTEND_API_URL
    clientKey: "<Frontend_API_token>", // replaces NEXT_PUBLIC_UNLEASH_FRONTEND_API_TOKEN
    appName: "nextjs", // replaces NEXT_PUBLIC_UNLEASH_APP_NAME

    refreshInterval: 15, // additional client configuration
    // see https://github.com/Unleash/unleash-proxy-client-js#available-options
  }}
>

If you only plan to use Unleash client-side React SDK now also works with Next.js. Check documentation there for more examples.

D). Static Site Generation, optimized performance (SSG)

With same access as in the client-side example above you can resolve Unleash feature flags when building static pages.

import {
  flagsClient,
  getDefinitions,
  evaluateFlags,
  getFrontendFlags,
  type IVariant,
} from "@unleash/nextjs";
import type { GetStaticProps, NextPage } from "next";

type Data = {
  isEnabled: boolean;
  variant: IVariant;
};

const ExamplePage: NextPage<Data> = ({ isEnabled, variant }) => (
  <>
    Flag status: {isEnabled ? "ENABLED" : "DISABLED"}
    <br />
    Variant: {variant.name}
  </>
);

export const getStaticProps: GetStaticProps<Data> = async (_ctx) => {
  /* Using server-side SDK: */
  const definitions = await getDefinitions();
  const context = {}; // optional, see https://docs.getunleash.io/reference/unleash-context
  const { toggles } = evaluateFlags(definitions, context);

  /* Or with the proxy/front-end API */
  // const { toggles } = await getFrontendFlags({ context });

  const flags = flagsClient(toggles);

  return {
    props: {
      isEnabled: flags.isEnabled("nextjs-example"),
      variant: flags.getVariant("nextjs-example"),
    },
  };
};

export default ExamplePage;

The same approach will work for ISR (Incremental Static Regeneration).

Both getDefinitions() and getFrontendFlags() can take arguments overriding URL, token and other request parameters.

E). Server Side Rendering (SSR)

import {
  flagsClient,
  evaluateFlags,
  getDefinitions,
  type IVariant,
} from "@unleash/nextjs";
import type { GetServerSideProps, NextPage } from "next";

type Data = {
  isEnabled: boolean;
};

const ExamplePage: NextPage<Data> = ({ isEnabled }) => (
  <>Flag status: {isEnabled ? "ENABLED" : "DISABLED"}</>
);

export const getServerSideProps: GetServerSideProps<Data> = async (ctx) => {
  const sessionId =
    ctx.req.cookies["unleash-session-id"] ||
    `${Math.floor(Math.random() * 1_000_000_000)}`;
  ctx.res.setHeader("set-cookie", `unleash-session-id=${sessionId}; path=/;`);

  const context = {
    sessionId, // needed for stickiness
    // userId: "123" // etc
  };

  const definitions = await getDefinitions(); // Uses UNLEASH_SERVER_API_URL
  const { toggles } = evaluateFlags(definitions, context);

  const flags = flagsClient(toggles); // instantiates a static (non-syncing) unleash-proxy-client

  return {
    props: {
      isEnabled: flags.isEnabled("nextjs-example")
    },
  };
};

export default ExamplePage;

F). Bootstrapping / rehydration

You can bootstrap Unleash React SDK to have values loaded from the start. Initial value can be customized server-side.

import App, { AppContext, type AppProps } from "next/app";
import {
  FlagProvider,
  getFrontendFlags,
  type IMutableContext,
  type IToggle,
} from "@unleash/nextjs";

type Data = {
  toggles: IToggle[];
  context: IMutableContext;
};

export default function CustomApp({
  Component,
  pageProps,
  toggles,
  context,
}: AppProps & Data) {
  return (
    <FlagProvider
      config={{
        bootstrap: toggles,
        context,
      }}
    >
      <Component {...pageProps} />
    </FlagProvider>
  );
}

CustomApp.getInitialProps = async (ctx: AppContext) => {
  const context = {
    userId: "123",
  };

  const { toggles } = await getFrontendFlags(); // use Unleash Proxy

  return {
    ...(await App.getInitialProps(ctx)),
    bootstrap: toggles,
    context, // pass context along so client can refetch correct values
  };
};

โš—๏ธ CLI (experimental)

You can use unleash [action] [options] in your package.json scripts section, or with:

npx @unleash/nextjs

For the CLI to work, the following environment variables must be configured with appropriate values:

  • UNLEASH_SERVER_API_URL
  • UNLEASH_SERVER_API_TOKEN

The CLI will attempt to read environment values from any .env files if they're present. You can also set the variables directly when invoking the interface, as in the CLI usage example.

Usage

  • get-definitions <outputFile.json> Download feature flags definitions for bootstrapping (offline use) of server-side SDK.
  • generate-types [options] <outputFile.ts> Generate types and typed functions from feature flags defined in an Unleash instance. It will also generate strictly typed versions of useFlag, useVariant, useFlags and flagsClient (unless --typesOnly is used).
    • -t, --typesOnly don't include typed versions of functions exported from @unleash/nextjs (default: false)
    • -b, --bootstrap <sourceFile.json> load definitions from a file instead of fetching definitions - work offline
  • -V Output the version number

Example

Try it now

UNLEASH_SERVER_API_URL=https://app.unleash-hosted.com/demo/api \
UNLEASH_SERVER_API_TOKEN=test-server:default.8a090f30679be7254af997864d66b86e44dcfc5291916adff72a0fb5 \
npx @unleash/nextjs generate-types ./unleash.ts

Known limitation

  • In current interation server-side SDK does not support metrics.
  • When used server-side, this SDK does not support the "Hostname" and "IP" strategies. Use custom context fields and constraints instead.

unleash-client-nextjs's People

Contributors

danilofuchs avatar fredrikoseberg avatar jax-p avatar karl-run avatar kwasniew avatar mltsy avatar nunogois avatar tepose avatar thomasheartman avatar tymek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

unleash-client-nextjs's Issues

Bundlesize

Describe the bug

Hello,
I've checked out bundlephobia for this package and found that 33% of the bundle is semver. Could someone explain why it is needed?
Refer to: https://bundlephobia.com/package/@unleash/[email protected]

image

Steps to reproduce the bug

No response

Expected behavior

No response

Logs, error output, etc.

No response

Screenshots

No response

Additional context

No response

Unleash version

No response

Subscription type

None

Hosting type

None

SDK information (language and version)

No response

"createContext only works in Client Components" error in API routes

Describe the bug

Next.js complains about using createContext inside a server component while I'm trying to use this library inside a Next.js 13 Route Handler.

Steps to reproduce the bug

  1. Install @unleash/[email protected]
  2. Create Next.js 13 API Route Handler:
// src/app/api/my-route.ts

import { evaluateFlags, flagsClient, getDefinitions } from "@unleash/nextjs";
import { getServerSession } from "next-auth";

export async function POST() {
  try {
    const session = await getServerSession(authOptions);
    if (!session) throw new Error("Unauthorized");

    // ...

    const definitions = await getDefinitions();
    const { toggles } = evaluateFlags(definitions, {
      userId: session.user.id,
    });
    const flags = flagsClient(toggles);

    if (flags.isEnabled("MyFlag") === false) {
      // ...
    }

    // ...
  } catch (error) {
    // ...
  }
}
  1. Call the route

Expected behavior

The server-side methods should not use any client-side methods.

Logs, error output, etc.

error - node_modules/@unleash/proxy-client-react/dist/index.js (1:805) @ createContext                                                                                                                                                                                                             
error - createContext only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/context-in-server-component                                                                                                    
    at eval (webpack-internal:///(sc_server)/./node_modules/@unleash/proxy-client-react/dist/index.js:40:259)                                                                                                                                                                                      
    at eval (webpack-internal:///(sc_server)/./node_modules/@unleash/proxy-client-react/dist/index.js:257:3)                                                                                                                                                                                       
    at (sc_server)/./node_modules/@unleash/proxy-client-react/dist/index.js (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/app/api/project-member/route.js:627:1)                                                                                                             
    at __webpack_require__ (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/webpack-runtime.js:33:43)                                                                                                                                                                           
    at eval (webpack-internal:///(sc_server)/./node_modules/@unleash/nextjs/dist/FlagProvider.js:7:30)                                                                                                                                                                                             
    at (sc_server)/./node_modules/@unleash/nextjs/dist/FlagProvider.js (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/app/api/project-member/route.js:363:1)                                                                                                                  
    at __webpack_require__ (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/webpack-runtime.js:33:43)                                                                                                                                                                           
    at eval (webpack-internal:///(sc_server)/./node_modules/@unleash/nextjs/dist/index.js:30:14)                                                                                                                                                                                                   
    at (sc_server)/./node_modules/@unleash/nextjs/dist/index.js (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/app/api/project-member/route.js:594:1)                                                                                                                         
    at __webpack_require__ (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/webpack-runtime.js:33:43)                                                                                                                                                                           
    at eval (webpack-internal:///(sc_server)/./src/app/api/project-member/route.ts:6:73)                                                                                                                                                                                                           
    at (sc_server)/./src/app/api/project-member/route.ts (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/app/api/project-member/route.js:3361:1)                                                                                                                               
    at __webpack_require__ (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/webpack-runtime.js:33:43)                                                                                                                                                                           
    at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/build/webpack/loaders/next-app-loader.js?name=app%2Fapi%2Fproject-member%2Froute&page=%2Fapi%2Fproject-member%2Froute&appPaths=&pagePath=private-next-app-dir%2Fapi%2Fproject-member%2Froute.ts&appDir=%2FUsers%2Fsimon.knitt
el%2FDocuments%2Fpersonal%2Fdashboard%2Fapp%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Fsimon.knittel%2FDocuments%2Fpersonal%2Fdashboard%2Fapp&isDev=true&tsconfigPath=tsconfig.json&assetPrefix=&nextConfigOutput=!:13:147)          
    at (sc_server)/./node_modules/next/dist/build/webpack/loaders/next-app-loader.js?name=app%2Fapi%2Fproject-member%2Froute&page=%2Fapi%2Fproject-member%2Froute&appPaths=&pagePath=private-next-app-dir%2Fapi%2Fproject-member%2Froute.ts&appDir=%2FUsers%2Fsimon.knittel%2FDocuments%2Fpersonal%
2Fdashboard%2Fapp%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Fsimon.knittel%2FDocuments%2Fpersonal%2Fdashboard%2Fapp&isDev=true&tsconfigPath=tsconfig.json&assetPrefix=&nextConfigOutput=! (/Users/simon.knittel/Documents/personal/da
shboard/app/.next/server/app/api/project-member/route.js:308:1)                                                                                                                                                                                                                                    
    at __webpack_require__ (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/webpack-runtime.js:33:43)                                                                                                                                                                           
    at __webpack_exec__ (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/app/api/project-member/route.js:4768:39)                                                                                                                                                               
    at /Users/simon.knittel/Documents/personal/dashboard/app/.next/server/app/api/project-member/route.js:4769:28                                                                                                                                                                                  
    at Object.<anonymous> (/Users/simon.knittel/Documents/personal/dashboard/app/.next/server/app/api/project-member/route.js:4772:3)                                                                                                                                                              
    at Module._compile (node:internal/modules/cjs/loader:1254:14)                                                                                                                                                                                                                                  
    at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)                                                                                                                                                                                                                           
    at Module.load (node:internal/modules/cjs/loader:1117:32)                                                                                                                                                                                                                                      
    at Module._load (node:internal/modules/cjs/loader:958:12)                                                                                                                                                                                                                                      
    at Module.require (node:internal/modules/cjs/loader:1141:19)                                                                                                                                                                                                                                   
    at require (node:internal/modules/cjs/helpers:110:18)                                                                                                                                                                                                                                          
    at Object.requirePage (/Users/simon.knittel/Documents/personal/dashboard/app/node_modules/next/dist/server/require.js:134:12)                                                                                                                                                                  
    at /Users/simon.knittel/Documents/personal/dashboard/app/node_modules/next/dist/server/load-components.js:51:73                                                                                                                                                                                
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)                                                                                                                                                                                                                  
    at async Object.loadComponentsImpl [as loadComponents] (/Users/simon.knittel/Documents/personal/dashboard/app/node_modules/next/dist/server/load-components.js:51:26)                                                                                                                          
    at async DevServer.findPageComponentsImpl (/Users/simon.knittel/Documents/personal/dashboard/app/node_modules/next/dist/server/next-server.js:667:36) {                                                                                                                                        
  name: 'TypeError',                                                                                                                                                                                                                                                                               
  digest: undefined                                                                                                                                                                                                                                                                                
}                                                                                                                                                                                                                                                                                                  
null

Screenshots

image

Additional context

Next.js 13
app directory
Route Handler

Unleash version

4.22.3

Subscription type

Open source

Hosting type

Self-hosted

SDK information (language and version)

@unleash/nextjs 0.1.2

Support for custom strategies

Describe the feature request

I would like to be able to use custom strategies with this sdk

Background

We use nodejs sdk for backend feature toggle with custom strategies and would like to use the same strategies with this sdk.

Solution suggestions

Possibility for defining custom strategies within the FlagProvider props

Defining UNLEASH_SERVER_API_URL with a trailing slash is not working

Describe the bug

Hello,

When using UNLEASH_SERVER_API_URL or (NEXT_PUBLIC_UNLEASH_SERVER_API_URL) with a value that contains a trailing slash(e.g. "https://eu.app.unleash-hosted.com/12345/api/") calling getDefinitions returns:

{
  message: 'invalid token: expected a different token type for this endpoint'
}

It would be nice if we could pass an URL with a trailing slash (like it's given in the Unleash UI).

Steps to reproduce the bug

  1. Using the library with valid tokens
  2. Set UNLEASH_SERVER_API_URL or (NEXT_PUBLIC_UNLEASH_SERVER_API_URL) to "https://eu.app.unleash-hosted.com/12345/api/"
  3. Call getDefinitions()

Expected behavior

Passing https://eu.app.unleash-hosted.com/12345/api/ OR https://eu.app.unleash-hosted.com/12345/api (for retro-compat with current version) to UNLEASH_SERVER_API_URL or (NEXT_PUBLIC_UNLEASH_SERVER_API_URL) should work.

Logs, error output, etc.

{
  message: 'invalid token: expected a different token type for this endpoint'
}


### Screenshots

_No response_

### Additional context

_No response_

### Unleash version

"@unleash/nextjs": "^0.1.0"

### Subscription type

Pro

### Hosting type

Hosted by Unleash

### SDK information (language and version)

"@unleash/nextjs": "^0.1.0"

Wrong example in docs

Describe the bug

Wrong definition

Example in docs is not up to date with types in package. Docs example:

const definitions = await getDefinitions();
const { toggles } = evaluateFlags(definitions); // this is wrong, second arg of evaluateFlags is typed as required Context 

Why does SSG example use frontEnd example?

Like you know... it is server side generated.

Steps to reproduce the bug

No response

Expected behavior

No response

Logs, error output, etc.

No response

Screenshots

No response

Additional context

No response

Unleash version

No response

Subscription type

None

Hosting type

None

SDK information (language and version)

No response

Unable to setup with CSR and GitLab instance

Describe the bug

The unleash client doesn't get the flag information from the GitLab instance. Although the proxy in /pages/api/feature-flags.tsx returns a JSON with seemingly enough information and a status code 200.

Here is a codesandbox with the reproduces issue:
https://codesandbox.io/p/sandbox/unleash-nextjs-issue-44-35tcdu

You can find:

  • the <FlagProvider /> in /pages/index.tsx
  • useFlag in /components/Features.tsx
  • environment variables in .env

Steps to reproduce the bug

  1. Open this codesandbox: https://codesandbox.io/p/sandbox/unleash-nextjs-issue-44-35tcdu
  2. The text "My feature should be visible" in /components/Features.tsx is not shown

Expected behavior

The feature "my_feature" should be read and handled correctly by the unleash client and the text "My feature should be visible" should be visible.

Logs, error output, etc.

No response

Screenshots

No response

Additional context

Instead of an actual GitLab instance, I mocked the response of the GitLab Unleash instance in /pages/api/feature-flags.tsx which is als present in the real app and is used as a proxy.

Unleash version

GitLab v15.10 (self hosted)

Subscription type

Open source

Hosting type

Self-hosted

SDK information (language and version)

@unleash/nextjs v1.0.0

Caching for server side toggles

Describe the feature request

It seems the current recommended approaches to get toggles with SSR/GSSP both use an un-cached fetch to the API. Since the definitions rarely change, I think it doesn't make much sense to fetch definitions for every request, but instead keep an unleash client instance around and re-fetch definitions every n seconds (in the background).

With this, we can then just do the toggles evaluation for each request.

Background

No response

Solution suggestions

Its fairly easy to implement something like this in user-land, but I think it would make sense to document/build this into the package itself for ease of usage.

URL from getDefinitions is not loading correctly.

Describe the bug

when trying to run getDefinitions with a server api url that wasn't in the env, I'd receive this message.

i was using this:
UNLEASH_SERVER_API_URL

but when trying to pass in a str for the url, i'd receive the following:

definitions {
  message: 'invalid token: expected a different token type for this endpoint'
}

Steps to reproduce the bug

No response

Expected behavior

No response

Logs, error output, etc.

No response

Screenshots

No response

Additional context

No response

Unleash version

No response

Subscription type

None

Hosting type

None

SDK information (language and version)

No response

Unleash: unable to fetch feature toggles TypeError: this.fetch is not a function

Describe the bug

Problem

I'm integrating Unleash v5.3.0-main in a NextJS 13 project via docker-compose. The objective is to evaluate feature flags both on the server and client sides. However, I'm observing inconsistent results between the two.

Server Side Implementation:

For server side flag evaluation, I'm using the flag function from @unleash/nextjs inside app/page.tsx. Here's a simplified snippet:

export default async function Home() {
  // Session ID retrieval and fallback
  const sessionId = cookies().get(COOKIE_NAME)?.value || `${Math.floor(Math.random() * 1_000_000_000)}`;
  const { enabled, variant, error } = await flag("nextjs-example", { sessionId }, { fetchOptions: { next: { revalidate: 1 } } });
  // ... other logic
}

Client Side Implementation:

On the client side, I'm using the FlagProvider and useFlag from @unleash/nextjs/client inside app/layout.tsx and another component respectively:

export default function RootLayout({ children }: { children: React.ReactNode }) {
  // Session ID retrieval and fallback
  const sessionId = cookies().get(COOKIE_NAME)?.value || `${Math.floor(Math.random() * 1_000_000_000)}`;
  // ... Provider setup
}
export const ClientSideCard = ({ title, description, img, price, index }: ClientSideCardProps) => {
  const enabled = useFlag("nextjs-example");
  // ... other logic
}

Issues Observed:

  1. Inconsistent Flags: Toggling the feature flag doesn't reflect the changes accurately on the server side. There seems to be a discrepancy in the state of the flag between the server and client sides.
  2. Client Side Error: The client side throws the following error:
app-index.js:31 Unleash: unable to fetch feature toggles TypeError: this.fetch is not a function
    ... stack trace from [email protected]

Steps to reproduce the bug

  1. Setup Environment:
    • Initialize a new NextJS 13 project.
    • Run Unleash v5.3.0-main using docker-compose.
  2. Server Side Integration:
    • In app/page.tsx, import the necessary modules from next/headers and @unleash/nextjs.
    • Add the logic to retrieve or generate a sessionId.
    • Fetch the feature flag named "nextjs-example" using the flag function and log the results.
  3. Client Side Integration:
    • In app/layout.tsx:
      • Import the required modules from @unleash/nextjs/client.
      • Retrieve or generate the same sessionId as in the server side.
      • Wrap the component's children with the FlagProvider component, setting up the configuration as provided in your example.
    • In a separate component (like ClientSideCard), use the useFlag hook to fetch the state of the "nextjs-example" flag.
  4. Run the Application:
    • Start the NextJS application and access the home page or the relevant page where the above code resides.
  5. Toggle the Feature Flag:
    • Using the Unleash dashboard or interface, toggle the state of the "nextjs-example" flag.
  6. Observe Results:
    • On the server side, observe the console log for the flag's state. Note if the change in flag state is not reflecting correctly.
    • On the client side, inspect the browser console for the mentioned error related to the [email protected].

Expected behavior

Upon integrating Unleash with NextJS 13, both server and client sides should consistently and accurately reflect the current state of the "nextjs-example" feature flag without any errors. Any toggling of the flag in the Unleash dashboard should be immediately and synchronously reflected in both evaluations. No client-side errors related to fetching feature flags should occur.

Logs, error output, etc.

Unleash: unable to fetch feature toggles TypeError: this.fetch is not a function
    at UnleashClient.eval (webpack-internal:///(:3001/app-pages-browser)/./node_modules/.pnpm/[email protected]/node_modules/unleash-proxy-client/build/index.js:399:51)
    at step (webpack-internal:///(:3001/app-pages-browser)/./node_modules/.pnpm/[email protected]/node_modules/unleash-proxy-client/build/index.js:59:23)
    at Object.eval [as next] (webpack-internal:///(:3001/app-pages-browser)/./node_modules/.pnpm/[email protected]/node_modules/unleash-proxy-client/build/index.js:40:53)
    at eval (webpack-internal:///(:3001/app-pages-browser)/./node_modules/.pnpm/[email protected]/node_modules/unleash-proxy-client/build/index.js:34:71)
    at new Promise (<anonymous>)
    at __awaiter (webpack-internal:///(:3001/app-pages-browser)/./node_modules/.pnpm/[email protected]/node_modules/unleash-proxy-client/build/index.js:30:12)
    at UnleashClient.fetchToggles (webpack-internal:///(:3001/app-pages-browser)/./node_modules/.pnpm/[email protected]/node_modules/unleash-proxy-client/build/index.js:382:16)
    at UnleashClient.eval (webpack-internal:///(:3001/app-pages-browser)/./node_modules/.pnpm/[email protected]/node_modules/unleash-proxy-client/build/index.js:310:51)
    at step (webpack-internal:///(:3001/app-pages-browser)/./node_modules/.pnpm/[email protected]/node_modules/unleash-proxy-client/build/index.js:59:23)
    at Object.eval [as next] (webpack-internal:///(:3001/app-pages-browser)/./node_modules/.pnpm/[email protected]/node_modules/unleash-proxy-client/build/index.js:40:53)
    at fulfilled (webpack-internal:///(:3001/app-pages-browser)/./node_modules/.pnpm/[email protected]/node_modules/unleash-proxy-client/build/index.js:31:58)

Screenshots

No response

Additional context

Additional Context:

  • I'm running Unleash through docker-compose with the version being v5.3.0-main.
  • The issue arose in a freshly initialized NextJS 13 project.
  • I've ensured that other network requests, both on the server and client sides, are functioning as expected, which suggests the issue might be specific to the integration with @unleash/nextjs and [email protected].
  • No other third-party libraries or plugins were added to the NextJS project which might interfere with the Unleash operations.
  • I've followed the official documentation for integrating Unleash with NextJS, ensuring configurations are set as recommended.

Understanding if there are known issues, changes in configuration methods, or any updates in the recent v5.3.0-main version of Unleash that could lead to such behaviors would be beneficial.

Unleash version

"@unleash/nextjs": "^1.2.0"

Subscription type

Open source

Hosting type

Self-hosted

SDK information (language and version)

"unleash-proxy-client": "^2.5.0"

Middleware cause Error : Initial locale argument was not passed into serverSideTranslations

Describe the bug

When i'm using next-i18next with middleware, it work fine in development mode (e.g yarn dev). But when i test it in production mode (e.g yarn build then yarn start), its not working anymore. It gave an error saying "Error: Initial locale argument was not passed into serverSideTranslations". Fyi my nextjs version is 13.0.2 and my next-i18next version is 12.1.0.

P.S : If i'm changing the middleware (change it into anything beside using unleash) it work fine. It's only error when i'm using unleash.

Steps to reproduce the bug

I'm adding middleware to my nextjs Project. Here it's the code :

import { NextRequest, NextResponse } from "next/server";
import { evaluateFlags, randomSessionId } from "@unleash/nextjs";
import { addBasePath } from "next/dist/client/add-base-path";

export default async function middleware(req: NextRequest) {
  let cookies: any = req.cookies.get("flags");
  const newUrl = req.nextUrl.clone();
  const res = NextResponse.rewrite(newUrl);
  if (cookies) {
    return res;
  } else {
    const sessionId = req.cookies.get("flags")?.value || randomSessionId();
    const context = { sessionId };
    let flagCookies = null;

    // Grab definitions from an endpoint cached on the edge
    const protocol = req.url.startsWith("https") ? "https://" : "http://";
    const host = req.headers.get("host");
    const endpoint = addBasePath("/api/proxy-definitions");
    const token = process.env.UNLEASH_SERVER_INSTANCE_ID || "";
    const definitionsUrl = `${protocol}${host}${endpoint}?token=${token}`;

    // Make a request to the edge-cached endpoint
    const definitions = await fetch(definitionsUrl, { method: "GET" }).then(
      (res) => res.json()
    );

    // Evaluate based on provided context
    const evaluated = await evaluateFlags(definitions, context);
    flagCookies = JSON.stringify(evaluated);
    // Redirect to variant
    if (flagCookies)
      res.cookies.set("flags", flagCookies, {
        expires: new Date(new Date().getTime() + 1 * 60 * 1000)
      });
    return res;
  }
}

export const config = {
  matcher: ["/:path*"]
};

My next-i18next configuration :

// used for SSR (getServerSideProps)
// const path = require('path')
// const localePath = path.resolve('./public/locales')

module.exports = {
  // https://www.i18next.com/overview/configuration-options#logging
  debug: process.env.NODE_ENV === "production",
  i18n: {
    defaultLocale: "id",
    locales: ["id"]
  },
  // localePath,
  reloadOnPrerender: process.env.NODE_ENV === "production"
};

Expected behavior

The app should working fine as its work fine in developmen mode

Logs, error output, etc.

Error [TypeError]: fetch failed
    at Object.processResponse (evalmachine.<anonymous>:7941:27)
    at <unknown> (evalmachine.<anonymous>:8271:42)
    at <unknown> (node:internal/process/task_queues:140:7)
    at AsyncResource.runInAsyncScope (node:async_hooks:204:9)
    at AsyncResource.runMicrotask (node:internal/process/task_queues:137:8)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Error: Initial locale argument was not passed into serverSideTranslations
    at _callee$ (C:\xampp\htdocs\parent-app\node_modules\next-i18next\dist\commonjs\serverSideTranslations.js:116:19)
    at tryCatch (C:\xampp\htdocs\parent-app\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:45:16)
    at Generator.<anonymous> (C:\xampp\htdocs\parent-app\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:133:17)
    at Generator.next (C:\xampp\htdocs\parent-app\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:74:21)
    at asyncGeneratorStep (C:\xampp\htdocs\parent-app\node_modules\@babel\runtime\helpers\asyncToGenerator.js:3:24)
    at _next (C:\xampp\htdocs\parent-app\node_modules\@babel\runtime\helpers\asyncToGenerator.js:22:9)
    at C:\xampp\htdocs\parent-app\node_modules\@babel\runtime\helpers\asyncToGenerator.js:27:7
    at new Promise (<anonymous>)
    at C:\xampp\htdocs\parent-app\node_modules\@babel\runtime\helpers\asyncToGenerator.js:19:12
    at serverSideTranslations (C:\xampp\htdocs\parent-app\node_modules\next-i18next\dist\commonjs\serverSideTranslations.js:217:17)

Screenshots

No response

Additional context

No response

Unleash version

1.2.0

Subscription type

None

Hosting type

Self-hosted

SDK information (language and version)

No response

Metrics support

Describe the feature request

Support for metrics and app registration in server-side part of this SDK. In serverless/edge environments it's different then what we had before.

Background

Users who enabled metrics when creating a feature toggle expect it to show up.

Solution suggestions

We have 2 options:

A. Do it as-is, sending a request on every evaluation, but it will not be performant (not scalable).
B. Research alternative ways of batching metrics. Not a trivial task.

Doesn't work but unleash-client does; Can't use instance_id

Describe the bug

I've been using 'unleash-client' and everything works fine. Now I've switched on @unleash/nextjs and I can't connect to my GitLab FF API anymore.


How do I set instanceId?

How/where do I set global instanceId? Working solution with 'unleash-client':

const unleash = initialize({
      url: '....',
      instanceId: '....',
      appName: '....',
});
...
unleash.isEnabled('test'); // true!

I see no option for this in nextjs solution.


Possible feature request?
Possibility to leave auth headers blank (without token) and use instanceId instead (GitLab FF solution)

getFrontendFlags hangs indefinitely when unleash-proxy is not reachable

Describe the bug

https://github.com/Unleash/unleash-client-nextjs/blob/main/lib/src/getFrontendFlags.ts#L20-L23

The promise isn't rejected on error, so when "ready" is never triggered, the code hangs forever.

Steps to reproduce the bug

  1. Call getFrontendFlags without a running unleash-proxy available
  2. Observe that Unleash: unable to fetch feature toggles is logged, but promise remains unresolved.

Expected behavior

The promise should reject when it fails.

Logs, error output, etc.

No response

Screenshots

No response

Additional context

No response

Unleash version

No response

Subscription type

None

Hosting type

None

SDK information (language and version)

No response

Broken Next.js build: Cannot destructure property 'updateContext' of null

Describe the bug

Error occurred prerendering page "/page". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: Cannot destructure property 'updateContext' of 'i(...)' as it is null.
    at B (file:///home/danilo/salvy/salvy-dashboard/node_modules/@unleash/proxy-client-react/dist/unleash-react.js:115:26

I think it refers to this line of code: https://github.com/Unleash/proxy-client-react/blob/46fa764e20617e83fd060b15f327c83cfa73fa66/src/useUnleashContext.ts#L5

Steps to reproduce the bug

Create a Next.js project (Pages router)

Add the following provider at the _app.tsx file:

const SessionAwareFlagContextProvider = ({
  children,
}: {
  children: ReactNode;
}) => {
  const updateContext = useUnleashContext();

  useEffect(() => {
    updateContext({
      userId: "fake-user-id",
    });
  }, [updateContext]);

  return <>{children}</>;
};

Expected behavior

No response

Logs, error output, etc.

No response

Screenshots

No response

Additional context

No response

Unleash version

@unleash/nextjs 1.3.0

Subscription type

Open source

Hosting type

Self-hosted

SDK information (language and version)

No response

Upgrade to unleash-client 5.5.1

Describe the bug

NPM is warning of a security issue for ip, which was patched in unleash-client 5.5.1

@unleash/nextjs 1.3.0 (latest published) is using pinned version 5.0.0

Unleash/unleash-client-node#586

Steps to reproduce the bug

No response

Expected behavior

No response

Logs, error output, etc.

No response

Screenshots

No response

Additional context

No response

Unleash version

No response

Subscription type

None

Hosting type

None

SDK information (language and version)

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.