[Next.js14] NextAuth v5 (2) - Session/Update

文化   2024-10-27 11:47   中国香港  

[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正式发布之前,使用客户端组件更新方法将是一个更稳定的选择。

最后:
React Hook 深入浅出
CSS技巧与案例详解
vue2与vue3技巧合集
VueUse源码解读

大迁世界
掘金LV8,思否10万+的作者。一个热爱前端的创业者。
 最新文章