DEV Community

Rohit Pujari
Rohit Pujari

Posted on • Edited on

Multi-tenant analytics for Next.js Apps

In the previous blog post, we explored how you can render interactive analytics in your Next.js app with just a few lines of code.

In this post, we'll discuss how you can deliver a multi-tenant analytics experience in your Next.js app. Specifically, we'll examine how to serve the same dashboard to multiple tenants (clients or organizations) while keeping each tenant's data isolated.

Semaphor offers two ways to implement multi-tenancy: connection-level security and row-level security. Connection-level security allows you to modify parts of the connection string (e.g., database name or user) at runtime. This is ideal if you have dedicated databases, database users, or folders (in the case of Amazon S3) for each client. Row-level security, on the other hand, provides logical isolation within a single shared table, displaying only the rows relevant to each tenant.

In this post we’ll focus on multi-tenancy using row-level security. Imagine you're a SaaS company providing embedded dashboards to multiple tenants, such as Tenant A, Tenant B, and Tenant C. These companies access their dashboards from within your product, but they can see only their own data, depending on the tenancy and the user.

For illustration, let's assume a simple data isolation scenario:

  • Tenant A users can only see data for eastern US states (NY, CT, FL)
  • Tenant B users can see data for central US states (IL, TX, OH)
  • Tenant C users can see data for western US states (CA, WA, NV)

Multi-tenant Demo

Let's see how we can implement this in Semaphor.

Before we begin, we assume that you already know how to integrate Semaphor dashboard into your Next.js app, or you can follow the instructions here to bootstrap a demo app.

To get started quickly, just clone this Git repository and run it locally. You’ll have a demo dashboard with row-level security set up in no time.

git clone https://github.com/rohitspujari/nextjs-semaphor-rls-demo.git
cd nextjs-semaphor-rls-demo
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

In the remainder of the post, we'll highlight the steps needed to enable row-level security.

Step 1: Define Row-Level Policy in Semaphor Console.

Navigate to your project in the Semaphor console. You can request a free trial here. You should see a "demo project" created for you when you first logged in. Under the "Security" tab, navigate to "Row/Column Level Security." Create a new policy as shown below. Make sure to allow all columns.

Row-level Policy

Step 2: Generate AuthToken with this policy.

In Semaphor, the AuthToken defines what a user can see on the dashboard. When rendering a dashboard for a specific tenant, you must include the row-level policy when you generate their AuthToken. The row-level security policy acts like a "hard" filter, restricting user's view to only the rows permitted by the policy.

For demonstration, we have used URL search parameters to determine which tenant's dashboard to render. In a production environment, you would likely use authentication logic to derive these parameters.

Tenant A: http://localhost:3000/?tenant=a

Tenant B: http://localhost:3000/?tenant=b

Tenant C: http://localhost:3000/?tenant=c

export const revalidate = 0; // This is to disable the Nextjs caching behavior.
import { AuthToken } from 'semaphor';
import DashboardComponent from './components/dashboard-component';
import { postRequest } from '@/server-utils';

const DASHBOARD_ID = 'd_a1a5275b-5ded-411c-a2bb-bfafd55e7b26'; // Replace with your actual dashboard ID
const DASHBOARD_SECRET = 'ds_13353da5-e413-46cb-a8af-fd58192aab11'; // Replace with your actual dashboard secret
const TOKEN_URL = 'https://semaphor.cloud/api/v1/token';

export default async function HomePage({
  searchParams,
}: {
  searchParams: { [key: string]: string | string[] | undefined };
}) {

  // extract tenant from search parameter
  const tenant = searchParams.tenant as string | undefined;

  // select the states
  let states: string[] = [];
  switch (tenant) {
    case 'a':
      states = ['New York', 'Connecticut', 'Florida'];
      break;
    case 'b':
      states = ['Texas', 'Illinois', 'Ohio'];
      break;
    case 'c':
      states = ['California', 'Nevada', 'Washington'];
      break;
    default:
      states = [];
      break;
  }

  // create a policy with the above states
  const rowPolicy = {
    name: 'states',
    params: {
      states,
    },
  };

    // generate auth token with the policy
  const token = await postRequest<AuthToken>(TOKEN_URL, {
    dashboardId: DASHBOARD_ID,
    dashboardSecret: DASHBOARD_SECRET,
    rcls: states.length > 1 ? [rowPolicy] : [], // only pass the row policy if there are multiple states
  });

  return (
      <DashboardComponent key={token.accessToken} authToken={token} />;
  );
}

Enter fullscreen mode Exit fullscreen mode

With these simple modifications, you can enable multi-tenancy for your dashboard, allowing different clients or organizations to access their specific data through a single dashboard instance.

This approach ensures data isolation and customized views for each tenant, enhancing both security and user experience.

If you have any questions, feel free to get in touch with us at support@semaphor.cloud.

Top comments (0)