(New Next.js 13.4 App Router approach)
My implementation is just a simplified version of the React Router's <Navigate/> component. As noted in the React Router doc, "the v6 <Navigate /> uses push logic by default and you may change it via replace prop". So the default value for replace in my implementation will be false just for API consistency.
import {useRouter} from 'next/navigation'
import {useEffect} from 'react'
type NavigateProps = {to: string; replace?: boolean}
export default function Navigate({to, replace = false}: NavigateProps): null {
const router = useRouter()
useEffect(() => {
if (replace) {
router.replace(to)
} else {
router.push(to)
}
}, [replace, router, to])
return null
}
And you might consume it at the root layout that wraps all the routes and pages you want to protect (e.g. /protected/* routes):
// /app/protected/layout.tsx
'use client'
import {useAuth} from '@scope/react-auth'
import {Fragment, ReactNode} from 'react'
import Navigate from '~/components/Navigate'
export default function Layout({children}: {children: ReactNode}) {
return <RequireLogin>{children}</RequireLogin>
}
function RequireLogin({children}: {children: ReactNode}) {
const {currentUser} = useAuth()
return <Fragment>{currentUser ? children : <Navigate to="/ap/login" replace />}</Fragment>
}
⚠️ According to one of the authors of react-router, using render prop for private routes should be avoided in React Router v6.
⚠️ According to the new useRouter() API, the new useRouter hook should be imported from next/navigation and not next/router.