> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runonatlas.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js

> `@runonatlas/next` is our Next.js library for Atlas, which provides all the components, hooks and backend resources you need.

## Installation

<Tabs>
  <Tab title="npm">
    ```bash Terminal theme={null}
    npm install @runonatlas/next
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash Terminal theme={null}
    pnpm add @runonatlas/next
    ```
  </Tab>

  <Tab title="yarn">
    ```bash Terminal theme={null}
    yarn add @runonatlas/next
    ```
  </Tab>
</Tabs>

## Initialization

<Steps>
  <Step title="Create the Atlas Provider">
    To initialize the application, you need to create a client provider component and wrap your application with it. Since Atlas needs to authenticate with your backend and verify that users are only accessing what they can, you will need to place it under your authentication provider.

    It's very important that you provide all the required props:

    * `getAuth`: A function that returns the authentication token. *This is only necessary if the authentication with the backend is done with tokens and headers (for example, Clerk)*.
    * `loginCallback`: A function that is called whenever the user tries to do an action that requires authentication.
    * `userId`: The ID of the user.
    * `userEmail`: The email of the user. *Optional.*
    * `userName`: The name of the user. *Optional.*
    * `isUserLoading`: A boolean that indicates whether the user is still loading. *Optional.* Helps Atlas to avoid flickering when the user status changes.

    You then need to wrap your application with the `AtlasProvider`, wherever you have the rest of your providers and under your authentication provider.

    For example, with Clerk:

    <CodeGroup>
      ```tsx src/atlas/client.tsx expandable theme={null}
      "use client";
      import { useAuth, useUser } from "@clerk/nextjs";
      import { AtlasProvider } from "@runonatlas/next/client";
      import { redirect } from "next/navigation";

      const loginCallback = () => {
        redirect("/login");
      };

      export function AtlasClientProvider({
        children,
      }: {
        children: React.ReactNode;
      }) {
        const { userId, getToken, isLoaded } = useAuth();
        const { user } = useUser();

        return (
          <AtlasProvider
            getAuth={getToken}
            loginCallback={loginCallback}
            userId={userId}
            userEmail={user?.primaryEmailAddress?.emailAddress}
            userName={user?.fullName}
            isUserLoading={!isLoaded}
          >
            {children}
          </AtlasProvider>
        );
      }
      ```

      ```tsx src/app/layout.tsx expandable theme={null}
      import { ClerkProvider } from "@clerk/nextjs";
      import { AtlasClientProvider } from "./atlas/client";

      export default function RootLayout({
        children,
      }: Readonly<{
        children: React.ReactNode;
      }>) {
        return (
          <html>
            <body>
              <ClerkProvider>
                <AtlasClientProvider> {/** Notice how we don't have any html or body tags after the AtlasClientProvider */}
                  {/* Don't forget the rest of your providers and your application! */}
                  {children}
                </AtlasClientProvider>
              </ClerkProvider>
            </body>
          </html>
        );
      }

      ```
    </CodeGroup>
  </Step>

  <Step title="Create the Atlas API routes">
    You will need to initialize the client in the backend. To do so, you will need to:

    1. Set the Atlas API key in the environment variables.
    2. Create the Atlas Next Server Client, providing a callback that returns the user that performed the request. This callback will depend on the authentication provider you are using.
    3. Use the Atlas Next Server Client to create the API routes that will be used to access the Atlas API. They should be in the `src/app/api/atlas-api/[slug]/route.ts` file.

    The result should be like this:

    <CodeGroup>
      ```bash .env theme={null}
      ATLAS_API_KEY="INSERT_YOUR_ATLAS_API_KEY"
      ```

      ```typescript src/atlas/server.ts expandable theme={null}
      import { AtlasNextServerClient } from "@runonatlas/next/server";
      import { auth } from "@clerk/nextjs/server";

      export const atlasServerClient = new AtlasNextServerClient(
        async () => {
          const { userId } = await auth();
          return userId;
        }
      );
      ```

      ```typescript src/app/api/atlas-api/[[...slug]]/route.ts expandable theme={null}
      import { atlasServerClient } from "@/atlas/server";
      import { NextRequest } from "next/server";

      export async function GET(
        request: NextRequest,
        { params }: { params: Promise<{ slug: string[] }> }
      ) {
        const { slug } = await params;
        return atlasServerClient.handleRequest(request, slug);
      }

      export async function POST(
        request: NextRequest,
        { params }: { params: Promise<{ slug: string[] }> }
      ) {
        const { slug } = await params;
        return atlasServerClient.handleRequest(request, slug);
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

## Use our Atlas widgets

<Warning>
  Remember that you need to have followed the [installation
  guide](#installation) before you can use any of these widgets.
</Warning>

You can use any of our Atlas widgets in your Next application by importing them from `@runonatlas/next/client`.

### PricingComponent Widget

The `PricingComponent` widget shows the plans in your Atlas pricing model and allows users to purhcase a subscription to a plan via Atlas' in-line checkout.

**IMPORTANT:** The `successUrl` must be absolute. For example, `/success` is not valid, but `https://your-app.com/success` is valid.

