RBAC & Private Routes in Next.js with Clerk using NextShield.
NextShield + Clerk Example

I'm an engineer that loves JavaScript technologies and Linux, currently learning Go.
Search for a command to run...
NextShield + Clerk Example

I'm an engineer that loves JavaScript technologies and Linux, currently learning Go.
No comments yet. Be the first to comment.
NextShield + Supabase Example.

Get RBAC & Private Routes in your Next.js app with any auth provider using NextShield.

Learn how to create email templates with react and send them with SMTP.js

As you maybe already know, the flutter's 2.0 stable channel already integrates the feature of building apps for desktop, but it stills in beta, you have to consider that. In this post, I'm going to show you how to generate the build for Windows and c...

Hello everyone! I'm Julián.
The past few weeks I've introduced my library called NextShield and show an example using it with Supabase. Today's example is using NextShield with Clerk, so let's get started 🚀.

Sign Up:

Create application:
Standard Setup:

That's it, let's create a Next.js app with NextShield.
For this tutorial you need to create a Next.js app configured with NextShield, to do so, you can follow the instructions of my first post or just clone the repo (clone the main branch).
That's it, let's set up the NextShield with Clerk.
npm i @clerk/nextjs


Instance Configuration section to get the Frontend API key:
.env.local file at the root of the project and setup the following variable with your key:NEXT_PUBLIC_CLERK_FRONTEND_API="FRONTEND API KEY HERE"
Settings -> URL & Redirects -> Component URLs:
Enable Use Custom URL on each field and write the following values:

Settings -> User management select your preferred providers.
_app.tsx and put the ClerkProvider:import type { AppProps } from 'next/app'
import { ClerkProvider } from '@clerk/nextjs'
import { Shield } from '@/components/routes/Shield'
import '@/styles/globals.css'
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<ClerkProvider>
<Shield>
<Component {...pageProps} />
</Shield>
</ClerkProvider>
)
}
pages directory with the following folder structure:
Nav component adding the sign in & out routes:import Link from 'next/link'
export function Nav() {
return (
<nav>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/sign-up">
<a>Sign Up</a>
</Link>
<Link href="/sign-in">
<a>Sign In</a>
</Link>
<Link href="/pricing">
<a>Pricing</a>
</Link>
<Link href="/profile">
<a>Profile</a>
</Link>
<Link href="/dashboard">
<a>Dashboard</a>
</Link>
<Link href="/users">
<a>Users</a>
</Link>
<Link href="/users/1">
<a>Single User</a>
</Link>
</nav>
)
}
index.tsx and pricing.tsx files:import { Layout } from '@/components/routes/Layout'
export default function PageName() {
return (
<Layout title="PageName">
<h1>PageName</h1>
</Layout>
)
}
dashboard.tsx, users/index.tsx and users/[id].tsx files, update the content to:import { Layout } from '@/components/routes/Layout'
import { UserButton } from '@clerk/nextjs'
export default function PageName() {
return (
<Layout title="PageName">
<h1>PageName</h1>
<div className="flex">
<UserButton />
</div>
</Layout>
)
}
Finally, add the Clerk components on the pages that we defined in our dashboard:
sign-up/[[...index]].tsx:import { SignUp } from '@clerk/nextjs'
export default function SignUpPage() {
return (
<>
<SignUp path="/sign-up" routing="path" signInUrl="/sign-in" />
</>
)
}
sign-in/[[...index]].tsx:import { SignIn } from '@clerk/nextjs'
export default function SignInPage() {
return (
<>
<SignIn path="/sign-in" routing="path" signUpUrl="/sign-up" />
</>
)
}
And the profile/[[...index]].tsx:
import { Layout } from '@/components/routes/Layout'
import { UserProfile } from '@clerk/clerk-react'
export default function Profile() {
return (
<Layout title="Profile">
<UserProfile path="/profile" routing="path" />
</Layout>
)
}
Finally the interesting part 🥳.
Shield component and import:import { useUser } from '@clerk/nextjs'
withAssertions set to true, this is because in that way we can access to isLoading and isSignedIn functions, which are must to have in NextShield parameters.const { user, isLoading, isSignedIn } = useUser({ withAssertions: true })
NextShieldProps with the following:const shieldProps: NextShieldProps<
['/profile/[[...index]]', '/dashboard', '/users', '/users/[id]'],
['/', '/sign-in', '/sign-up']
> = {
router,
isAuth: isSignedIn(user),
isLoading: isLoading(user),
privateRoutes: ['/profile/[[...index]]', '/dashboard', '/users', '/users/[id]'],
publicRoutes: ['/', '/sign-in', '/sign-up'],
hybridRoutes: ['/pricing'],
loginRoute: '/sign-in',
accessRoute: '/dashboard',
LoadingComponent: <Loading />,
}
import { useRouter } from 'next/router'
import { NextShield, NextShieldProps } from 'next-shield'
import { useUser } from '@clerk/nextjs'
import { Children } from '@/types/Components'
import { Loading } from './Loading'
export function Shield({ children }: Children) {
const router = useRouter()
const { user, isLoading, isSignedIn } = useUser({ withAssertions: true })
const shieldProps: NextShieldProps<
['/profile/[[...index]]', '/dashboard', '/users', '/users/[id]'],
['/', '/sign-in', '/sign-up']
> = {
router,
isAuth: isSignedIn(user),
isLoading: isLoading(user),
privateRoutes: ['/profile/[[...index]]', '/dashboard', '/users', '/users/[id]'],
publicRoutes: ['/', '/sign-in', '/sign-up'],
hybridRoutes: ['/pricing'],
loginRoute: '/sign-in',
accessRoute: '/dashboard',
LoadingComponent: <Loading />,
}
return <NextShield {...shieldProps}>{children}</NextShield>
}


