I am using Lucia Auth for authentication.
This function returns back the session
object:
export const getPageSession = cache(async () => {
const authRequest = auth.handleRequest("GET", context)
const session = await authRequest.validate()
return session
})
"use client"
import React from "react"
import { redirect } from "next/navigation"
import NextImg from "next/image"
import ky, { KyResponse } from "ky"
import { getPageSession } from "@/app/auth/lucia"
import { BASE_URL } from "@/app/lib/constants"
const Admin = async () => {
const [admin, setAdmin] = React.useState(false)
React.useEffect(() => {
const fetchAdmin = async () => {
const res: KyResponse & { isAdmin: boolean } = await ky.get(`${BASE_URL}/api/admin`).json()
setAdmin(res.isAdmin)
}
fetchAdmin()
}, [])
const session = await getPageSession()
if (!session && !admin) redirect("/login")
return (
<div className="isolate bg-slate-900 text-white">
...
</div>
)
}
export default Admin
The first check is to check if session
exists because only logged-in users can access the page. It works completely fine.
And the second check is to check if its an admin
because only admin can access the page. This doesn't work.
I get this error:
You're importing a component that needs next/headers. That only works in a Server Component but one of its parents is marked with "use client", so it's a Client Component.
The getPageSession
is a server-side check and the React.useEffect
is a client call to the api that returns a json with a boolean like { isAdmin: false }
or { isAdmin: true }
How do I perform both checks without getting an error? I get too many errors regarding using Server Things in Client Component & the likes.
Ideally, I'd like a common function called isAdmin
that returns if someone is an admin or not. And if they are not, then redirect. If they are, then show the page.
How do I do it?
I found a solution. Not sure if its the best way to do it but here it is.
I created a Client Component to check if someone is an admin:
This solution doesn't work. I bet I am using useEffect
wrong somehow which can be fixed but the alternate solution below works fine.
"use client"
import React from "react"
import { redirect } from "next/navigation"
import ky, { KyResponse } from "ky"
import { BASE_URL } from "@/app/lib/constants"
export const AdminComponent = () => {
const [admin, setAdmin] = React.useState(false)
React.useEffect(() => {
const fetchAdmin = async () => {
const res: KyResponse & { isAdmin: boolean } = await ky.get(`${BASE_URL}/api/admin`).json()
setAdmin(res.isAdmin)
}
fetchAdmin()
}, [])
if (!admin) redirect("/dashboard")
return null
}
Then, I imported the Client Component into a Server Component.
import React from "react"
import { redirect } from "next/navigation"
import { getPageSession } from "@/app/auth/lucia"
import { AdminComponent } from "@/app/components/dashboard/AdminComponent"
const Admin = async () => {
const session = await getPageSession()
if (!session) redirect("/login")
return (
<div className="isolate bg-slate-900 text-white">
...
</div>
)
}
export default Admin
I also rewrote AdminComponent
as:
"use client"
import React from "react"
import { redirect } from "next/navigation"
import ky, { KyResponse } from "ky"
import { BASE_URL } from "@/app/lib/constants"
const fetchAdmin = React.cache(async () => {
const res: KyResponse & { isAdmin: boolean } = await ky.get(`${BASE_URL}/api/admin`).json()
return res.isAdmin
})
export const AdminComponent = () => {
const admin = React.use(fetchAdmin())
if (!admin) redirect("/dashboard")
return null
}
This looks much cleaner but not a common pattern as of now.
Would love to know if there is a more simpler or cleaner solution.
Edit:
Found a much better solution.
"use client"
import React from "react"
import { redirect } from "next/navigation"
import { useAdminStatus } from "@/app/hooks/useAdminStatus"
// eslint-disable-next-line react/display-name
export const AdminComponent = React.memo(() => {
const { data, error, isLoading } = useAdminStatus()
if (isLoading) return null
if (error) return <span className="text-slate-500">Failed to load `AdminComponent`</span>
if (!data.isAdmin) redirect("/dashboard")
return null
})
import useSWRImmutable from "swr/immutable"
const fetcher = (...args: [any]) => fetch(...args).then((res) => res.json())
export const useAdminStatus = () => {
const { data, error, isLoading } = useSWRImmutable(`/api/admin`, fetcher)
return {
data,
error,
isLoading,
}
}