Skip to content

Admin-only links

In the previous guide, we learned how to protect routes in Remix by using the loader function to check if a user has the โ€˜adminโ€™ role.

Now that we have protected the admin review page, we need to provide a link in the navbar to the admin review page only for users with the โ€˜adminโ€™ role.

The problem is that our current function that checks the userโ€™s role throws an error if they are not an admin.

This wouldnโ€™t work in the case of a navbar link, as we still want users who are not admins to see the navbar but not the link.

This is actually a straightfoward fix, and we will do this in three stages:

Open app/utils/rootProviders/generateRootJson.ts and add the following code to the generateRootJson function that starts around line 43:

app/utils/rootProviders/generateRootJson.ts
export default function generateRootJson({
user,
request,
honeyProps,
toast,
csrfToken,
timings,
toastHeaders,
csrfCookieHeader,
}: GenerateRootJsonArgs) {
const pageData = {
user,
requestInfo: {
hints: getHints(request),
origin: getDomainUrl(request),
path: new URL(request.url).pathname,
userPrefs: {
theme: getTheme(request),
},
},
ENV: getEnv(),
toast,
honeyProps,
csrfToken,
isAdminUser: user ? user.roles.some(role => role.name === 'admin') : false,
}
const headerData = {
headers: combineHeaders(
{ 'Server-Timing': timings.toString() },
toastHeaders,
csrfCookieHeader ? { 'set-cookie': csrfCookieHeader } : null,
),
}
return { pageData, headerData }
}

Next, open app/root.tsx and add the following code to the HeaderWithSearch component (or whichever navbar component you are using):

app/root.tsx
export default function App() {
3 collapsed lines
const data = useLoaderData<typeof loader>()
const theme = useTheme()
const nonce = useNonce()
return (
<ParallaxProvider>
<AuthenticityTokenProvider token={data.csrfToken}>
<HoneypotProvider {...data.honeyProps}>
<Document nonce={nonce} theme={theme}>
<div className="flex h-screen flex-col justify-between">
<HeaderWithSearch isAdminUser={data.isAdminUser} />
10 collapsed lines
<div className="flex-1">
<Outlet />
</div>
<div className="fixed m-4 rounded-full bg-black/20 p-2 backdrop-blur-sm transition hover:bg-black/30 dark:bg-white/20 dark:hover:bg-white/30">
<ThemeSwitch
userPreference={data.requestInfo.userPrefs.theme}
/>
</div>
<FooterMenuRight companyName="Epic News" />
</div>
</Document>
</HoneypotProvider>
</AuthenticityTokenProvider>
</ParallaxProvider>
)
}

Finally, open app/components/organisms/HeaderWithSearch.tsx.

Add the following code to the HeaderWithSearch component:

app/components/organisms/HeaderWithSearch.tsx
import { Link, useMatches } from '@remix-run/react'
import logo from '~/assets/png/epic-news-logo.png'
import { SearchBar } from '../molecules/SearchBar'
import LoginOrUserDropdown from './LoginOrUserDropdown'
interface HeaderWithSearchProps {
isAdminUser: boolean
}
export default function HeaderWithSearch({ isAdminUser }: HeaderWithSearchProps) {
const matches = useMatches()
const isOnSearchPage = matches.find(m => m.id === 'routes/users+/index')
const searchBar = isOnSearchPage ? null : <SearchBar status="idle" />
return (
<header className="bg-emerald-50 py-6 dark:bg-emerald-950">
<nav className="container flex flex-wrap items-center justify-between gap-4 sm:flex-nowrap md:gap-8">
<Link to="/" className="group">
<div className="flex items-center gap-4 transition group-hover:opacity-80">
<img src={logo} alt="Epic News Logo" className="w-16" />
<span className="text-sm font-bold text-foreground">Epic News</span>
</div>
</Link>
<div className="flex flex-1 items-center justify-center gap-8">
{isAdminUser && (
<Link
to="/admin-review"
className="rounded-lg bg-green-900 px-4 py-2 text-sm font-semibold text-foreground transition hover:bg-green-800"
>
Admin Review
</Link>
)}
<Link
to="/news"
className="text-sm font-semibold text-foreground transition hover:text-foreground/80"
>
News
</Link>
13 collapsed lines
<Link
to="/about-us"
prefetch="intent"
className="text-sm font-semibold text-foreground transition hover:text-foreground/80"
>
About us
</Link>
<Link
to="/contact-us"
className="text-sm font-semibold text-foreground transition hover:text-foreground/80"
>
Contact us
</Link>
</div>
<div className="ml-auto hidden max-w-sm flex-1 sm:block">
{searchBar}
</div>
<div className="flex items-center gap-10">
<LoginOrUserDropdown />
</div>
<div className="block w-full sm:hidden">{searchBar}</div>
</nav>
</header>
)
}

Whilst logged in as a normal user, or when not logged in at all, you should not be able to see the new admin link in the navbar:

Navbar without admin link

Now, log out of the application completely.

Once successfully logged out, log back in with the following details:

Username: kody Password: kodylovesyou

Once you are logged in as โ€˜Kodyโ€™, you should now see the โ€œAdmin Reviewโ€ link has now appeared in the navbar:

Navbar with admin link

Click on this link, and you should be taken straight to the admin review page.

In this tutorial, we have:

  • Created an optional admin user check in the generateRootJson function.
  • Added an admin link to the navbar that is only visible to users with the โ€˜adminโ€™ role.