- Published on
- Published:
- Updated on
- Updated:
- Reading time
- 7 min read
How to create a protected route in Next.js using middleware or getServerSideProps
- Authors
- Name
- Nico Botha
- @nwbotha
What is Next.js?
Next.js is a JavaScript framework for building server-side-rendered React applications. It provides a powerful set of features for web and mobile applications such as automatic code splitting, server-side rendering, and static site generation. Next.js also allows developers to build highly performant and scalable applications with minimal setup and configuration. With an unmatched development experience, it helps developers easily build, test, and deploy their applications with minimal configuration.
getServerSideProps
How to protect a route in Next.js When rendering a page in Next.js, there are three different methods for fetching data:
getStaticProps
getStaticPaths
(used in combination withgetStaticProps
)getServerSideProps
To read more about the getStaticProps
and getStaticPaths
functions and when to use which, check out the official Next.js docs.
This post will focus on the getServerSideProps
function. This function is executed every time a page is requested and is normally used for pages that can't be statically rendered.
To protect a route in Next.js the general logic is:
- Check if a user is authenticated
- If they are authenticated, fetch data and render the page
- If they are not authenticated, redirect the user to the login page or return an "unauthorized" response
Your logic might be different depending on your specific use case, but for this post, the above logic will be used.
To generically protect a server-side route use the following code:
export default async function ProtectedPageRoute(
context,
redirectTo, // string route where user will be redirected if they are not authenticated
getProps, // function to fetch initial props
) {
const userIsAuthenticated = true // TODO: check if user is authenticated
if (!userIsAuthenticated) {
return {
redirect: {
destination: redirectTo ?? '/signin',
permanent: false,
}
}
}
if (getProps) {
return {
props: getProps(),
}
}
return {
props: {},
}
}
And use it like this:
export const getServerSideProps = (context) =>
ProtectedPageRoute(context, null, async () => {
// fetch props
});
Similarly, Next.js also supports API routes which can be protected like this:
export default async function ProtectedApiRoute(
req,
res,
requestHandler,
) {
const userIsAuthenticated = true // TODO: check if user is authenticated
if (!userIsAuthenticated) {
return res
.status(401)
.json({
error: {
code: 'unauthorized',
message: 'User is not authorized',
}
})
}
if (requestHandler) {
return requestHandler(req, res)
}
return res
.status(400)
.json({
error: {
code: 'no_request_handler_specified',
message: 'No request handler specified',
}
})
}
And use it like this:
export default function handler(req, res) {
return ApiProtectedRoute(req, res, (req, res) => {
// fetch data
});
}
The main difference between the ProtectedPageRoute
and ProtectedApiRoute
functions is that the ProtectedPageRoute
redirects the user to the login page (307 response - temporary redirect) whereas the ProtectedApiRoute
returns a 401 response when a user is not authenticated. How you handle the 401 response client-side depends on your app (i.e. show a popup, redirect, etc.).
Fetching data server-side or client-side?
The getServerSideProps
function runs on the server before a page is rendered. This means that your API call has to finish before any HTML is sent to the browser. A user might see a couple of milliseconds of delay while waiting for the API call to finish. Depending on how long the function runs, this might result in a bad user experience. If your queries are fast then use getServerSideProps
. In this case, the protected-page-route.js
code should be used.
useEffect
is a React hook that runs on the client (browser) after the components have mounted. This means that an API call will be made only after the basic HTML has been rendered in the browser. Using the useEffect
hook might result in better UX because a loading indicator can be displayed while the data is fetched. But, to do this you will need an extra protected API route. This scenario is more desirable if your API call takes a while to return a response. In this case, the protected-api-route.js
code should be used.
middleware
How to protect a route in Next.js Vercel recently introduced middleware for Next.js. Next.js middleware allows you to run code before an HTTP request is handled. To add your middleware logic to your app, add a middleware.js
or middleware.ts
file in the root directory of your Next.js project.
The above "protected route" logic can also be moved to a middleware function which will be executed for every route that matches the matcher
value in the configuration. Here is an example of how to modify the above logic to run in a middleware function instead of getServerSideProps
:
export async function middleware(req: NextRequest) {
const token = req.headers.get('token') // get token from request header
const userIsAuthenticated = true // TODO: check if user is authenticated
if (!userIsAuthenticated) {
const signinUrl = new URL('/signin', req.url)
return NextResponse.redirect(signinUrl)
}
return NextResponse.next()
}
// Here you can specify all the paths for which this middleware function should run
// Supports both a single string value or an array of matchers
export const config = {
matcher: ['/api/auth/:path*'],
}
Conclusion
That's how easy it is to secure your Next.js application. This approach is also used to secure pages and API routes in this Next.js SaaS boilerplate. Whether you are using getServerSideProps
or Next.js middleware, and server-side or client-side rendering, now you know where to start to secure your application.
If you have any questions or comments, please reach out to me on Twitter or via the contact form.