首页 > 解决方案 > React Router 在本地工作,但在部署到 Heroku 后不起作用

问题描述

在这里,我一直在使用 Create React App 开发一个 React Web 应用程序,但我没有使用过Express FrameworkWebpack。此外,我已将其集成到 Spring Boot 应用程序中。Spring Boot 应用程序包含前端 React Web 应用程序使用的所有 API。现在,在将两个应用程序集成到一个之后,我在本地对其进行了测试并将一个应用程序部署到 Heroku 服务器。部署后,只能查看登陆页面。其他登录、仪表板页面等无法打开,出现以下错误:

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Tue Jun 30 05:18:45 UTC 2020
There was an unexpected error (type=Not Found, status=404).
No message available

我已经在本地环境中进行了测试,一切正常,但是在部署后没有任何效果,我尝试了很多解决方案,但没有一个适合我。请帮我解决这个问题。

在这里,我添加了代码片段,请查看代码并帮助我找到解决方案。

公共/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <link href="%PUBLIC_URL%/favicon.ico" rel="icon"/>
    <meta content="width=device-width, initial-scale=1" name="viewport"/>
    <meta content="#000000" name="theme-color"/>
    <meta
            content="Web site created using create-react-app"
            name="description"
    />
    <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
    <title>Zodi</title>
</head>
<body style="margin: 0">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

src/index.js

import React from 'react';
import {render} from 'react-dom';
import {Provider} from 'react-redux';

import {store} from './_helpers';
import CssBaseline from '@material-ui/core/CssBaseline';
import {ThemeProvider} from '@material-ui/core/styles';
import {App} from './App';
import theme from './utils/Theme';

import * as serviceWorker from './serviceWorker';

