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
相关产品推荐
相关产品推荐

