Web app taking too long to determine whether user is authenticated or not

Background info

I have just started using/ learning how to use Next.js and I’m facing an issue where my user authentication logic is making my components take a long time to render on the page. I believe I’m missing something really fundamental and I’m not sure if it’s Next.js related or something to do with how I’m handling user states across my application.

I’m using Firebase’s Google Authentication to handle my user login.

The code I will be referring to in my question exists in the following repository:
https://github.com/myamazingthrowaway/nextjswebsite

A live demo of the app can be found here:
https://nextjswebsite-kappa-sand.now.sh/

(it uses cross-site cookies to handle firebase google login – I don’t know how to change this default behaviour, so if it doesn’t work first time, make sure your browser allows cross-site cookies)

I based my authentication logic on the following repository:
https://github.com/taming-the-state-in-react/nextjs-redux-firebase-authentication

My web app was made with create-next-app.

The Problem

When a user visits my website, the sidebar component and the rest of the components which rely on the logged in state of the user don’t appear immediately on page load. They appear some time after the initial page is rendered. It’s a significant delay and there is no ‘loading indicator’ on my chrome tab to say that the dom is still being built

Is this the expected behaviour?

The issue can also be seen on the following site (signing in with google demonstrates what I mean).

Steps to reproduce:

1. Go to: https://kage.saltycrane.com/

2. Press ‘Sign In’

3. Press ‘Sign In with Google’

You will get redirected to the google sign in page, select an account etc. (to sign in)

Then you will be redirected back to the site in step 1, where the menu bar at the top still remains ‘Sign in’.. for a moment or two, before it changes to your email address.

Why does this happen?

(the code behind the webpage above is here: https://github.com/saltycrane/kage)

My Code

In my _app.js file, I have a ‘Shell’ component which handles my sidebar & navbar for the entire web app. It accepts child components to be rendered within the confines of the sidebar etc. Perhaps this isn’t the best way to handle how the application flows (would be more than happy for suggestions on how to improve this).

The _app.js file looks like this:

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);

My Shell component looks like this:

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="https//github.com/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);

As you can see, the Shell component uses a HOC to wrap it with an authUser prop from the session state. I don’t know if this is what causes issues when loading the page?

The ProfileIcon component doesn’t load immediately when user signs in either. Similarly to the kage website I mentioned earlier. I don’t understand why this happens. I feel like my code is all over the place.

My signin.js page looks like this:

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 };

Where AppWithAuthentication looks like this:

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 };

So whenever a user goes onto my webpage and tries to access any ‘authenticated only’ route, they will see the content of the route first for a few seconds, then get redirected to the sign in page. I don’t want this to happen and I cannot see why this happens either.

How can I fix these issues? I’m completely stuck for ideas. Need a fresh pair of eyes to help me understand where the issue is.

Source: ReactJs