Let's move things further and add RBAC.
Users section on your dashboard and select your user:
Metadata section and edit the public data:

types/User.ts:export type Metadata =
| {
role?: string
}
| undefined
Shield component, and create a constant with that type: const { user, isLoading, isSignedIn } = useUser({ withAssertions: true })
const userMetadata: Metadata = user?.publicMetadata
const shieldProps: NextShieldProps<
['/profile/[[...index]]', '/dashboard', '/users', '/users/[id]'],
['/', '/sign-in', '/sign-up']
> = {
router,
isAuth: isSignedIn(user),
isLoading: isLoading(user),
privateRoutes: ['/profile/[[...index]]', '/dashboard', '/users', '/users/[id]'],
publicRoutes: ['/', '/sign-in', '/sign-up'],
hybridRoutes: ['/pricing'],
loginRoute: '/sign-in',
LoadingComponent: <Loading />,
RBAC: {
ADMIN: {
grantedRoutes: ['/dashboard', '/profile/[[...index]]', '/users', '/users/[id]'],
accessRoute: '/dashboard',
},
EMPLOYEE: {
grantedRoutes: ['/profile/[[...index]]', '/dashboard'],
accessRoute: '/profile/[[...index]]',
},
},
userRole: userMetadata?.role,
}

You can move things one step further and protect your components depending on the user's role or a custom condition, even if that user has access to that route.
Test this code in your pricing page and tell me what you think in the comments 😏:
import { useUser } from '@clerk/clerk-react'
import { ComponentShield } from 'next-shield'
import { Layout } from '@/components/routes/Layout'
import { Metadata } from '@/types/User'
export default function Pricing() {
const { user, isLoading, isSignedIn } = useUser({ withAssertions: true })
const userMetadata: Metadata = user?.publicMetadata
return (
<Layout title="Pricing">
<h1>Pricing</h1>
<ComponentShield
showIf={isSignedIn(user) && !isLoading(user)}
fallback={<p>You are unauthorized</p>}
>
<p>You are authorized</p>
</ComponentShield>
<ComponentShield RBAC showForRole="ADMIN" userRole={userMetadata?.role}>
<p>You are an admin</p>
</ComponentShield>
</Layout>
)
}
NextShield & Clerk make a perfect symbiosis!!!

You can get the completed example here.
Also you can read the complete NextShield series.
Hope you like it, see you next time!
