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

NextAuth.js登录成功后重定向失效问题求助

Next.js认证登录后未跳转至callbackUrl问题排查

问题场景

基于Next.js搭建应用,按官方流程添加认证层后,未登录访问首页会被正确重定向到登录页(URL携带callbackUrl参数),提交有效账号密码后,浏览器已生成有效的认证Cookie,但页面始终停留在登录页,未跳转到callbackUrl指定的首页。

相关代码配置

auth.ts

import { sql } from "@vercel/postgres"
import bcrypt from "bcrypt"
import type { User } from "lib/definitions"
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
import { z } from "zod"

import { authConfig } from "./auth.config"

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
          }
        }

        return null
      },
    }),
  ],
})

auth.config.ts

import type { NextAuthConfig } from "next-auth"

export const authConfig = {
  pages: {
    signIn: "/signIn",
  },
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      return !!auth?.user
    },
  },
  providers: [],
} satisfies NextAuthConfig

middleware.ts

import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
 
export default NextAuth(authConfig).auth;
 
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};

登录表单(app/signIn/page.tsx)

"use client"

import { useFormState } from "react-dom"
import { Button } from "@/registry/new-york/ui/button"
import { Input } from "@/registry/new-york/ui/input"
import { signIn } from "../lib/actions"
import { ActionResult } from "../sharedTypes"

export function SignInForm() {
  const [result, action] = useFormState<ActionResult | undefined, FormData>(
    signIn,
    undefined
  )
  return (
    <form action={action}>
      <div className="grid gap-4">
        <div>
          <Input id="email" placeholder="Email" type="email" name="email" />
        </div>
        <div>
          <Input
            id="password"
            placeholder="Password"
            type="password"
            name="password"
          />
        </div>

        <Button>Sign in</Button>
        <div
          className="flex h-8 items-end space-x-1"
          aria-live="polite"
          aria-atomic="true"
        >
          {result?.errorMessage && (
            <>
              <p className="text-sm text-red-500">{result.errorMessage}</p>
            </>
          )}
        </div>
      </div>
    </form>
  )
}

服务端Action(lib/actions.ts)

"use server"

import { signIn as signInAuth } from "@/auth"
import { AuthError } from "next-auth"

import { ActionResult } from "../sharedTypes"

export async function signIn(
  prevState: ActionResult | undefined,
  formData: FormData
): Promise<ActionResult | undefined> {
  try {
    await signInAuth("credentials", formData)
  } catch (error) {
    if (error instanceof AuthError) {
      if (error.type === "CredentialsSignin") {
        return { errorMessage: "Invalid credentials" }
      }
      return { errorMessage: "Something went wrong" }
    }
    throw error
  }
}

问题原因

服务端Action调用signInAuth时,未传递URL中携带的callbackUrl参数,导致NextAuth无法识别登录成功后应跳转的目标页面,因此停留在登录页。

修复步骤

1. 在登录表单中传递callbackUrl

登录页URL已包含callbackUrl参数,需将其取出并通过隐藏字段传递给服务端Action。修改app/signIn/page.tsx:

"use client"

import { useFormState } from "react-dom"
import { useSearchParams } from "next/navigation" // 新增:获取URL参数
import { Button } from "@/registry/new-york/ui/button"
import { Input } from "@/registry/new-york/ui/input"
import { signIn } from "../lib/actions"
import { ActionResult } from "../sharedTypes"

export function SignInForm() {
  const searchParams = useSearchParams()
  // 获取callbackUrl,默认跳转首页
  const callbackUrl = searchParams.get("callbackUrl") || "/"
  const [result, action] = useFormState<ActionResult | undefined, FormData>(
    signIn,
    undefined
  )
  return (
    <form action={action}>
      {/* 新增隐藏字段,传递callbackUrl */}
      <input type="hidden" name="callbackUrl" value={callbackUrl} />
      <div className="grid gap-4">
        <div>
          <Input id="email" placeholder="Email" type="email" name="email" />
        </div>
        <div>
          <Input
            id="password"
            placeholder="Password"
            type="password"
            name="password"
          />
        </div>

        <Button>Sign in</Button>
        <div
          className="flex h-8 items-end space-x-1"
          aria-live="polite"
          aria-atomic="true"
        >
          {result?.errorMessage && (
            <>
              <p className="text-sm text-red-500">{result.errorMessage}</p>
            </>
          )}
        </div>
      </div>
    </form>
  )
}

2. 在服务端Action中使用callbackUrl

修改lib/actions.ts,调用signInAuth时传入callbackUrl参数:

"use server"

import { signIn as signInAuth } from "@/auth"
import { AuthError } from "next-auth"

import { ActionResult } from "../sharedTypes"

export async function signIn(
  prevState: ActionResult | undefined,
  formData: FormData
): Promise<ActionResult | undefined> {
  try {
    const callbackUrl = formData.get("callbackUrl") as string | undefined
    await signInAuth("credentials", {
      ...Object.fromEntries(formData),
      callbackUrl: callbackUrl || "/",
    })
  } catch (error) {
    if (error instanceof AuthError) {
      if (error.type === "CredentialsSignin") {
        return { errorMessage: "Invalid credentials" }
      }
      return { errorMessage: "Something went wrong" }
    }
    throw error
  }
}

额外检查项

  • 确认auth.config.ts中pages.signIn的路径与实际登录页一致(当前配置为/signIn,需确保页面存在)。
  • 检查middleware的matcher规则,避免拦截登录成功后的跳转请求。
  • 确认浏览器Cookie中已生成next-auth.session-token,且用户信息正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 03:12:04