前言
自从next.js14发布之后,app router
变成了官网主推的架构区别于pages router
的传统架构,app router
更适合最新的react
,于是自己动手把next-auth
、redux-toolkit
、ant-design
、tailwindcss
也一同集成进来,分享给大家,如果有错误之处欢迎大家指正。
操作
1、创建项目
使用下面命令创建项目,并且选择tailwind css
和app router
模式
➜ npx create-next-app@latest
Need to install the following packages:
create-next-app@14.1.4
Ok to proceed? (y) y
✔ What is your project named? … next-auth-redux
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /Users/justinzhang/WorkSpace/com.seaurl/web/next-auth-redux.
2、引入依赖包
添加下面的依赖包:
"dependencies": {
"@ant-design/nextjs-registry": "^1.0.0",
"@ant-design/icons": "^5.3.6",
"@reduxjs/toolkit": "^2.2.3",
"antd": "^5.16.0",
"install": "^0.13.0",
"next": "14.1.4",
"next-auth": "5.0.0-beta.16",
"react": "^18",
"react-dom": "^18",
"react-redux": "^9.1.0",
"redux-logger": "^3.0.6"
},
"devDependencies": {
"autoprefixer": "^10.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0"
}
3、配置ant-design
首先,我们来创建src/context/antdProvider.js
文件,如下所示:
"use client";
import {AntdRegistry} from '@ant-design/nextjs-registry';
import {ConfigProvider, theme} from 'antd'
export default function AntdProvider({children}) {
return <AntdRegistry>
<ConfigProvider>
{children}
</ConfigProvider>
</AntdRegistry>
}
我们在src/app/layout.js
中配置ant-design
如下所示:
import AntdProvider from "@/context/antdProvider";
export default function RootLayout({ children }) {
return (
<html>
<body>
<AntdProvider>
<div>
{children}
</div>
</AntdProvider>
</body>
</html>
);
}
这样就完成了ant-design
的配置,我们就可以在页面中安心的使用它提供的组件了。
4、配置next-auth
同样的,我们创建文件src/app/(auth)/auth.js
,代码如下所示:
import CredentialsProvider from "next-auth/providers/credentials"
import NextAuth from "next-auth"
export const authOptions = {
providers: [
CredentialsProvider({
// The name to display on the sign in form (e.g. 'Sign in with...')
name: 'Credentials',
// The credentials is used to generate a suitable form on the sign in page.
// You can specify whatever fields you are expecting to be submitted.
// e.g. domain, username, password, 2FA token, etc.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
userName: {label: "用户名", type: "text", placeholder: "用户名"},
password: {label: "密码", type: "password"}
},
async authorize(credentials, req) {
const user = {id: "42", name: "justin", password: "123456", role: "manager"}
if (credentials?.userName === user.name && credentials?.password === user.password) {
return user
} else {
return null
}
}
})
]
}
export const {
handlers: { GET, POST },
auth,
} = NextAuth(authOptions)
在这个文件里面我们使用CredentialsProvider
来自定义认证,然后在authorize
中验证用户,并且返回用户信息,这里我是固定的写法,大家可能使用fetch
查询并验证,如下所示:
const res = await fetch(`${process.env.NEXT_PUBLIC_API_HOST}/users/web/login`, {
method: 'POST',
body: JSON.stringify(credentials),
headers: { "Content-Type": "application/json" }
})
const resData = await res.json()
// If no error and we have user data, return it
if (res.ok && resData && resData.status === 0) {
return resData.data
}
// Return null if user data could not be retrieved
return null
接着,我们可以再创建src/context/authProvider.js
文件,代码如下所示:
'use client'
import { SessionProvider } from 'next-auth/react'
export default function AuthProvider({ children }) {
return (
<SessionProvider>
{children}
</SessionProvider>
)
}
接着,我们在src/app/layout.js
中配置一下,如下所示:
import AntdProvider from "@/context/antdProvider";
import AuthProvider from "@/context/authProvider";
export default function RootLayout({ children }) {
return (
<html>
<body>
<AuthProvider>
<AntdProvider>
<div>
{children}
</div>
</AntdProvider>
</AuthProvider>
</body>
</html>
)
}
这样我们就完成了配置next-auth
,下面我们来看看server component
和client component
组件如何调用,如下所示:
服务端页面:src/app/page.js
import {auth} from "@/app/(auth)/auth";
export default async function Home() {
const session = await auth()
return (
<main className="p-12">
<div>Access Token: {session.user.name}</div>
</main>
);
}
客户端页面:src/app/components/auth/login-btn.js
'use client'
import { useSession, signIn, signOut } from "next-auth/react"
import {Button} from 'antd'
import { useRouter } from 'next/navigation'
export default function Component() {
const router = useRouter()
const { data: session } = useSession()
if (session) {
return (
<div>
<div className={'flex flex-row justify-between px-6 py-6'}>
<div className={'flex gap-4 '}>
<Button onClick={() => goto('/news')}>跳转到news页面</Button>
<Button onClick={() => goto('/')}>跳转到首页面</Button>
</div>
<div>
<Button onClick={() => signOut()}>退出</Button>
</div>
</div>
</div>
)
}
function goto(val) {
router.push(val)
}
return (
<div className={'flex justify-between px-6 py-6'}>
<Button onClick={() => signIn()}>登录</Button>
</div>
)
}
注意它们获取session
的两种方式:
服务端:
import {auth} from "@/app/(auth)/auth";
const session = await auth()
客户端:
import { useSession, signIn, signOut } from "next-auth/react"
const { data: session } = useSession()
5、配置redux-toolkit
首先,我们创建一个新文件src/context/storeProvider.js
,如下所示:
'use client'
import { useRef } from 'react'
import { Provider } from 'react-redux'
import { makeStore } from '@/lib/store'
export default function StoreProvider({ children }) {
const storeRef = useRef()
if (!storeRef.current) {
// Create the store instance the first time this renders
storeRef.current = makeStore()
}
return <Provider store={storeRef.current}>{children}</Provider>
}
再创建src/lib/store.js
、src/lib/hooks.js
、src/lib/slices/homeSlice.js
文件,如下所示:
src/lib/store.js
// "use client";
import logger from "redux-logger";
import {configureStore, combineReducers} from '@reduxjs/toolkit'
import {homeSlice} from "@/lib/slices/homeSlice";
const rootReducer = combineReducers({
[homeSlice.name]: homeSlice.reducer
})
export const makeStore = () => {
return configureStore({
reducer: rootReducer,
devTools: false,
// middleware: new MiddlewareArray().concat(logger),
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
serializableCheck: false, // 禁用序列化检查
}).concat(logger)
})
}
src/lib/hooks.js
import { useDispatch, useSelector, useStore } from 'react-redux'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes()
export const useAppSelector = useSelector.withTypes()
export const useAppStore = useStore.withTypes()
src/lib/slices/homeSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
value: 0,
};
export const homeSlice = createSlice({
name: "home",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
console.log('increment state value = ', state.value)
},
decrement: (state) => {
state.value -= 1;
console.log('decrement state value = ', state.value)
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
})
export const { increment, decrement, incrementByAmount } = homeSlice.actions;
最后我们重新调整layout.js
文件
import { Inter } from "next/font/google";
import "./(global)/globals.css";
import Link from 'next/link'
import AntdProvider from "@/context/antdProvider";
import AuthProvider from "@/context/authProvider";
import StoreProvider from "@/context/storeProvider";
import {Button} from "antd";
const inter = Inter({ subsets: ["latin"] });
import LoginBtn from '@/components/auth/login-btn'
export const metadata = {
title: "Next.js & Next-Auth & Redux-Toolkit & Antd & Tailwindcss",
description: "集成示例",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<StoreProvider>
<AuthProvider>
<AntdProvider>
<div>
<LoginBtn />
</div>
<div>
{children}
</div>
</AntdProvider>
</AuthProvider>
</StoreProvider>
</body>
</html>
);
}
这里我们配置好了redux store相关文件,然后我们在组件中使用,如下所示:
src/components/user/userDetail/index.js
'use client'
import {useAppDispatch, useAppSelector} from "@/lib/hooks";
import {useEffect} from "react";
import {increment} from "@/lib/slices/homeSlice";
export default function UserDetail() {
const dispatch = useAppDispatch()
const {value} = useAppSelector(state => state.home)
useEffect(() => {
console.log('useeffect value is = ', value)
dispatch(increment())
}, []);
return (
<div>
user detail components
</div>
);
}
我们可以观察打印的结果,如果能够打印说明配置成功,这样我们就完成了全部配置,接下来,我们启动项目,然后测试登录
和退出
看看能不能通过session
获取用户信息,如下所示:
登录前
登录中
登录后
成功!
设置夜间模式
事实上设置夜间模式是每个网站的标配,这里我会使用tailwind css
和antd
教你如何设置。
1、设置antd组件的夜间模式
设置方法官网已经提供,大家可以看下,如下所示:
import {ConfigProvider, theme} from 'antd'
"use client";
import {AntdRegistry} from '@ant-design/nextjs-registry';
import {ConfigProvider, theme} from 'antd'
import zhCN from 'antd/locale/zh_CN';
import 'dayjs/locale/zh-cn';
import {useAppSelector} from "@/lib/hooks";
import {useEffect, useState} from "react";
export default function AntdProvider({children}) {
const {myTheme, defaultTheme, darkTheme} = useAppSelector(state => state.system)
useEffect(() => {
if (myTheme === 'dark'){
document.documentElement.classList.add('dark')
}else{
document.documentElement.classList.remove('dark')
}
}, [myTheme])
return <AntdRegistry>
<ConfigProvider locale={zhCN} theme={{
token: {
colorPrimary: myTheme === 'dark' ? darkTheme.primary : defaultTheme.primary
},
algorithm: myTheme === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm
}}>
<div>
{children}
</div>
</ConfigProvider>
</AntdRegistry>
}
2、设置除了antd组件外的夜间模式(也就是页面)
设置页面的夜间模式我们使用tailwind css
来实现,官网也有实例大家可以看下,如下所示:
tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'selector',
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
plugins: [],
};
src/app/layout.js
import { Inter } from "next/font/google";
import "./globals.css";
import Link from 'next/link'
import AntdProvider from "@/context/antdProvider";
import AuthProvider from "@/context/authProvider";
import StoreProvider from "@/context/storeProvider";
const inter = Inter({ subsets: ["latin"] });
import LoginBtn from '@/components/auth/login-btn'
export const metadata = {
title: "Next.js & Next-Auth & Redux-Toolkit & Antd & Tailwindcss",
description: "集成示例",
};
export default function RootLayout({ children }) {
return (
<html>
<body className="bg-white dark:bg-black dark:text-white">
<StoreProvider>
<AuthProvider>
<AntdProvider>
<div>
<LoginBtn/>
</div>
<div>
{children}
</div>
</AntdProvider>
</AuthProvider>
</StoreProvider>
</body>
</html>
);
}
src/context/antdProvider.js
const {myTheme, defaultTheme, darkTheme} = useAppSelector(state => state.system)
useEffect(() => {
if (myTheme === 'dark'){
document.documentElement.classList.add('dark')
}else{
document.documentElement.classList.remove('dark')
}
}, [myTheme])
注意看上面的body
设置了<body class='bg-white dark:bg-black dark:text-white'>
,所以你只要js触发设置html属性<html class='dark'>
就可以完成黑色和白色主题的切换了,效果如下所示:
总结
1、完整代码
2、app router
比pages router
更好用,配置更少,尤其使用pages router
使用next-redux-wrapper
组件也不维护了,有问题也解决不了
3、app rouer
的文件夹模式更出色,再加上server compoent
和 clinet component
的分离,可以让你更专注业务
4、app router
好多新功能不知道怎么用可以参考next.js
官网文档
5、设置夜间模式时,只要在每个body
上面添加class='dark:'
前缀,然后在html标签上面添加<html class='dark'>
就会有效果。
引用
Redux Toolkit Setup with Next.js
Upgrade Guide (v5)
next-auth-role-based
在 Next 14 的 appRouter 模式中接入 React-Redux
分享 7 个你可能不知道的 Next.js 14 小技巧
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。