[Next.js14] NextAuth v5 (2) - Session/Update
我们从文件夹结构开始。
src/app/api/auth/[…nextauth]/route.ts
src/app/client/page.tsx
src/ui/update_button.tsx
src/auth_wrapper.tsx
相比之前的教程,新增了4个文件
👉 服务器端渲染 & 客户端渲染
在继续之前,我们简单讨论一下 Next.js 14 中服务器端渲染(SSR)和客户端渲染(CSR)的区别。网上可以找到详细的比较,所以这里我只解释本教程所需的概念。
Next.js 14默认使用服务器端渲染来显示网页组件。在组件代码顶部添加"use client"行可以让Next.js 14使用客户端渲染来显示组件。
在服务器端代码中添加console.log("message")会在运行Next应用的终端中显示消息,而在客户端代码中添加则会在浏览器控制台显示消息(浏览器 -> 开发者工具 -> 控制台)
现在我们在src/app/page.tsx
中添加console.log,并编写src/app/client/page.tsx
的代码:
// src/app/page.tsx
...
export default async function Home() {
console.log('Server Side Rendering') // 添加console.log
return (
...
// src/app/client/page.tsx
"use client"
export default function Page() {
console.log('Client Side Rendering')
return (
<div>
<h1>Client Side Rendeing Page</h1>
<h2>Unavailable without auth</h2>
</div>
)
}
现在访问 http://localhost:3000/
我们可以在终端看到"Server Side Rendering"消息
访问 http://localhost:3000/client
我们现在可以在浏览器控制台看到"Client Side Rendering"消息!看到多条消息的原因在下面解释。
为什么我的nextjs组件渲染了两次?
Session(会话)
之所以讨论SSR和CSR,是因为session的调用方法取决于代码是在服务器端还是客户端运行。
👉 在服务器端调用session: await auth()
要在服务器端调用session,让我们首先编辑src/auth.ts和src/app/page.tsx中的代码
// src/auth.ts
...
// 添加了 { handlers: { GET, POST }, auth, update }
export const { handlers: { GET, POST }, auth, signIn, signOut, update } = NextAuth({
...authConfig,
...
// src/app/page.tsx
import { auth, signOut } from "@/auth" // 添加 { auth }
export default async function Home() { // 现在是'async function'
console.log('Server Side Rendering')
const session = await auth() // 调用session
console.log(session); // console log读取session
return (
...
现在访问 http://localhost:3000/ ,我们可以从终端读取session数据
👉 在客户端调用session: useSession()
要在客户端调用session,我们可以使用'useSession()'钩子。要使用'useSession()'钩子,必须先完成几件事。我们必须设置一个内部api并用SessionProvider包装应用。
// src/app/api/auth/[...nextauth]/route.ts
export { GET, POST } from "@/auth"
export const runtime = "edge"
// src/app/auth_wrapper.tsx
"use client";
// SessionProvider必须与客户端渲染一起使用
// 因此我们创建一个单独的客户端组件来运行AuthWrapper
import { SessionProvider } from "next-auth/react";
type Props = {
children:React.ReactNode;
}
export default function AuthWrapper({ children }: Props) {
return <SessionProvider>{children}</SessionProvider>;
}
// src/app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import AuthWrapper from '../auth_wrapper'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
<AuthWrapper> {/* 用AuthWrapper包装整个应用 */}
{children}
</AuthWrapper>
</body>
</html>
)
}
layout.tsx
文件是一个特殊文件,它作为page.tsx组件的父组件。
现在让我们编辑src/client/page.tsx
// src/app/client/page.tsx
"use client"
import { useSession } from "next-auth/react" // 添加import
export default function Page() {
console.log('Client Side Rendering')
const { data: session, update } = useSession() // useSession()
console.log(session); // console.log
return (
<div>
<h1>Client Side Rendeing Page</h1>
<h2>Unavailable without auth</h2>
</div>
)
}
访问 http://localhost:3000/client ,打开浏览器控制台,我们可以读取session数据
Session更新
在Next Auth v5发布之前,更新session只能在客户端完成。有了新的v5,session更新也可以在服务器端完成。
👉 编辑auth.ts
我们首先编辑src/auth.ts
文件。在callbacks -> jwt中添加了一个代码块
// src.auth.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import { User } from '@/lib/definitions';
export const { handlers: { GET, POST }, auth, signIn, signOut, update } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
if (credentials.id && credentials.password) {
// 在这里添加你的后端代码
// let loginRes = await backendLogin(credentials.id, credentials.password)
let loginRes = {
success : true,
data : {
user: {
ID: "john_doe",
NAME: "John Doe",
EMAIL: "email@email.email",
},
}
}
// 登录失败
if (!loginRes.success) return null;
// 登录成功
const user = {
id: loginRes.data.user.ID ?? '',
name: loginRes.data.user.NAME ?? '',
email: loginRes.data.user.EMAIL ?? '',
} as User;
return user;
}
return null;
},
})
],
callbacks: {
async session({ session, token, user }) {
session.user = token.user as User
return session;
},
async jwt({ token, user, trigger, session }) {
if (user) {
token.user = user;
}
// ***************************************************************
// 添加的代码
if (trigger === "update" && session) {
token = {...token, user : session}
return token;
};
// **************************************************************
return token;
},
},
});
👉 客户端Session更新
让我们先处理客户端代码。编辑src/app/client/page.tsx中的代码
// src/app/client/page.tsx
"use client"
import { User } from "@/lib/definitions"
import { useSession } from "next-auth/react"
import { useEffect, useState } from "react"
export default function Page() {
const { data: session, update } = useSession()
// useState和useEffect用于仅在检索到session后
// 渲染UI
const [user, setUser] = useState<User>({} as User)
useEffect(() => {
if(session && session.user)
{
setUser(session.user as User)
console.log(session.user)
}
}, [session])
return (
session && // UI在检索到session后渲染
<div>
<h1>Client Side Rendeing Page</h1>
<h2>Unavailable without auth</h2>
<br />
<button onClick={ () => {
// 使用从useSession钩子调用的update()
update({...user, name: 'Client-Man'});
}}>
Client Side Update
</button>
</div>
)
}
访问 http://localhost:3000/client ,打开浏览器控制台,然后点击"Client Side Update"按钮。控制台将打印更新后的session用户数据。
👉 服务器端Session更新
在服务器端更新session有两种方式。第一种方式是在服务器组件中调用客户端组件来运行客户端Session更新。
另一种方式是利用NextAuth v5的新特性;在服务器组件中使用从@auth导入的update方法。
我们试试这两种方法
👉 服务器组件中的客户端组件
首先,我们在src/ui/update_button.tsx
中创建一个单独的客户端组件文件
// src/ui/update_button.tsx
"use client"
import { useSession } from "next-auth/react"
export default function UpdateButton({newName} : {newName: String}) {
const { data: session, update } = useSession()
return (
<button onClick={() => {
update({...session!.user, name: newName});
}}>
Client Side Update
</button>
)
}
编辑src/app/page.tsx文件
// src/app/page.tsx
import { auth, signOut } from "@/auth"
import UpdateButton from "@/ui/update_button";
export default async function Home() {
console.log('Server Side Rendering')
const session = await auth() // 调用session
console.log(session); // console log读取session
return (
<div>
<h1>Home Page</h1>
<h2>Unavailable without auth</h2>
<form
action={async () => {
'use server';
await signOut();
}}
>
<button>
Log Out
</button>
<br />
<UpdateButton newName={'Server-Man'} />
</form>
</div>
)
}
访问 http://localhost:3000/ ,然后点击'Client Component Update'按钮。刷新页面或转到不同的路由(http://localhost:3000/client)。
我们可以检查session中的user.name已成功更新为'Server-man'
👉 在服务器组件中使用从@auth导入的update
编辑src/app/page.tsx文件
// src/app/page.tsx
import { auth, signOut, update } from "@/auth"
import UpdateButton from "@/ui/update_button";
export default async function Home() {
console.log('Server Side Rendering')
const session = await auth() // 调用session
const user = session!.user;
console.log(session); // console log读取session
return (
<div>
<h1>Home Page</h1>
<h2>Unavailable without auth</h2>
<form
action={async () => {
'use server';
await signOut();
}}
>
<button>
Log Out
</button>
<br />
<UpdateButton newName={'Server-Man'} />
</form>
<br />
<form
action={async () => {
'use server';
await update({...user, name: 'Serverserver-man'});
}}
>
<button>
Server Side Update
</button>
</form>
</div>
)
}
访问 http://localhost:3000/ ,然后点击'Server Side Update'按钮。刷新页面或转到不同的路由(http://localhost:3000/client)。
我们可以检查session中的user.name已成功更新为'Serverserver-man'
我注意到这个新的update功能目前似乎有点不稳定。因此,在NextAuth v5正式发布之前,使用客户端组件更新方法将是一个更稳定的选择。