我刚刚开始使用/学习如何使用Next.js,并且遇到了一个问题,我的用户身份验证逻辑使我的组件需要很长时间才能在页面上呈现。我相信我缺少真正重要的东西,并且不确定是与Next.js相关还是与我如何处理应用程序中的用户状态有关。
我正在使用Firebase的Google身份验证来处理我的用户登录。
我将在我的问题中引用的代码位于以下存储库中:https://github.com/myamazingthrowaway/nextjswebsite
可以在此处找到该应用的实时演示:https://nextjswebsite-kappa-sand.now.sh/
((它使用跨站点cookie来处理Firebase google登录-我不知道如何更改此默认行为,因此,如果第一次无法使用,请确保您的浏览器允许跨站点cookie)
我将身份验证逻辑基于以下存储库:https://github.com/taming-the-state-in-react/nextjs-redux-firebase-authentication
我的Web应用是用create-next-app
制作的。
[当用户访问我的网站时,侧栏组件以及依赖于用户登录状态的其余组件不会在页面加载时立即显示。它们在呈现初始页面后的一段时间出现。这是一个严重的延迟,我的chrome标签上没有“加载指示器”,表示dom仍在构建中。这是否是预期的行为?
该问题也可以在以下网站上看到(使用google登录可以证明我的意思)。重现步骤:1.转到:https://kage.saltycrane.com/2.按“登录”3.按“使用Google登录”您将被重定向到Google登录页面,选择一个帐户等。(登录)然后,您将在第1步中被重定向回该站点,该站点顶部的菜单栏在更改为您的电子邮件地址之前仍会保持“登录”状态一两分钟。
为什么会这样?
((上面网页的代码在这里:https://github.com/saltycrane/kage)
在我的_app.js
文件中,我有一个“外壳”组件,用于处理整个Web应用程序的边栏和导航栏。它接受要在侧边栏等范围内呈现的子组件。也许这不是处理应用程序流程的最佳方法(对于如何改进此方法的建议将非常高兴)。
_app.js
文件看起来像这样:
import React from "react";
import App from "next/app";
import CssBaseline from "@material-ui/core/CssBaseline";
import { ThemeProvider } from "@material-ui/styles";
import { Provider } from "react-redux";
import withRedux from "next-redux-wrapper";
import initStore from "../src/store";
import theme from "../src/theme";
import Shell from "../src/components/Shell";
class EnhancedApp extends App {
static async getInitialProps({ Component, ctx }) {
return {
pageProps: Component.getInitialProps
? await Component.getInitialProps(ctx)
: {}
};
}
componentDidMount() {
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentNode.removeChild(jssStyles);
}
}
render() {
const { Component, pageProps, store } = this.props;
return (
<>
<Provider store={store}>
<ThemeProvider theme={theme}>
<title>Next.js</title>
<CssBaseline />
<Shell>
<Component {...pageProps} />
</Shell>
</ThemeProvider>
</Provider>
</>
);
}
}
export default withRedux(initStore)(EnhancedApp);
我的Shell
组件看起来像这样:
import React from "react";
import Router from "next/router";
import { connect } from "react-redux";
import {
Drawer,
List,
Divider,
ListItem,
ListItemIcon,
ListItemText,
Hidden,
AppBar,
Toolbar,
IconButton,
Button
} from "@material-ui/core";
import { ProfileIcon } from "../index";
import MonetizationOnOutlinedIcon from "@material-ui/icons/MonetizationOnOutlined";
import AccountBalanceWalletRoundedIcon from "@material-ui/icons/AccountBalanceWalletRounded";
import AccountBoxRoundedIcon from "@material-ui/icons/AccountBoxRounded";
import VpnKeyRoundedIcon from "@material-ui/icons/VpnKeyRounded";
import ExitToAppRoundedIcon from "@material-ui/icons/ExitToAppRounded";
import MenuIcon from "@material-ui/icons/Menu";
import { makeStyles } from "@material-ui/core/styles";
import * as routes from "../../constants/routes";
import { auth } from "../../firebase/firebase";
const drawerWidth = 180;
const useStyles = makeStyles(theme => ({
content: {
flexGrow: 1,
padding: theme.spacing(3)
},
root: {
display: "flex"
},
container: {
flexGrow: 1
},
toolbar: theme.mixins.toolbar,
drawer: {
[theme.breakpoints.up("md")]: {
width: drawerWidth,
flexShrink: 0
}
},
drawerPaper: {
width: drawerWidth
},
appBar: {
background: "linear-gradient(45deg, #FF8E53 30%, #ff4d73 90%)",
marginLeft: drawerWidth,
[theme.breakpoints.up("md")]: {
width: `calc(100% - ${drawerWidth}px)`
}
},
logoContainer: {
background: "linear-gradient(45deg, #ff4d73 30%, #FF8E53 90%)",
justifyContent: "center",
flexDirection: "column",
height: "15rem"
},
menuButton: {
marginRight: theme.spacing(2),
[theme.breakpoints.up("md")]: {
display: "none"
}
},
rightAlign: {
marginLeft: "auto",
marginRight: -12,
cursor: "pointer"
},
hoverCursor: {
cursor: "pointer"
}
}));
const Shell = ({ children, authUser }) => {
const classes = useStyles();
const [mobileOpen, setMobileOpen] = React.useState(false);
const handleGoToEarnPage = () => {
Router.push(routes.EARN);
if (mobileOpen) handleDrawerToggle();
};
const handleGoToSignInPage = () => {
Router.push(routes.SIGN_IN);
if (mobileOpen) handleDrawerToggle();
};
const handleGoToWithdrawPage = () => {
Router.push(routes.WITHDRAW);
if (mobileOpen) handleDrawerToggle();
};
const handleGoToProfilePage = () => {
Router.push(routes.PROFILE);
if (mobileOpen) handleDrawerToggle();
};
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const handleGoToHomePage = () => {
Router.push(routes.LANDING);
if (mobileOpen) handleDrawerToggle();
};
const handleSignOut = () => {
auth.signOut();
if (mobileOpen) handleDrawerToggle();
};
const drawer = (
<>
<AppBar position="static">
<Toolbar className={classes.logoContainer}>
<img
src="/images/logo/logo.png"
alt="my logo"
height="120rem"
onClick={handleGoToHomePage}
className={classes.hoverCursor}
/>
</Toolbar>
</AppBar>
<List>
<ListItem button key="Earn" href="/earn" onClick={handleGoToEarnPage}>
<ListItemIcon>
<MonetizationOnOutlinedIcon />
</ListItemIcon>
<ListItemText primary="Earn" />
</ListItem>
<ListItem
button
key="Withdraw"
href="/withdraw"
onClick={handleGoToWithdrawPage}
>
<ListItemIcon>
<AccountBalanceWalletRoundedIcon />
</ListItemIcon>
<ListItemText primary="Withdraw" />
</ListItem>
<Divider variant="middle" />
{!authUser && (
<List>
<ListItem
button
key="Sign In"
href="/signin"
onClick={handleGoToSignInPage}
>
<ListItemIcon>
<VpnKeyRoundedIcon />
</ListItemIcon>
<ListItemText primary="Sign In" />
</ListItem>
</List>
)}
{authUser && (
<List>
<ListItem
button
key="Profile"
href="/profile"
onClick={handleGoToProfilePage}
>
<ListItemIcon>
<AccountBoxRoundedIcon />
</ListItemIcon>
<ListItemText primary="Profile" />
</ListItem>
<ListItem button key="Sign Out" onClick={handleSignOut}>
<ListItemIcon>
<ExitToAppRoundedIcon />
</ListItemIcon>
<ListItemText primary="Sign Out" />
</ListItem>
</List>
)}
</List>
</>
);
return (
<div className={classes.root}>
<AppBar position="fixed" className={classes.appBar}>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
className={classes.menuButton}
>
<MenuIcon />
</IconButton>
<div className={classes.rightAlign}>
{authUser && <ProfileIcon className={classes.hoverCursor} />}
{!authUser && (
<Button color="inherit" onClick={handleGoToSignInPage}>
Sign In
</Button>
)}
</div>
</Toolbar>
</AppBar>
<nav className={classes.drawer} aria-label="sidebar">
<Hidden mdUp>
<Drawer
variant="temporary"
anchor={classes.direction === "rtl" ? "right" : "left"}
open={mobileOpen}
onClose={handleDrawerToggle}
classes={{
paper: classes.drawerPaper
}}
ModalProps={{
keepMounted: true // Better open performance on mobile.
}}
>
{drawer}
</Drawer>
</Hidden>
<Hidden smDown>
<Drawer
classes={{
paper: classes.drawerPaper
}}
variant="permanent"
open
>
{drawer}
</Drawer>
</Hidden>
</nav>
<main className={classes.content}>
<div className={classes.toolbar} />
{children}
</main>
</div>
);
};
const mapStateToProps = state => ({
authUser: state.sessionState.authUser
});
export default connect(mapStateToProps)(Shell);
如您所见,Shell
组件使用HOC从会话状态将其与authUser
属性一起包装。我不知道这是什么原因导致加载页面时出现问题?
ProfileIcon
组件不会在用户登录时立即加载。与我之前提到的kage
网站类似。我不明白为什么会这样。我觉得我的代码无处不在。
我的signin.js
页面看起来像这样:
import React from "react";
import Router from "next/router";
import Button from "@material-ui/core/Button";
import { AppWithAuthentication } from "../src/components/App";
import { auth, provider } from "../src/firebase/firebase";
import { db } from "../src/firebase";
import * as routes from "../src/constants/routes";
const SignInPage = () => (
<AppWithAuthentication>
<h1>Sign In</h1>
<SignInForm />
</AppWithAuthentication>
);
const updateByPropertyName = (propertyName, value) => () => ({
[propertyName]: value
});
const INITIAL_STATE = {
user: null,
error: null
};
class SignInForm extends React.Component {
constructor(props) {
super(props);
this.state = { ...INITIAL_STATE };
if (auth.currentUser) {
console.log(`already signed in`);
Router.push(routes.HOME);
}
}
componentDidMount() {
auth.onAuthStateChanged(user => {
if (user) {
console.log(user);
// add them to the db and then redirect
db.doCreateUser(
user.uid,
user.email,
user.displayName,
user.photoURL,
false
)
.then(() => {
this.setState(() => ({ ...INITIAL_STATE }));
Router.push(routes.HOME);
})
.catch(error => {
this.setState(updateByPropertyName("error", error));
});
} else {
console.log(`No active user found. User must log in`);
}
});
}
onClick = () => {
auth.signInWithRedirect(provider);
};
render() {
return (
<Button variant="contained" color="primary" onClick={this.onClick}>
Sign In with Google
</Button>
);
}
}
export default SignInPage;
export { SignInForm };
AppWithAuthentication
看起来像这样:
import React from "react";
import { compose } from "recompose";
import withAuthentication from "../Session/withAuthentication";
import withAuthorisation from "../Session/withAuthorisation";
const App = ({ children }) => (
<div className="app">
{children}
</div>
);
const AppWithAuthentication = compose(
withAuthentication,
withAuthorisation(false)
)(App);
const AppWithAuthorisation = compose(
withAuthentication,
withAuthorisation(true)
)(App);
export { AppWithAuthentication, AppWithAuthorisation };
因此,只要用户进入我的网页并尝试访问任何“仅经过身份验证的”路由,他们就会在几秒钟内首先看到该路由的内容,然后重定向到登录页面。我不希望发生这种情况,我也不明白为什么会发生这种情况。
如何解决这些问题?我完全想念主意。需要新鲜的眼睛来帮助我了解问题出在哪里。
这是由等待响应的认证过程的请求引起的。使用开发人员工具(Ctrl + Shift + I)->网络->在chrome中检查您的网络连接,然后重新加载您的应用并查看触发的请求。您会发现,在您单击登录并选择一个帐户后,您的州将等待https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAssertion?key=xxx返回带有Google帐户名称等的响应。
使加载程序的可见性取决于请求返回的信息(在您的状态下创建一个变量(exp .:用户),一旦您从Google获得用户信息,请将其设置为用户对象。然后您可以说!user-> show loader。您还可以使用其他状态变量来触发加载程序,该状态变量在您单击登录时设置为true。
考虑到您正在使用Firebase,请查看https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial了解更多信息。我也将在此处包括代码,但是我认为您可以使用此信息来根据我想要构建应用程序的方式来构建我所说的内容。不应有太大的关系。希望对您有所帮助!
删除此部分
static async getInitialProps({ Component, ctx }) {
return {
pageProps: Component.getInitialProps
? await Component.getInitialProps(ctx)
: {}
};
}
让nextjs自行进行优化。当您自己处理初始道具时,优化就会丢失。