image.png

准备工作

👉 文件夹结构

我们从文件夹结构开始。

image.png

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/

image.png

我们可以在终端看到"Server Side Rendering"消息

访问 http://localhost:3000/client

image.png

我们现在可以在浏览器控制台看到"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数据

image.png

👉 在客户端调用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数据

image.png

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用户数据。

image.png

👉 服务器端Session更新

在服务器端更新session有两种方式。第一种方式是在服务器组件中调用客户端组件来运行客户端Session更新。

另一种方式是利用NextAuth v5的新特性;在服务器组件中使用从@auth导入的update方法。

image.png

我们试试这两种方法

👉 服务器组件中的客户端组件

首先,我们在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'

image.png

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

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

王大冶
68k 声望104.9k 粉丝