You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS-NextAuth登录成功后无法重定向至/dashboard问题求助

问题描述

我已花费12小时解决同一问题,查阅了几乎所有论坛仍未找到适配方案。我按照NextAuth和NextJS官方教程配置认证功能,期望用户登录成功后重定向至/dashboard页面,但默认跳转至/login页面。我尝试修改middleware逻辑、重新验证路径,也试图理解其运行机制,但均未解决问题。我了解callbackurl选项,但它不符合我的表单逻辑,现求助解决方案。

相关代码文件

loginForm.ts

'use client';

import { useFormState, useFormStatus } from 'react-dom';
import Link from 'next/link';
import { authenticate } from '@/app/actions/login/loginEngine';
import ErrorAlert from '../errorAlert';
import { Button } from '../button';

export default function LoginForm() {

  const [errorMessage, dispatch] = useFormState(authenticate, undefined);
  return (
    <>
      {errorMessage && (
        <ErrorAlert AlertMessage={errorMessage} />
      )}

      <div className="flex min-h-full flex-1 items-center justify-center px-4 py-12 sm:px-6 lg:px-8">
        <div className="w-full max-w-sm space-y-10">
          <div>
            {/* Include your entreprise logo here */}
            <h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
              Sign in to your account
            </h2>
          </div>
          <form className="space-y-6" action={dispatch}>
            <div className="relative -space-y-px rounded-md shadow-sm">
              <div className="pointer-events-none absolute inset-0 z-10 rounded-md ring-1 ring-inset ring-gray-300" />
              <div>
                <label htmlFor="email-address" className="sr-only">
                  Email address
                </label>
                <input
                  id="email-address"
                  name="email"
                  type="email"
                  autoComplete="email"
                  required
                  minLength={6}
                  className="relative block w-full rounded-t-md border-0 pl-4 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-100 placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                  placeholder="Email address"
                />
              </div>
              <div>
                <label htmlFor="password" className="sr-only">
                  Password
                </label>
                <input
                  id="password"
                  name="password"
                  type="password"
                  autoComplete="current-password"
                  required
                  className="relative block w-full rounded-b-md border-0 pl-5 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-100 placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                  placeholder="Password"
                />
              </div>
            </div>

            <div className="flex items-center justify-between">
              <div className="flex items-center">
                <input
                  id="remember-me"
                  name="remember-me"
                  type="checkbox"
                  className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
                />
                <label htmlFor="remember-me" className="ml-3 block text-sm leading-6 text-gray-900">
                  Remember me
                </label>
              </div>

              <div className="text-sm leading-6">
                <a href="#" className="font-semibold text-indigo-600 hover:text-indigo-500">
                  Forgot password?
                </a>
              </div>
            </div>

            <LoginButton />

          </form>

          <p className="text-center text-sm leading-6 text-gray-500">
            Not a member?{' '} <Link href="/register" className="font-semibold leading-6 text-indigo-600 hover:text-indigo-500">Create an account</Link>
          </p>
        </div>
      </div>
    </>
  )
}

function LoginButton() {
  const { pending } = useFormStatus();
  return (
    <div>
      <Button className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
        aria-disabled={pending}>
        Sign in
      </Button>

      <button >
        Log in
      </button>
    </div>
  );
}

loginEngine.ts

'use server'
import { AuthError } from 'next-auth';
import { signIn } from '../../../auth';
import { revalidatePath } from 'next/cache';
 
export async function authenticate(
  prevState: string | undefined,
  formData: FormData,
) {
  try {
    await signIn('credentials', formData);
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case 'CredentialsSignin':
          return 'Invalid credentials.';
        default:
          return 'Something went wrong.';
      }
    }
    throw error;
  }
}

auth.ts

import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';
import bcrypt from 'bcrypt';
import { getUser } from '@/app/libs/dbCore';


export const { auth, signIn, signOut } = NextAuth({
    ...authConfig,
    providers: [
      Credentials({
        async authorize(credentials) {
          const parsedCredentials = z
            .object({ email: z.string().email(), password: z.string().min(6) })
            .safeParse(credentials);
   
          if (parsedCredentials.success) {
            const { email, password } = parsedCredentials.data;
            const user = await getUser(email);
            if (!user) return null;

            const passwordsMatch = await bcrypt.compare(password, user.password);
            if (passwordsMatch) return user;
          }

          console.log('Invalid credentials');
          return null;
        },
      }),
    ],
  });

auth.config.ts

import { NextAuthConfig } from "next-auth";

export const authConfig = {
  pages: {
    signIn: '/login',
    signOut: '/'
  },
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
      const isOnSettings = nextUrl.pathname.startsWith('/settings')
      if (isOnDashboard || isOnSettings) {
        if (isLoggedIn) return true;
        return false; // Redirect unauthenticated users to login page
      } else if (isLoggedIn) {
        return Response.redirect(new URL('/dashboard', nextUrl));
      }
      return true;
    },
  },
  providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;

middleware.ts

import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
 
export default NextAuth(authConfig).auth;
 
export const config = {
  // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
  matcher: ['/((?!api|_next/static|_next/image|.*\.png$).*)'],
};

解决方案

1. 手动在登录成功后触发重定向

修改loginEngine.ts,在signIn成功后使用Next.js的redirect方法直接跳转到/dashboard,绕过默认回调逻辑:

'use server'
import { AuthError } from 'next-auth';
import { signIn } from '../../../auth';
import { redirect } from 'next/navigation'; // 引入redirect
 
export async function authenticate(
  prevState: string | undefined,
  formData: FormData,
) {
  try {
    await signIn('credentials', formData);
    // 登录成功后立即重定向到dashboard
    redirect('/dashboard');
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case 'CredentialsSignin':
          return 'Invalid credentials.';
        default:
          return 'Something went wrong.';
      }
    }
    throw error;
  }
}

2. 清理冗余按钮

LoginButton组件存在两个登录按钮,易造成交互混淆,删除多余的按钮:

function LoginButton() {
  const { pending } = useFormStatus();
  return (
    <div>
      <Button className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
        aria-disabled={pending}>
        Sign in
      </Button>
    </div>
  );
}

3. 验证middleware逻辑

当前auth.config.ts中的authorized回调逻辑已正确配置:已登录用户访问非dashboard/settings页面时会自动重定向到dashboard。无需修改,确保middleware.ts的匹配规则覆盖所有需要验证的路由即可。

原理说明

默认情况下,signIn成功后会跳转到当前登录页/login,即使authorized回调会触发重定向,也可能存在延迟或优先级问题。通过在authenticate函数中手动调用redirect,可在登录成功后直接触发跳转,避免默认回跳逻辑,确保用户直接进入/dashboard。

内容的提问来源于stack exchange,提问作者pish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 10:36:00