reactjs - React Router 在本地工作,但在部署到 Heroku 后不起作用
问题描述
在这里,我一直在使用 Create React App 开发一个 React Web 应用程序,但我没有使用过Express Framework和Webpack。此外,我已将其集成到 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"
]
}
}
解决方案
推荐阅读
- javascript - Box Node SDK 不使用 readStream 提取文件
- python - 我可以创建一个脚本来测试我是否可以在 python 中远程在服务器上使用 SSH 和 PING 命令吗?
- json - 如何反序列化在另一个字段中指定值类型的 JSON?
- python - 如何删除具有 NaN 的重复行并保留不具有 NaN 的重复行
- javascript - DOM 元素在字段中的输入更改时加载,而不是在页面加载时加载
- git - 如何在 GitHub 上的 PR 中附加测试报告
- r - 在组内使用 ifelse 子句重新编码
- oracle - Spring Boot/JDBC 上的 TKProf(Oracle 事件 10046)
- javascript - socket.io 如何改变连接取决于服务器
- amazon-web-services - 在构建 API 时,AWS 中的小时价格如何运作?