skip to Main Content

I’m working on a Next.js application, and I have a specific page, let’s call it "thank-you," that I want to prevent users from accessing directly via the URL. However, I want to allow access to this page only through internal navigation within the app, such as clicking a button.

I’ve tried using a middleware function to redirect direct requests to the home page, and it works as expected. However, when I try to navigate to the "thank-you" page using client-side routing (e.g., router.push("/thank-you")), it still triggers the middleware and redirects to the home page.

Here’s a simplified version of the code:

Middleware Function (middleware.ts):

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL("/", request.url));
}

export const config = {
  matcher: ["/thank-you"],
};

Button Click Handling (YourComponent.tsx):

import { useRouter } from "next/router";

export default function Home() {
  const router = useRouter();

  const handleButtonClick = () => {
    router.push("/thank-you");
  };
...
}

How can I prevent direct access to the "thank-you" page while allowing access through internal navigation within the app?

Any help or suggestions would be greatly appreciated!

UPDATE

I tried with getServerSideProps and still have the same problem.
Redirect to home happens from button click as well.

export const getServerSideProps = () => {
  return {
    redirect: {
      permanent: false,
      destination: "/",
    },
    props: {},
  };
};

2

Answers


  1. This seems like auth-guard issue. Add AuthGuard in root folder. If using app Router it would be layout, and control access with redux or other state management.

    I didn’t include but you also should change authRights.thankYou to false in auth guard or in thank you page.

    Root Layout

    export default function RootLayout({
      children,
    }: IPageParamsLayout) {
      return (
        <html>
          <body>
           <main>
            <AuthGuard>{children}</AuthGuard>
           </main>
          </body>
        </html>
      );
    }
    

    AuthGuard

    "use client"
    import { useRouter } from "next/navigation";
    import { usePathname } from "next/navigation";
    
    // Authguard would have all redirection logic to keep redirection in one place and prevent redirection loop.
    const AuthGuard = (children) => {
    const pathname = usePathname();
    const router = useRouter();
    const dispatch = useAppDispatch();
    const authRights = useAppSelector(authRights);
    
      if(pathname === 'thank-you' && !authRights.thankYou) {
       router.push(`/`);
      }
    
      return (
        <>{children}</>
      )
    }
    export default AuthGuard
    

    Component that redirects to Thank you.

    
    "use client"
    
    const BeforeThankYou = () => {
      const dispatch = useAppDispatch();
      return (
        <div onClick={() => {
          dispatch(setAuthRights({
           thankYou: true
           }))
         router.push("/thank-you")
        }}>Click to Thank You Page</div>
    
      )
    }
    export default BeforeThankYou
    
    
    Login or Signup to reply.
  2. Maybe this is a bit too simple, but why expose a route to the component when don’t want it accessible via a direct URL?

    The simplest solution is to simply avoid the issue entirely and not expose an URL for the thank-you page. You can do this by creating a <ThankYou /> component, and render that on the current page as part of some procedure.

    This can be done by toggling some state (showThankYou for example):

    export default function Page() {
      const [showThankYou, setShowThankYour] = useState(false);
    
      // ...
    
      if (showThankYou) return <ThankYou />;
    
      return (
        <NormalPage />
      );
    }
    

    Or by returning it as part of a more complicated process:

    export enum SignUp {
      None,
      UserInfo,
      ThankYou,
    }
    
    export default function Page() {
      const [signUp, setSignUp] = useState(SignUp.None);
      const [_context, setContext] = useState({});
      const context = useMemo(() => ({ ..._context, setContext }), [_context]);
    
      const navigateSignUp = useCallback(function () {
        setSignUp(SignUp.UserInfo);
      }, []);
    
      const navigateThankYou = useCallback(function () {
        setSignUp(SignUp.ThankYou);
      }, []);
    
      switch (signUp) {
        case SignUp.UserInfo:
          return <CollectUserInfo {...context} navigateThankYou={navigateThankYou} />;
        case SignUp.ThankYou:
          return <ThankYou {...context} />;
        else
          return <InformationPage {...context} navigateSignUp={navigateSignUp} />;
      }
    }
    

    Because this doesn’t expose a route to the "thank you" page, users can only get there by following the procedure.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search