Published on

How to create a generic protected route in Next.js

Authors

What is Next.js?

Next.js is an open source React framework that makes statically and server-side rendered pages possible in your app. Where traditional React apps are rendered entirely in the browser, Next.js makes it possible to render pages server-side. This improves page speed and reduces bundle size significantly. You can read more about this powerful framework here.

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 with getStaticProps)
  • 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 the purpose of this post the above logic will be used.

To generically protect a server-side route use the following code:

protected-page-route.js
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:

protected-page.js
export const getServerSideProps = (context) =>
  ProtectedPageRoute(context, null, async () => {
    // fetch props
  });

Next.js also supports API routes which can be protected in a similar way:

protected-api-route.js
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:

route.js
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) where 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 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, in order 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.


Vercel recently introduced middleware for Next.js. The above "protected route" logic can also be moved to a middleware function which will be executed for each request. Check back soon for a more detailed post on Next.js middleware.

Stay up to date

Sign up for the Ship SaaS newsletter to get notifiedabout the latest updates and blog posts

We care about the protection of your data. Read our Privacy Policy and Terms & Conditions.