Blog

Embedding A Luzmo Dashboard In Next.js Apps Using Clerk on Vercel

Embedded Analytics
Jul 22, 2024
Embedding A Luzmo Dashboard In Next.js Apps Using Clerk on Vercel

When presenting customer data or visualizations in user-facing applications, security is super important. It's crucial to ensure that users can only access the data they are authorized to view.

All authentication and authorization solutions provide ways to add users to groups, or apply roles to their profiles. By accessing these roles in your application, you can limit or restrict access to parts of your application that may contain sensitive customer data.

In this article, we’ll show you how we’ve used Clerk’s user metadata to assign a role to a user's profile. From within a Next.js application, we’ve then used the role to determine which one of two different data visualization dashboards created in Luzmo to display.

Table of contents

  1. Why use Next.js with Vercel for your application?
  2. What we're building
  3. Getting started with Next.js
  4. Setting up authentication in Clerk
  5. Luzmo API
  6. Setting up collections in Luzmo
  7. Embedding Luzmo's dashboard component
  8. Deploy to production
  9. Conclusion

We’ve chosen to use Next.js as it’s the first framework to support React’s new Server Components (RSCs). When using RSCs, pages or components are rendered on the server only. This allows developers to write code that makes server-side requests, or use environment variables without worrying about leaking sensitive information to the client (browser). 

This is super powerful when building applications that need to apply conditional logic based on a user’s profile information which won’t be exposed to the client. 

We’ll build a Help Desk application that allows users to sign in with their Google Account, and based on the role assigned to their profile, defined in Clerk, we’ll determine which of the below Luzmo dashboards will be displayed. 

All the code referenced in this article can be found on this GitHub repo.

Manager Dashboard

Our manager dashboard is only visible to users with a role of manager and displays information relating to customer satisfaction, quantity of tickets resolved, the average resolution time and the top recurring issues.

A screenshot of the Luzmo manager dashboard displaying various charts.

Support Dashboard

Our support dashboard is visible to users with a role that is not a manager and displays information relating to new tickets created, a list of overdue tickets and new tickets categorized by severity.

A screenshot of the Luzmo support dashboard displaying various charts.

To build a Next.js application using the App router, run the following in your terminal.

npx create-next-app@latest

Visit the Next.js documentation for step-by-step guidance on installation.

With a Next.js application setup, you can now add Clerk.

If you don’t have an account with Clerk, sign up here to get started. 

Once you’ve signed in, you can create a new application.

A screenshot of Clerk's UI displaying create application button.

You can now give your application a name, and configure which sign-in options you’d like to use. In our application, we’ve disabled Email and selected Google as the only method that can be used by users to sign in to our app. Choose the options that best suit your requirements and when you’re ready, click Create application.

A screenshot of Clerk's UI displaying authentication method options.

In the next step, select Next.js from the available quick-start guides and complete step 1 and 2. Steps 3 and 4 will be slightly different for the purposes of this article. Below, we’ve explained what the differences are, and why we made the changes.

Update middleware.ts

In step 3, instead of using the code snippet shown in the setup guide, use the one below. 

// src/middleware.ts

import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) auth().protect();
});

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};

The changes we’ve made introduce a protected route named /dashboard. Only signed-in users will have access to this route.

Add ClerkProvider to your app

In step 4, instead of using the code snippet shown in the setup guide, use the one below.

// src/app/layout.tsx

import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { ClerkProvider, SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/nextjs';
import './globals.css';
import Link from 'next/link';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <ClerkProvider>
      <html lang='en'>
        <body className={`${inter.className} m-0`}>
          <header className='p-4 shadow'>
            <nav className='flex items-center justify-between'>
              <ul className='flex gap-4'>
                <li>
                  <Link href='/'>Home</Link>
                </li>
                <li>
                  <Link href='/dashboard'>Dashboard</Link>
                </li>
              </ul>
              <SignedOut>
                <SignInButton />
              </SignedOut>
              <SignedIn>
                <UserButton />
              </SignedIn>
            </nav>
          </header>
          <main className='min-h-screen'>{children}</main>
        </body>
      </html>
    </ClerkProvider>
  );
}

The changes we’ve made are to include an HTML header element that contains two links, one to the /dashboard, and one back to the index page. We’ve also added some CSS to more neatly position navigation links and the sign-in button. 

You can also modify the index page. In our app, we’ve removed the HTML included by the Next.js installation and returned null.

// src/app/page.tsx

export default function Home() {
  return null;
}

Here’s a screenshot of our app.

A screenshot of our demo application displaying the navigation items.