render(
    <Provider store={store}>
        <ThemeProvider theme={theme}>
            {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
            <CssBaseline/>
            <App/>
        </ThemeProvider>
    </Provider>,
    document.getElementById('root')
);

serviceWorker.unregister();

src/main/App.js

import React, {Component} from 'react';
import {Route, Router, Switch} from "react-router-dom";
import {connect} from "react-redux";
import {history} from '../_helpers';
import {alertActions} from '../_actions';
import {Login} from "../login/Login";
import {Dashboard} from "../dashboard/Dashboard";
import {PrivateRoute} from "../_components";
import Welcome from "../main/Welcome";
import './App.css';
import ErrorNotFound from "../error/ErrorNotFound";
import {Alert} from '@material-ui/lab';

class App extends Component {
    constructor(props) {
        super(props);

        const {dispatch} = this.props;
        history.listen((location, action) => {
            // clear alert on location change
            dispatch(alertActions.clear());
        });
    }

    render() {
        const {alert} = this.props;
        const heading = "Welcome To Zodi";
        const quote = "This project includes simple spring boot application with spring security and react js as frontend for authentication with JWT.";
        const footer = "Kabindra Shrestha";

        return (
            <div>
                {alert.message &&
                <Alert severity="error">{alert.message}</Alert>
                }
                <Router history={history}>
                    <Switch className="padding-left">
                        <Route path="/" exact
                               component={() => <Welcome heading={heading} quote={quote} footer={footer}/>}/>
                        <Route path="/login/admin" exact component={Login}/>
                        <PrivateRoute path="/dashboard" exact component={Dashboard}/>
                        <Route component={() => <ErrorNotFound/>}/>
                    </Switch>
                </Router>
            </div>
        );
    }
}

function mapStateToProps(state) {
    const {alert} = state;
    return {
        alert
    };
}

const connectedApp = connect(mapStateToProps)(App);
export {connectedApp as App};

src/login/Login.js

import React, {Component} from 'react';
import {
    Avatar,
    Box,
    Button,
    CircularProgress,
    Container,
    CssBaseline,
    Link,
    TextField,
    Typography,
    withStyles
} from '@material-ui/core';
import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
import {loginActions} from "../_actions";
import {connect} from "react-redux";
import {withRouter} from "react-router-dom";

const useStyles = theme => ({
    paper: {
        marginTop: theme.spacing(8),
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
    },
    avatar: {
        margin: theme.spacing(1),
        backgroundColor: theme.palette.secondary.main,
    },
    form: {
        width: '100%', // Fix IE 11 issue.
        marginTop: theme.spacing(1),
    },
    submit: {
        margin: theme.spacing(3, 0, 2),
    },
    spinner: {
        display: 'block',
        marginLeft: 'auto',
        marginRight: 'auto'
    },
});

class Login extends Component {

    constructor(props) {
        super(props);

        // reset login status
        this.props.dispatch(loginActions.logout());

        this.state = {
            username: '',
            password: '',
            submitted: false
        };

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleChange(e) {
        const {name, value} = e.target;
        this.setState({[name]: value});
    }

    handleSubmit(e) {
        e.preventDefault();

        this.setState({submitted: true});
        const {username, password} = this.state;
        const {dispatch} = this.props;
        if (username && password) {
            dispatch(loginActions.login(username, password));
        }
    }

    render() {
        const {classes} = this.props;

        const {loggingIn} = this.props;
        const {username, password, submitted} = this.state;

        return (
            <Container component="main" maxWidth="xs">
                <CssBaseline/>
                <div className={classes.paper}>
                    <Avatar className={classes.avatar}>
                        <LockOutlinedIcon/>
                    </Avatar>
                    <Typography component="h1" variant="h5">
                        Sign in
                    </Typography>
                    <form className={classes.form} noValidate onSubmit={this.handleSubmit}>
                        <TextField
                            variant="outlined"
                            margin="normal"
                            required
                            fullWidth
                            id="username"
                            name="username"
                            label="Username"
                            type="text"
                            value={username}
                            autoComplete="email"
                            autoFocus
                            onChange={this.handleChange}
                            className={(submitted && !username ? 'has-error' : '')}
                            helperText={submitted && !username && "Username is required"}/>
                        <TextField
                            variant="outlined"
                            margin="normal"
                            required
                            fullWidth
                            id="password"
                            name="password"
                            label="Password"
                            type="password"
                            value={password}
                            autoComplete="current-password"
                            onChange={this.handleChange}
                            className={(submitted && !password ? 'has-error' : '')}
                            helperText={submitted && !password && "Password is required"}/>
                        {/*<FormControlLabel
                            control={<Checkbox value="remember" color="primary"/>}
                            label="Remember me"
                        />*/}
                        <Button
                            variant="contained"
                            fullWidth
                            type="submit"
                            color="primary"
                            className={classes.submit}>
                            Sign In
                        </Button>
                        {loggingIn &&
                        <CircularProgress className={classes.spinner}/>
                        }
                        {/*<Grid container>
                            <Grid item xs>
                                <Link href="#" variant="body2">
                                    Forgot password?
                                </Link>
                            </Grid>
                            <Grid item>
                                <Link href="#" variant="body2">
                                    {"Don't have an account? Sign Up"}
                                </Link>
                            </Grid>
                        </Grid>*/}
                    </form>
                </div>
                <Box mt={8}>
                    <Copyright/>
                </Box>
            </Container>
        );
    }
}

function Copyright() {
    return (
        <Typography variant="body2" color="textSecondary" align="center">
            {'Copyright © '}
            <Link color="inherit" href="https://material-ui.com/">
                Zodi
            </Link>{' '}
            {new Date().getFullYear()}
            {'.'}
        </Typography>
    );
}

function mapStateToProps(state) {
    const {loggingIn} = state.authentication;
    return {
        loggingIn
    };
}

const connectedLoginPage = withStyles(useStyles, {withTheme: true})(withRouter(connect(mapStateToProps)(Login)));
export {connectedLoginPage as Login};

src/dashboard/Dashboard.js

import React, {Component} from 'react';
import {connect} from 'react-redux';

import {userActions} from '../_actions';
import AppBarNavigation from "../main/AppBarNavigation";
import {withStyles} from "@material-ui/core";
import {withRouter} from "react-router-dom";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import Card from "@material-ui/core/Card/Card";

const useStyles = theme => ({
    root: {
        margin: '1.5rem',
        borderWidth: '.2rem',
        position: 'relative'
    },
    content: {
        padding: '4rem 2rem !important',
        backgroundColor: theme.palette.card.background,
        borderRadius: '.3rem'
    },
    title: {
        fontSize: '3.5rem',
        fontWeight: 300,
        lineHeight: 1.2,
        marginBottom: '.5rem',
        marginTop: 0,
        display: 'block',
        marginBlockStart: '0.67em',
        marginBlockEnd: '0.67em',
        marginInlineStart: '0px',
        marginInlineEnd: '0px',
        color: theme.palette.text,
        textAlign: 'left'
    },
    quote: {
        fontSize: '1.25rem',
        fontWeight: 300,
        marginTop: 0,
        marginBottom: '1rem',
        display: 'block',
        marginBlockStart: '1em',
        marginBlockEnd: '1em',
        marginInlineStart: '0px',
        marginInlineEnd: '0px',
        lineHeight: 1.5,
        color: theme.palette.text,
        textAlign: 'left'
    },
    footer: {
        marginTop: 0,
        marginBottom: '1rem',
        display: 'block',
        marginBlockStart: '1em',
        marginBlockEnd: '1em',
        marginInlineStart: '0px',
        marginInlineEnd: '0px',
        fontSize: '1rem',
        fontWeight: 400,
        lineHeight: 1.5,
        color: theme.palette.text,
        textAlign: 'left'
    },
    space: {
        marginBottom: '1.5rem!important',
        marginTop: '1.5rem!important',
        border: 0,
        borderTop: '1px solid rgba(0,0,0,.1)',
        boxSizing: 'content-box',
        height: 0,
    },
});

class Dashboard extends Component {
    componentDidMount() {
        this.props.dispatch(userActions.getAll());
    }

    render() {
        const {classes} = this.props;
        const {user, users, usersData} = this.props;

        return (<div>
                <AppBarNavigation/>

                <div>
                    <Card className={classes.root}>
                        <CardContent className={classes.content}>
                            <Typography className={classes.title} gutterBottom>
                                <p>Hi {user.firstname + " " + user.lastname}! From Authentication Redux</p>
                            </Typography>
                            <Typography className={classes.title} gutterBottom>
                                {usersData &&
                                <p>Hi {usersData.firstname + " " + usersData.lastname}! From Users Redux</p>}
                            </Typography>
                            <Typography className={classes.quote}>
                                <p>You're logged in with React & JWT!!</p>
                            </Typography>
                            <hr className={classes.space}/>
                            <Typography className={classes.footer}>
                                {users.loading && <em>Loading users...</em>}
                                {users.error && <p className="text-danger">ERROR: {users.error}</p>}
                            </Typography>
                            <Typography className={classes.footer}>
                                {users.usersStatus &&
                                <p className="text-danger">STATUS: {users.usersStatus ? "True" : "False"}</p>}
                                {users.usersMessage &&
                                <p className="text-danger">MESSAGE: {users.usersMessage}</p>}
                            </Typography>
                        </CardContent>
                    </Card>
                </div>
            </div>
        );
    }
}

function mapStateToProps(state) {
    const {users, authentication} = state;
    const {user} = authentication;
    const {usersData} = users;
    return {
        user,
        users,
        usersData
    };
}

const connectedDashboard = withStyles(useStyles, {withTheme: true})(withRouter(connect(mapStateToProps)(Dashboard)));
export {connectedDashboard as Dashboard};

包.json

{
  "name": "frontend",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "@material-ui/core": "^4.10.2",
    "@material-ui/icons": "^4.9.1",
    "@material-ui/lab": "latest",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "env-cmd": "^10.1.0",
    "history": "4.7.2",
    "axios": "^0.19.2",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "3.4.1",
    "react-redux": "5.1.2",
    "redux": "4.0.5",
    "redux-logger": "3.0.6",
    "redux-thunk": "2.3.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

标签: reactjsspring-bootherokureact-reduxreact-router

解决方案


推荐阅读