```tsx theme={null}
"use client";
import { PricingComponent } from "@runonatlas/next/client";

/**
 * IMPORTANT:
 * Replace the `successUrl` prop with values specific to your application.
 */
export default function PricingPage() {
  return (
    <PricingComponent
      // Where to redirect after successful checkout.
      successUrl={"https://your-app.com/customer-portal"}
    />
  );
}
```

If you want to display your pricing model on a **public facing marketing site**, you can embed a hosted pricing page via `iFrame`. When a visitor selects a plan, they'll be redirected to your core application where the SDK is installed to sign in and complete the purchase.

To embed pricing on a website outside your app, use the `/pricing-embed` endpoint and pass the following URL query parameters:

* `publishableKey` - public key with read access to your pricing model (accessible in [settings/api-keys](https://app.runonatlas.com/settings/api-keys))
* `redirectTo` - absolute URL to redirect users to your app when they select a plan
* `successUrl` - absolute URL to return users to after they succesfully purchase a plan

**IMPORTANT:** The `redirectTo` and`successUrl` must be absolute. For example, `/success` is not valid, but `https://your-app.com/success` is valid.

```html theme={null}
<iframe
  src="https://app.runonatlas.com/pricing-embed?publishableKey=<YOUR_PUBLISHABLE_KEY>&redirectTo=<YOUR_REDIRECT_URL>&successUrl=<YOUR_SUCCESS_URL>"
  style="width: 100%; height: 650px; border: none;"
  title="Pricing"
  allowfullscreen
></iframe>
```

### CustomerPortalComponent Widget

The CustomerPortalComponent widget shows the current user what plan they are subscribed to, a history of their payments, and allows them to cancel or change their plan at any time.

**IMPORTANT:** The `successUrl` must be absolute. For example, `/success` is not valid, but `https://your-app.com/success` is valid.

```tsx theme={null}
"use client";
import { CustomerPortalComponent } from "@runonatlas/next/client";

/**
 * IMPORTANT:
 * Replace the `successUrl` prop with values specific to your application.
 */
export default function CustomerPortalPage() {
  return (
    <CustomerPortalComponent
      // Where to redirect after successful checkout.
      successUrl={"https://your-app.com/customer-portal"}
    />
  );
}
```

## Restrict user access based on their subscriptions

You can prevent users from accessing restricted parts of your application based on their subscription. To do so, it is important that you do it both in the UI and in the backend:

<Columns cols={2}>
  <Card title="UI Protection" icon="desktop" href="#ui-protection">
    Protecting the UI is the easiest way to prevent users from accessing
    restricted parts of your application.
  </Card>

  <Card title="Backend Protection" icon="server" href="#backend-protection">
    Protecting the UI is cool, but not enough! You need to protect the backend
    as well. This way, you can ensure that users cannot access restricted parts
    of your application even if they bypass the UI.
  </Card>
</Columns>

### UI Protection

#### Using hooks

You can use the `useFeaturesAllowed` hook to check if a user has access to a feature.

```tsx theme={null}
import { useFeaturesAllowed } from "@runonatlas/next/client";

export default function Home() {
  const { isAllowed } = useFeaturesAllowed(["feature-1", "feature-2"]);

  return (
    <div>
      Our system has determined that...
      {isAllowed
        ? "You have access to these features!"
        : "You don't have access to these features. Please upgrade your subscription!"}
    </div>
  );
}
```

Or you can use the `useCustomerFeatures` hook to get a users access status for all features in your pricing model:

```tsx theme={null}
import { useFeaturesAllowed } from "@runonatlas/next/client";

...

const { features } = useCustomerFeatures();
console.log(features); // { "data-explorer": { allowed: true, included: true, limit: 10, currentUsage: 5 }, "ai-assistant": { allowed: false, included: false } }
```

#### Using components

You can use the `FeatureProtect` component to check if a user has access to a feature.

```tsx theme={null}
import { FeatureProtect } from "@runonatlas/next/client";

export default function Home() {
  return (
    <FeatureProtect
      disallowedFallback={
        <div>Oh, you don't have access to these features. Upgrade now!</div>
      }
      features={["feature-1", "feature-2"]}
    >
      <div>Cool features</div>
    </FeatureProtect>
  );
}
```

### Backend Protection

Using Atlas in the backend is **very easy**. You just need to use the `atlasServerClient` that we created before to check if a user has access to a feature.
For example, if you are using Clerk:

```typescript src/app/api/example/route.ts expandable theme={null}
import { atlasServerClient } from "@/atlas/server";
import { auth } from "@clerk/nextjs/server";

export async function GET() {
  const { userId } = await auth();
  if (!userId) {
    return new Response("Unauthorized", { status: 401 });
  }

  const { ok } = await atlasServerClient.areFeaturesAllowed(userId, [
    "data-explorer",
  ]);

  if (!ok) {
    return new Response("You don't have this feature enabled", { status: 401 });
  }

  return new Response("Hello from the backend!!");
}
```

## Limit-based features

Sometimes, just having a feature as enabled or disabled is not enough, and our pricing models require limits to be set. For example, 5 users per account, or 20GB of storage.
Setting this up with Atlas is very easy. And, if at some point the limits change, you won't need to change the code again!

### Configuring the backend

The backend needs to understand what the limits are and how to check if the limit has been reached. To do so, you need to use the `atlasServerClient` and provide callbacks per each limit that you might want to use.

For example, given a feature whose id is `data-explorer`, you can provide a callback to check if the limit has been reached:

```typescript src/atlas/server.ts expandable {11,12,13,14,15,16,17} theme={null}
import { AtlasNextServerClient } from "@runonatlas/next/server";
import { auth } from "@clerk/nextjs/server";
import { prisma } from "./db";

export const atlasServerClient = new AtlasNextServerClient(
  async () => {
    const { userId } = await auth();
    return userId;
  },
  {
    limits: {
      "data-explorer": (userId: string) =>
        prisma.explorations.count({
          where: {
            userId,
          },
        }),
    },
  }
);
```

Now, every time you the application needs to check if the `data-explorer` feature is available, Atlas will use the callback to compute it.

<Accordion title="What happens if you don't configure a limit?" icon="server">
  By default, if you haven't configured a limit callback, Atlas will deny access to the feature if it has a limit. You can easily override this behavior. For example:

  ```typescript src/atlas/server.ts expandable {9,10,11} theme={null}
  import { AtlasNextServerClient } from "@runonatlas/next/server";
  import { auth } from "@clerk/nextjs/server";

  export const atlasServerClient = new AtlasNextServerClient(
    async () => {
      const { userId } = await auth();
      return userId;
    },
    {
      baseClientOptions: {
        allowUnknownLimits: true,
      },
    }
  );
  ```
</Accordion>

### Explanation in the UI

When using both the `<FeatureProtect>` component and the `useFeaturesAllowed()` hook, it will automatically check if the user has access to the features you are protecting AND if the limit has not been reached.

However, it is possible that you want to show your user why the access was denied! To to do this, we give you the reasons why the access was denied. For example, with the hook `useFeaturesAllowed`:

```tsx src/app/home/page.tsx expandable theme={null}
import { useFeaturesAllowed } from "@runonatlas/next/client";

export default function Home() {
  const { isAllowed, reasons } = useFeaturesAllowed(["feature-1", "feature-2"]);

  if (!isAllowed) {
    return (
      <div>
        Our system has determined that...
        {reasons.map((reason) => {
          if (reason.reason === "notIncluded") {
            return (
              <div key={reason.slug}>
                You need to upgrade your plan to access {reason.slug}
              </div>
            );
          }

          if (reason.reason === "limitReached") {
            return (
              <div key={reason.slug}>
                You have reached the limit for {reason.slug}. You currently have{" "}
                {reason.currentUsage}/{reason.limit}.
              </div>
            );
          }
        })}
      </div>
    );
  }

  return <div>Cool feature</div>;
}
```

In the `<FeatureProtect>` component, instead of directly providing the FallbackComponent, you can actually provide a function that returns the component to be rendered when the access is denied. For example:

```tsx src/app/home/page.tsx expandable theme={null}
import { FeatureProtect } from "@runonatlas/next/client";

export default function Home() {
  return (
    <FeatureProtect
      disallowedFallback={(reasons) => {
        return (
          <div>
            Our system has determined that...
            {reasons.map((reason) => {
              if (reason.reason === "notIncluded") {
                return (
                  <div key={reason.slug}>
                    You need to upgrade your plan to access {reason.slug}
                  </div>
                );
              }

              if (reason.reason === "limitReached") {
                return (
                  <div key={reason.slug}>
                    You have reached the limit for {reason.slug}. You currently
                    have {reason.currentUsage}/{reason.limit}.
                  </div>
                );
              }
            })}
          </div>
        );
      }}
      features={["feature-1", "feature-2"]}
    >
      <div>Cool features</div>
    </FeatureProtect>
  );
}
```

## Usage-based and credit-based features

<Warning>
  When using Next.js in a serverless environment (e.g., Vercel, Netlify), it is
  crucial to properly manage event batching to prevent data loss. See the
  [Batching events](#batching-events) section for required configurations.
</Warning>

Usage-based and credit-based features allow you to bill customers based on their consumption.
These features track usage events and apply billing according to your pricing model configuration.

### Understanding billing types

Atlas supports two types of billing for consumption-based features:

**Usage-based billing**: Events are rolled up for the entire billing period, the price is applied, and the user
is billed in arrears according to what they used. This is traditional usage-based billing where customers pay
for what they consume after the fact.

**Credit-based billing**: Users are allocated a specific amount of a custom pricing unit (e.g., credits) each month.
When an event is received in Atlas that matches a credit-based price, their balance of custom pricing units is
instantly deducted. This provides near real-time consumption tracking with immediate balance updates.

<Accordion title="Blocking consumption-based feature access and eventual consistency" icon="exclamation-triangle">
  Atlas offers the ability to block access to a usage-based and credit-based features
  via the **Max usage** and **Block overage** options, respectively.

  **Max usage**: (Number) The maximum amount of a usage-based feature a customer is allowed to consume within a single billing
  period. If specified, Atlas will block access after usage reaches the amount specified and then unblock access at the start
  of the next billing period. If not specified, Atlas will allow unlimited usage in any given billing period.

  **Block overage**: (Boolean) Whether or not to prevent a customer from drawing custom pricing unit balance negative.
  If enabled, Atlas will block access to a feature when a balance of 0 has been detected. Otherwise, Atlas will continue
  accepting events and further reduce the customers credit balance negative. The next allocation of custom pricing units
  will be applied as normal, increasing the customers balance from the current negative balance by the amount of the
  allocation.

  Because Atlas's event ingestion system is designed to be eventually
  consistent, it is possible for a small number of events to be processed before
  Atlas feature flagging has recognized that the customer has hit a balance of 0.
  This means that small overages are possible even if `blockOverage` is enabled.
  For more real time access control, please reach out to customer support at
  [support@runonatlas.com](mailto:support@runonatlas.com).
</Accordion>

### Configuring the backend

To bill your customers correctly, your application must send an event to Atlas each time a usage-based or credit-based feature is consumed.

This is done using the `atlasServerClient.enqueueFeatureEvents` method, followed by `atlasServerClient.flushEvents` to ensure the event is sent immediately. In a serverless environment, this two-step process is essential.

```tsx src/app/api/example/route.ts expandable {18-27} theme={null}
import { atlasServerClient } from "@/atlas/server";
import { auth } from "@clerk/nextjs/server";

export async function GET() {
  const { userId } = await auth();
  if (!userId) {
    return new Response("Unauthorized", { status: 401 });
  }

  const { ok } = await atlasServerClient.areFeaturesAllowed(userId, [
    "usage-based-feature",
  ]);

  if (!ok) {
    return new Response("You don't have this feature enabled", { status: 403 });
  }

  try {
    await atlasServerClient.enqueueFeatureEvents({
      featureIds: ["usage-based-feature"],
      customerId: userId,
    });

    await atlasServerClient.flushEvents(); // This will send the events immediately
  } catch (err) {
    console.error("Failed to enqueue feature events", err);
  }

  return new Response("Hello from the backend!!");
}
```

### Batching events

To optimize performance and avoid rate limits, the Atlas client automatically batches usage events before sending them.

You can customize this batching behavior by configuring the `eventsFlushAt` (number of events) and `eventsFlushInterval` (time in milliseconds) options during client initialization.

This will be very important if you are using a serverless backend, such as Vercel or Netlify, to ensure that events are sent before the serverless function is terminated.

```typescript src/atlas/server.ts [expandable] {9,10,11} theme={null}
import { AtlasNextServerClient } from "@runonatlas/next/server";
import { auth } from "@clerk/nextjs/server";

export const atlasServerClient = new AtlasNextServerClient(
  async () => {
    const { userId } = await auth();
    return userId;
  },
  {
    eventsFlushAt: 0,
    eventsFlushInterval: 0,
  }
);
```

You can also trigger a manual flush of all enqueued events at any time using the `flushEvents` method.

```typescript {11} theme={null}
import { atlasServerClient } from "@/atlas/server";

async function onProcessShutdown() {
  await atlasServerClient.flushEvents();
}
```

## Organizations

In many applications, it's common to allow multiple users to belong to the same organization and share a single subscription. For example: Alice creates an organization called **Standard Corp**, purchases a plan, and invites Bob and Carol to join. Since all three are members of the same organization, they should share access to the same plan and limits.

To enable this, you should use the **organization ID** as the user identifier instead of the individual user ID. This tells Atlas that access and usage should be scoped to the organization — not the individual — ensuring that Bob and Carol both have access under Standard Corp’s plan.

<CodeGroup>
  ```tsx src/atlas/client.tsx expandable {2,17,24,25} theme={null}
  "use client";
  import { useAuth, useUser, useOrganization } from "@clerk/nextjs";
  import { AtlasProvider } from "@runonatlas/next/client";
  import { redirect } from "next/navigation";

  const loginCallback = () => {
    redirect("/login");
  };

  export function AtlasClientProvider({
    children,
  }: {
    children: React.ReactNode;
  }) {
    const { userId, getToken, isLoaded } = useAuth();
    const { user } = useUser();
    const { organization } = useOrganization();

    return (
      <AtlasProvider
        getAuth={getToken}
        loginCallback={loginCallback}
        userEmail={user?.primaryEmailAddress?.emailAddress}
        userId={organization?.id}
        userName={organization?.name}
        isUserLoading={!isLoaded}
      >
        {children}
      </AtlasProvider>
    );
  }
  ```

  ```typescript src/atlas/server.ts expandable {6,7} theme={null}
  import { AtlasNextServerClient } from "@runonatlas/next/server";
  import { auth } from "@clerk/nextjs/server";

  export const atlasServerClient = new AtlasNextServerClient(async () => {
    const { orgId } = await auth();
    return orgId;
  });
  ```
</CodeGroup>