Create the dashboard route

The last step is to create a new route which will be accessible at /dashboard.

Create a new directory named dashboard in src/app. Now create a new file named page.tsx and add the following code. We’ll be adding to this later, but for now you can test that only signed-in users can access this page.

// src/app/dashboard/page.tsx

export default async function Dashboard() {
  return <h1>This is the Dashboard</h1>;
}

With the page setup, you can now navigate to it using the link. If you aren’t signed in, you’ll be redirected to a Clerk sign-in page. If you are signed in, you should see the text returned in the HTML h1 element from the above code snippet.  

Add Clerk metadata

If you’ve signed in once, head over to your Clerk dashboard and you should see yourself appear in the users list. Click on the item and then scroll down the page until you see the Metadata section. 

Click the Edit button under the Public section.

A screenshot of Clerk's UI displaying public metadata section.

In the modal that opens up, add a key for role, and a value of manager, then click Save

A screenshot of Clerk's UI displaying public metadata JSON object.

This metadata is what we’ll use to determine who has access to the Help Desk Manager dashboard. If a user doesn’t have this role in their metadata, they’ll only be able to view the Help Desk Support dashboard. This is the default until the role is manually added in Clerk.

But, before we go too much further, there are some Luzmo steps we’ll need to complete. 

To embed Luzmo dashboards in your application, you’ll need to create an API key and API token. To do this, head over to Profile > API tokens and click Create API token

It’s helpful to give your token a description so you know which application uses it.

 A screenshot of Luzmo UI displaying an API Key and Token.

Add two new environment variables to your .env.local file and add the API key and API token.

// .env.local

LUZMO_API_KEY=
LUZMO_API_TOKEN=

Luzmo Host and Server

There’s two additional pieces of Luzmo information we’ll need. These are the Luzmo App Server and Luzmo API Host.

Add the following to your env.local file. These environment variables aren’t sensitive and have been prefixed with NEXT_PUBLIC so they can be referenced by client-side code.

// .env.local

NEXT_PUBLIC_LUZMO_APP_SERVER=
NEXT_PUBLIC_LUZMO_API_HOST=

Depending on which region your account has been created in, you’ll either use the US or EU addresses listed below. 

Ensure you don’t include a trailing slash with any of the URLs. 

With all the required environment variables set, you can now start creating collections

In this step, we’ll create the two collections: one for the manager dashboard and one for the support dashboard. From your Luzmo homepage, create two new collections. For our app, we’ve created one named manager and one named support.

A screenshot of Luzmo UI highlighting my collections.

Once you’ve created your collections, click on each one and make a note of the URL in your browser's address bar, this contains the collection id. Add each one to your environment variables as we’ll need them in a later step.

// .env.local

MANAGER_LUZMO_COLLECTION_ID=
SUPPORT_LUZMO_COLLECTION_ID=

Add dashboards to collections 

In this step, we’ll add each dashboard to the relevant collection so that the two are associated. 

Click the “more dots” in the top right of the dashboard thumbnail and select Add to collection

A screenshot of Luzmo UI displaying a dropdown menu with add to collection highlighted.

From the modal that opens up, select the collection you’d like to add the dashboard to. In our app, we’ve added the Help Desk Manager dashboard to the manager collection and the Help Desk Support dashboard to the support collection. When you’re ready, click Apply.

A screenshot of Luzmo UI displaying a modal with the manager collection checkbox ticked.

Dashboards can be added to, or removed from collections at any time, all from the Luzmo dashboard. In a later step, we’ll show you how you can display dashboards associated with collections for users with different permissions.

In this step, we’ll install the dependencies required to use Luzmo’s embeddable dashboard component, and create a new React component that accepts props that will be used to render the correct dashboard for each user. 

To install Luzmo’s dashboard component, run the following in your terminal.

npm install @luzmo/react-embed

Next, create a new directory inside the src/app directory named components. Now create a new file named luzmo-dashboard.tsx and add the following code.

// src/app/components/luzmo-dashboard.tsx

'use client';

import { LuzmoDashboardComponent } from '@luzmo/react-embed';

interface Props {
  authKey: string;
  authToken: string;
  dashboards: Dashboard[];
}
interface Dashboard {
  id: string;
  type: 'dashboard';
  rights: {
    Own: boolean;
    Use: boolean;
    Read: boolean;
    Modify: boolean;
  };
}

