Prevent Router from Loading Page Briefly before Redirect

Viewed 35

I have a session context for my NextJS application where anyone accessing /app/ directory pages have to go through an authorization check prior to allowing the user to access the page.

While my logic works in redirecting users without proper authentication, it is a bit glitchy because when someone navigate to the URL, /app/profile/ the page briefly loads before being redirected by Router.

I am wondering what is the best way to have this check happen prior to router loading the unauthorized page and redirecting them to the /login/ page.

Here are the steps in the authorization check:

  1. Check is the user object has a property, authorized
  2. Query the server for a session token
  3. if the object from the server request comes back with authorized = false, then redirect user to /login/

Here is the code:

import React, { createContext, useContext, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import axios from 'axios'

export const SessionContext = createContext(null);

const AppSession = ({ children }) => {
    const router = useRouter()
    const routerPath = router.pathname;
    const [user, setUser] = useState({ user_id: '', user_email: '', user_avatar: ''})

    useEffect(()=> {
        // Check for populated user state if pages are accessed with the path /app/
        if (routerPath.includes("/app/")){
            if (user){
                if(user.authenticated === undefined){
                    // Check if user session exists
                    axios.get('/api/auth/session/')
                    .then(res => {
                        const data = res.data;
                        // Update user state depending on the data returned
                        setUser(data)
                        // If user session does not exist, redirect to /login/
                        if (data.authenticated === false){
                            router.push('/login/')
                        }
                    })
                    .catch(err => {
                        console.log(err)
                    });
                }
            }
        }
    }, [])

    return (
        <SessionContext.Provider value={{user, setUser}}>
            {children}
        </SessionContext.Provider>
    )

}

export const getUserState = () => {
    const { user } = useContext(SessionContext)
    return user;
}

export const updateUserState = () => {
    const { setUser } = useContext(SessionContext)
    return (user) => {
        setUser(user);
    }
}

export default AppSession;
2 Answers

Since user.authenticated isn't defined in the initial user state you can conditionally render null or some loading indicator while user.authenticated is undefined. Once user.authenticated is defined the code should either redirect to "/login" or render the SessionContext.Provider component.

Example:

const AppSession = ({ children }) => {
  const router = useRouter();
  const routerPath = router.pathname;
  const [user, setUser] = useState({ user_id: '', user_email: '', user_avatar: ''});

  ...

  if (user.authenticated === undefined) {
    return null; // or loading indicator/spinner/etc
  }

  return (
    <SessionContext.Provider value={{ user, setUser }}>
      {children}
    </SessionContext.Provider>
  );
};

Check out getServerSideProps, redirects in getServerSideProps and this article.

In your client-side, if you export the NextJS function definition named getServerSideProps from a page, NextJS pre-renders the page on each request using the data returned by getServerSideProps.

In other words, you can use getServerSideProps to retrieve and check the user while pre-rendering the page and then choose to redirect instead of render if your condition is not met.

Here is an example.

function Page({ data }) {
  // Render data...
}

export async function getServerSideProps(context) {
  const { req, res } = context;
  try {
    // get your user
    if (user.authenticated === undefined) {
      return {
        redirect: {
          permanent: false,
          destination: `/`,
        },
      };
    }

    
    return {
      props: {
        // any static props you want to deliver to the component
      },
    };
  } catch (e) {
    console.error("uh oh");
    return;
  }
}

Good luck!

Related