Coming from React Router, Next.js route authentication patterns can seem confusing. Something you can accomplish in one line with React Router is a bit more complicated in Next.js.
Protected route with React Router:
{isAuthorized && <Route path='/about' ... />}
What is the Next.js equivalent of this code?
Disclaimer
In this article, we will only focus on static rendering authentication. It is the closest concept to the usual React implementation with React Router. If you want to do server-side authentication, check out the corresponding section of the docs. Now, let's move on.
Official documentation
Next.js documentation has this example of the protected route implementation:
// pages/profile.js
import useUser from '../lib/useUser'
import Layout from '../components/Layout'
const Profile = () => {
// Fetch the user client-side
const { user } = useUser({ redirectTo: '/login' })
// Server-render loading state
if (!user || user.isLoggedIn === false) {
return <Layout>Loading...</Layout>
}
// Once the user request finishes, show the user
return (
<Layout>
<h1>Your Profile</h1>
<pre>{JSON.stringify(user, null, 2)}</pre>
</Layout>
)
}
export default Profile
But do you repeat this code for every single page? What if you want to add some additional logic, like user types? Should you repeat this on every page too? Well, not really.
Move user logic to _app.js
To get more control over the routes, we can modify default _app.js
.
// pages/_app.js
import { UserContext } from "../components/user";
function MyApp({ Component, pageProps }) {
const [user, setUser] = useState(null);
useEffect(() => {
/**
* Here goes the logic of retrieving a user
* from the backend and redirecting
* an unauthorized user
* to the login page
*/
setUser(result)
}, []);
if (pageProps.protected && !user) {
return (
<Layout>Loading...</Layout>
)
}
return (
<UserContext.Provider value={user}>
<Component {...pageProps} />
</UserContext.Provider>
);
}
// components/user.js
import { createContext, useContext } from "react";
export const UserContext = createContext(null);
export const useUser = () => {
return useContext(UserContext);
};
Now we can use getStaticProps
to make any page protected.
// pages/profile.js
const Profile = () => {
return (
<Layout>
<h1>Your Profile</h1>
</Layout>
)
}
export async function getStaticProps(context) {
return {
props: {
protected: true
}
};
}
export default Profile
User types
Let's add user types logic to our _app.js
.
// pages/_app.js
function MyApp({ Component, pageProps }) {
const [user, setUser] = useState(null);
...
if (pageProps.protected && !user) {
return (
<Layout>Loading...</Layout>
)
}
if (
pageProps.protected &&
user &&
pageProps.userTypes &&
pageProps.userTypes.indexOf(user.type) === -1
) {
return <Layout>Sorry, you don't have access</Layout>;
}
return (
<UserContext.Provider value={user}>
<Component {...pageProps} />
</UserContext.Provider>
);
}
Now we can specify allowed user types for a protected route.
// pages/admin.js
const Admin = () => {
const { user } = useUser()
return (
<Layout>
<h1>Hi {user.name}</h1>
<p>Welcome to admin dashboard</p>
</Layout>
)
}
export async function getStaticProps(context) {
return {
props: {
protected: true,
userTypes: ['admin']
}
};
}
export default Admin
That's it. Here is a demo. I hope this helps to create protected routes in Next.js for those coming from React Router.
Originally published at alexsidorenko.com
Top comments (0)