export default function LuzmoDashboard({ authKey, authToken, dashboards }: Props) {
  // Check if the dashboards array is not empty
  if (dashboards.length === 0) {
    return null;
  }

  // Get the first dashboard
  const firstDashboard = dashboards[0];
  const { id } = firstDashboard;

  return (
    <LuzmoDashboardComponent
      appServer={process.env.NEXT_PUBLIC_LUZMO_APP_SERVER}
      apiHost={process.env.NEXT_PUBLIC_LUZMO_API_HOST}
      authKey={authKey}
      authToken={authToken}
      dashboardId={id}
    ></LuzmoDashboardComponent>
  );
}

Notice at the top of the file we’re using the 'use client' directive. This informs React/Next.js that this component is to be used by the browser and will require client-side JavaScript to work.

Props

This component accepts the following props; authKey, authToken and dashboards.

The values for all three props are returned by a separate Luzmo API request (which we’ll cover next). The authKey and authToken provide access to the dashboard, and dashboards is an array of available dashboards in the collection. To return the first dashboard in the collection we can target it using dashboards[0] and destructure the id and pass it onto the component using the dashboardId prop.

You can read more about the Embedding in the Luzmo documentation.

Update /dashboard page

This is the final step where we’ll update the dashboard page. We'll include the necessary Clerk hook to determine the current signed-in user, then make an API request to the Luzmo API to grant authorization to one of the two collections. 

To complete this step, you'll need to install Luzmo's Node.js SDK.

npm install @luzmo/nodejs-sdk

Update src/app/dashboard/page.tsx with the following code.

+ import Luzmo from '@luzmo/nodejs-sdk';
+ import { currentUser } from '@clerk/nextjs/server';
+ import LuzmoDashboard from '../components/luzmo-dashboard';

+ const client = new Luzmo({
+   api_key: process.env.LUZMO_API_KEY!,
+   api_token: process.env.LUZMO_API_TOKEN!,
+   host: process.env.NEXT_PUBLIC_LUZMO_API_HOST!,
+ }) as any;

export default async function Dashboard() {
-   return <h1>This is the Dashboard</h1>;
+   const user = await currentUser();

+   const response = await client.create('authorization', {
+     type: 'embed',
+     username: user!.id,
+     name: `${user!.firstName} ${user!.lastName}`,
+     email: user!.emailAddresses[0].emailAddress,
+     access: {
+       collections: [
+         {
+           id:
+             user!.publicMetadata.role === 'manager'
+               ? process.env.MANAGER_LUZMO_COLLECTION_ID
+               : process.env.SUPPORT_LUZMO_COLLECTION_ID,
+           inheritRights: 'use',
+         },
+       ],
+     },
+   });

+   const {
+     id,
+     token,
+     access: { dashboards },
+   } = await response;

+   return <LuzmoDashboard authKey={id} authToken={token} dashboards={dashboards} />;
}

The first part of the Dashboard page deals with using the Clerk/Next.js currentUser hook which returns details about the current signed-in user. From the response we can access the user.id, user.firstName, user.lastName, user.emailAddresses and the user.publicMetadata.

The user details are used in the Luzmo authorization request which creates the authKey and authToken required to grant permission to query the collections, and the user publicMetadata is how we determine if a user has a role of manager, in which case we request the manager collection, if not, the support collection.

You can read more about Luzmo’s authorization endpoint in the documentation.

This all happens securely server-side and the id, token and dashboards from the response of the authorization request are passed on to the LuzmoDashboard component you created earlier.

If you’re ready to deploy your Next.js application, follow the steps as outlined in the Vercel documentation. Similarly, there are some additional steps to complete when using Clerk in production. You can read more about deploying in the Clerk documentation.

And that’s it. Using a combination of Luzmo collections which contain specific dashboards and combining them with Clerk’s metadata, you can conditionally determine which authenticated users are authorized to access dashboards associated with a collection.

If you ever need to make permission changes, these can all be managed by your existing auth provider, no changes in Luzmo would be required. And lastly, in our Next.js app, all the logic happens server-side so there’s no chance an unauthenticated, or unauthorized user will ever see sensitive customer data, unless permitted to do so.

If you need to build secure analytics quickly, start your free Luzmo trial to get started today!

Paul Scanlon

Paul Scanlon

Independent Developer Advocate

Paul is a Senior Software Engineer, Independent Developer Advocate and Technical Writer. He has ~20 years of experience and regularly writes for major tech publications such as The New Stack, Smashing Magazine and CSS Tricks. More from Paul can be found on his website, paulie.dev.

Build your first embedded dashboard in less than 15 min

Experience the power of Luzmo. Talk to our product experts for a guided demo  or get your hands dirty with a free 10-day trial.

Dashboard