skip to Main Content

I have a client component that checks the authentication state and is incorporated into app/layout.tsx. The objective is to determine if the user is logged in or not. If not, the user is redirected to the login page if the current URL path is not /login. If the user is logged in, they are redirected to / if the current URL path is /login. Here’s the code:

(Firebase)

onAuthStateChanged(auth, (user) => {
  if (user) {  // if user is logged in
    if (path === "/login") {
      router.push("/");
    }
  } else { // if user is not logged in
    if (path !== "/login") {
      router.push("/login");
    }
  }
});

Notice that using this code, the user cannot access any 404 pages if they aren’t logged in. The code will redirect the user to the login page if they do so, which I don’t want. To prevent this, I need to detect if it’s a 404 page.

So, how do I check if it’s a 404 page with Next.js 13?

Let me know if you need any additional info to solve the issue.

2

Answers


  1. Next.js and Error Handling

    In Next.js the built-in Error page component, _error.js or _error.tsx located in the /pages directory, handles all error codes, including 404. However, it doesn’t directly provide a mechanism for checking if the current page is a 404 from another component.

    A possible solution to your problem is to set a state within your root component layout.tsx that’s altered whenever the Error page component is triggered. You can achieve this via React context or a state management library of your choice.

    So lets go on with my approach:

    First we need to construct a context. I have created a NotFoundContext for this case.

    The Code:

    // Import the necessary React hooks
    import { createContext, useContext, useState } from 'react';
    
    // Define our NotFoundContext context
    const NotFoundContext = createContext();
    
    // Provide a Provider function that holds our state
    export function NotFoundProvider({ children }) {
      // Define state variable to track 404 status
      const [isNotFound, setNotFound] = useState(false);
    
      // Use Provider to allow children components access to our 404 state
      return (
        <NotFoundContext.Provider value={{ isNotFound, setNotFound }}>
          {children}
        </NotFoundContext.Provider>
      );
    }
    
    // A hook for easy access to the NotFoundContext
    export function useNotFound() {
      return useContext(NotFoundContext);
    }
    

    Now we need to wrap the application with NotFoundProvider that we have defined.

    The Code:

    // Import NotFoundProvider context
    import { NotFoundProvider } from './NotFoundContext';
    
    // Define your Application
    function MyApp({ Component, pageProps }) {
      return (
        // Provide the NotFound context to the entire Application
        <NotFoundProvider>
          <Component {...pageProps} />
        </NotFoundProvider>
      );
    }
    
    export default MyApp;
    

    We need to adjust _error.js or _error.tsx to reflect the isNotFound status. Just set isNotFound to true inside the js or tsx file.

    The Code:

    // Import useNotFound hook
    import { useNotFound } from '../NotFoundContext';
    import { useEffect } from 'react';
    
    function Error({ statusCode }) {
      // Obtain our function for setting NotFound state
      const { setNotFound } = useNotFound();
    
      // Till component mount, check if statusCode is 404 and adjust NotFound state
      useEffect(() => {
        setNotFound(statusCode === 404);
      }, [statusCode]);
    
      // TODO: Rest of this component
    }
    
    // Get statusCode from server response or the error object itself
    Error.getInitialProps = ({ res, err }) => {
      const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
      return { statusCode };
    };
    
    export default Error;
    

    Now with everything at its place isNotFound can be accessed and utilized within your own component.

    The Code:

    // Import our custom hook
    import { useNotFound } from './NotFoundContext';
    import { onAuthStateChanged } from 'firebase/auth';
    
    // Other code
    
    onAuthStateChanged(auth, (user) => {
      // Obtain our NotFound state value
      const { isNotFound } = useNotFound();
      
      if (user) { // User is logged in
        if (path === "/login") {
          router.push("/");
        }
      } else if (!isNotFound) { // User is not logged in and the page is not a 404 status
        if (path !== "/login") {
          router.push("/login");
        }
      }
    });
    

    I hope this helps you!

    Login or Signup to reply.
  2. Usually, redirection or authentication is handled in middleware files or a wrapper files. if you still want to follow your approach, you should manually defined all the routes in your app

    const routes=["route1",....]
    
    
    onAuthStateChanged(auth, (user) => {
      if (user) {  // if user is logged in
        if (path === "/login") {
          router.push("/");
        }
      } else { // if user is not logged in
        // if not logged in and visiting a valid path
        if (routes.includes(path)) {
          router.push("/login");
        }
      }
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search