- Token 存储
- 数据持久化
- 状态管理
- 集成状态管理器 Redux 及共享 Token 信息
- 注意
- 总结

剩下代码
pages/login.js
import React, {useState, useEffect} from "react";
import {Form, Input, Button, Checkbox, message, Alert, Typography} from "antd";
import Record from "../../components/layout/record";
import styles from "./index.module.scss";
import {useRouter} from "next/router";
import {useSelector, useDispatch} from 'react-redux'
import {login} from '@/store/slices/auth';
import {wrapper} from '@/store'
const {Text, Link} = Typography;
const layout = {
labelCol: {span: 24},
wrapperCol: {span: 24}
};
const Login = props => {
const dispatch = useDispatch();
const router = useRouter();
const [isLoding, setIsLoading] = useState(false);
const [error, setError] = useState({
show: false,
content: ""
});
function closeError() {
setError({
show: false,
content: ""
});
}
const onFinish = async ({username, password}) => {
if (!username) {
setError({
show: true,
content: "请输入用户名"
});
return;
}
if (!password) {
setError({
show: true,
content: "请输入密码"
});
return;
}
setIsLoading(true);
let res = await dispatch(login({
grant_type: "password",
username,
password
}));
if (res.payload.errorMsg) {
message.warning(res.payload.errorMsg);
} else {
router.push("/");
}
setIsLoading(false);
};
function render() {
return props.isLogin ? (
<></>
) : (
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.card}>
<div className={styles.cardBody}>
<div className={styles.error}>{error.show ?
<Alert message={error.content} type="error" closable afterClose={closeError}/> : null}</div>
<div className={styles.cardContent}>
<Form
{...layout}
name="basic"
initialValues={{remember: true}}
layout="vertical"
onFinish={onFinish}
// onFinishFailed={onFinishFailed}
>
<div className={styles.formlabel}>
<b>用户名或邮箱</b>
</div>
<Form.Item name="username">
<Input size="large"/>
</Form.Item>
<div className={styles.formlabel}>
<b>密码</b>
<Link href="/account/password_reset" target="_blank">
忘记密码
</Link>
</div>
<Form.Item name="password">
<Input.Password size="large"/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block size="large" className="submit" loading={isLoding}>
{isLoding ? "正在登录..." : "登录"}
</Button>
</Form.Item>
</Form>
<div className={styles.newaccount}>
首次使用 Seaurl?{" "}
<Link href="/join?ref=register" target="_blank">
创建一个账号
</Link>
{/* <a className="login-form-forgot" href="" >
创建一个账号</a> */}
</div>
</div>
</div>
<div className={styles.recordWrapper}>
<Record/>
</div>
</div>
</div>
</div>
);
}
return render();
};
export const getServerSideProps = wrapper.getServerSideProps(store => ({ctx}) => {
const {isLogin, me} = store.getState().auth;
if(isLogin){
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: {}
};
});
export default Login;
注意
1、使用了next-redux-wrapper一定要加 HYDRATE,目的是同步服务端和客户端 reducer 数据,否则两个端数据不一致造成冲突。
[HYDRATE]: (state, action) => {
console.log('HYDRATE', state, action.payload);
return Object.assign({}, state, {...action.payload.auth});
},
2、注意next-redux-wrapper和next-redux-cookie-wrapper版本。
"next-redux-cookie-wrapper": "^2.0.1", "next-redux-wrapper": "^7.0.2",
总结
- ssr 项目不要用持久化,而是直接从 server 端请求接口拿数据直接渲染,否则失去使用 SSR 的意义了;
- Next.js 分为静态渲染和服务端渲染,其实 SSR 项目如果你的项目很小,或者都是静态数据可以考虑直接使用客户端静态方法
getStaticProps来渲染。