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

NextAuth凭证登录成功后地址栏未自动跳转的原因及解决问询

问题描述

我参照Next官方教程实现了仅基于邮箱的凭证认证,登录功能可正常完成,但地址栏路径未从/login自动跳转至/dashboard,需手动刷新页面才会更新路径;未刷新时点击「登出」按钮无响应,仅手动刷新路径更新后才能正常执行登出操作。

项目代码

./auth.config.ts

import type { NextAuthConfig } from 'next-auth';

export const authConfig = {
  pages: {
    signIn: '/login',
  },
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
      if (isOnDashboard) {
        if (isLoggedIn) return true;
        return false; // 未认证用户重定向到登录页
      } else if (isLoggedIn) {
        return Response.redirect(new URL('/dashboard', nextUrl));
      }
      return true;
    },
  },
  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$).*)'],
};

./auth.ts

import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';
import { sql } from '@vercel/postgres';
import { authConfig } from './auth.config';
import type { User } from '~/lib/definitions';

async function getUser(email: string): Promise<User | undefined> {
  try {
    const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
    return user.rows[0];
  } catch (error) {
    console.error('获取用户失败:', error);
    throw new Error('获取用户失败。');
  }
}

export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [
    Credentials({
      async authorize(credentials) {
        const parsedCredentials = z
          .object({ email: z.string().email() })
          .safeParse(credentials);

        if (parsedCredentials.success) {
          const { email } = parsedCredentials.data;
          const user = await getUser(email);
          if (user) return user;
          return null;
        }
        return null;
      },
    }),
  ],
});

./app/lib/actions.ts

'use server';

import { AuthError } from 'next-auth';
import { signIn } from '~/../auth';

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 '无效凭证。';
        default:
          return '发生未知错误。';
      }
    }
    throw error;
  }
}

./app/login/page.tsx

'use client';

import { useFormState, useFormStatus } from 'react-dom';
import { authenticate } from '~/lib/actions';

export default function LoginPage() {
  const [errorMessage, dispatch] = useFormState(authenticate, undefined);

  return (
    <main>
      <form action={dispatch}>
        <div>
          <label htmlFor="email">
            邮箱
          </label>
          <div>
            <input
              id="email"
              type="email"
              name="email"
              placeholder="输入你的邮箱地址"
              required
            />
          </div>
        </div>
        <LoginButton />
      </form>
    </main>
  );
}

function LoginButton() {
  const { pending } = useFormStatus();

  return (
    <button aria-disabled={pending}>
      登录
    </button>
  );
}

./app/dashboard/page.tsx

import { signOut } from '~/../auth';

export default function Page() {
  return (
    <main>
      <h1>控制台</h1>
      <p>控制台内容将在此展示</p>
      <form
        action={async () => {
          'use server';
          await signOut({ redirectTo: '/login' });
        }}
      >
        <button>
          <div>登出</div>
        </button>
      </form>
    </main>
  );
}
问题演示

登录后地址栏未跳转演示

文件夹结构

项目文件夹结构

内容的提问来源于stack exchange,提问作者Mix Master Mike

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 06:04:56