[英]Create-react-app with express backend works locally but routing incorrect when deployed to heroku
I followed this article on how to create an app using create-react-app with an express backend and the spotify api, and have been trying to deploy it to heroku ever since: https://medium.com/@jonnykalambay/now-playing-using-spotifys-awesome-api-with-react-7db8173a7b13 我关注了这篇关于如何使用带有后端后端和Spotify API的create-react-app创建应用程序的文章,并且从那以后一直尝试将其部署到heroku: https ://medium.com/@jonnykalambay/now- 打-使用-spotifys -真棒-API与反应的-7db8173a7b13
Everything seems to be working fine locally, the authorization flows well and once it's completed, express serves a static index.html containing a div with a 'root' id on the client side, and index.js renders the react 'App' component within it. 一切似乎在本地都可以正常工作,授权流程很好,一旦完成,express将提供一个静态index.html,其中包含一个在客户端具有div根ID的div,而index.js则在其中呈现react的“ App”组件它。
However, when the app is deployed to heroku, the server-side process still works, authorization and redirect succeed, but the index.html express serves is the upper, server-side one (in the project's root 'public' directory), where no react script is running. 但是,将应用程序部署到heroku时,服务器端进程仍然可以工作,授权和重定向成功,但是index.html express服务是服务器端的上层服务器(在项目的根“ public”目录中),没有反应脚本正在运行。 Even when trying to 'force' the script to run by adding react, babel, the index.js script and a 'root' div to this index.html, nothing seems to work.
即使试图通过在此index.html中添加react,babel,index.js脚本和“ root” div来“强制”脚本运行,也似乎没有任何效果。
The Github repo can be found @ https://github.com/Johnnybar/spot.stats and the app can be found @ https://spot-stats.herokuapp.com/ GitHub的回购可以发现@ https://github.com/Johnnybar/spot.stats和应用程序可以发现@ https://spot-stats.herokuapp.com/
Feels like there is something simple I'm missing here, but after trying to hack my way to deployment for a while now, I'd really appreciate any tips, thanks! 感觉这里缺少一些简单的东西,但是在尝试破解一段时间的部署方式后,我真的很感谢任何提示,谢谢!
app.js (app-root/, the authorization flow, succeeds both in heroku and locally) app.js(app-root /,授权流程,在heroku和本地均成功)
/**
* https://developer.spotify.com/web-api/authorization-guide/#authorization_code_flow
*/
var express = require('express'); // Express web server framework
var request = require('request'); // "Request" library
var querystring = require('querystring');
var cookieParser = require('cookie-parser');
let secrets;
let client_id;
let client_secret;
let redirect_uri;
let client_url;
if (process.env.NODE_ENV != 'production') {
secrets = require('./secrets.json');
client_id = secrets.client_id;
client_secret = secrets.client_secret;
redirect_uri = secrets.redirect_uri;
client_url = 'http://localhost:3000/#';
}
else{
client_id = process.env.CLIENT_ID;
client_secret = process.env.CLIENT_SECRET;
redirect_uri = process.env.REDIRECT_URI;
client_url = 'https://spot-stats.herokuapp.com/#';
}
/**
* Generates a random string containing numbers and letters
* @param {number} length The length of the string
* @return {string} The generated string
*/
var generateRandomString = function(length) {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
var stateKey = 'spotify_auth_state';
var app = express();
app.use(express.static(__dirname + '/public'))
.use(cookieParser());
app.get('/login', function(req, res) {
var state = generateRandomString(16);
res.cookie(stateKey, state);
// application requests authorization
var scope = 'user-read-private user-read-email user-read-playback-state user-top-read';
res.redirect('https://accounts.spotify.com/authorize?' +
querystring.stringify({
response_type: 'code',
client_id: client_id,
scope: scope,
redirect_uri: redirect_uri,
state: state
}));
});
app.get('/callback', function(req, res) {
// application requests refresh and access tokens
// after checking the state parameter
var code = req.query.code || null;
var state = req.query.state || null;
var storedState = req.cookies ? req.cookies[stateKey] : null;
// console.log('here storedstate: ', storedState);
if (state === null || state !== storedState) {
res.redirect('/#' +
querystring.stringify({
error: 'state_mismatch'
}));
} else {
res.clearCookie(stateKey);
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: redirect_uri,
grant_type: 'authorization_code'
},
headers: {
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64'))
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token,
refresh_token = body.refresh_token;
var options = {
url: 'https://api.spotify.com/v1/me',
headers: { 'Authorization': 'Bearer ' + access_token },
json: true
};
// use the access token to access the Spotify Web API
request.get(options, function(error, response, body) {
console.log(body);
});
// we can also pass the token to the browser to make requests from there
res.redirect(client_url +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.redirect('/#' +
querystring.stringify({
error: 'invalid_token'
}));
}
});
}
});
app.get('/refresh_token', function(req, res) {
// requesting access token from refresh token
var refresh_token = req.query.refresh_token;
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: { 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) },
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token;
res.send({
'access_token': access_token
});
}
});
});
console.log('Listening on 5000');
app.listen(process.env.PORT || 5000);
index.html (app-root/public, displayed on heroku after successful auth - this is not the desired index.html) index.html(应用程序根目录/公共,在成功通过身份验证后显示在heroku上-这不是所需的index.html)
<!doctype html>
<html>
<head>
<title>Authorization Code flow with Spotify</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<style type="text/css">
#login, #loggedin {
display: none;
}
.text-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 500px;
}
</style>
</head>
<body>
<div class="container">
<div id="login">
<a href="/login" class="btn btn-primary">Log in with Spotify</a>
</div>
<div id="loggedin">
<div id="user-profile">
</div>
<div id="oauth">
</div>
<button class="btn btn-default" id="obtain-new-token">Obtain new token using the refresh token</button>
</div>
</div>
<script id="user-profile-template" type="text/x-handlebars-template">
<h1>Logged in as {{display_name}}</h1>
<div class="media">
<div class="pull-left">
<img class="media-object" width="150" src="{{images.0.url}}" />
</div>
<div class="media-body">
<dl class="dl-horizontal">
<dt>Display name</dt><dd class="clearfix">{{display_name}}</dd>
<dt>Id</dt><dd>{{id}}</dd>
<dt>Email</dt><dd>{{email}}</dd>
<dt>Spotify URI</dt><dd><a href="{{external_urls.spotify}}">{{external_urls.spotify}}</a></dd>
<dt>Link</dt><dd><a href="{{href}}">{{href}}</a></dd>
<dt>Profile Image</dt><dd class="clearfix"><a href="{{images.0.url}}">{{images.0.url}}</a></dd>
<dt>Country</dt><dd>{{country}}</dd>
</dl>
</div>
</div>
</script>
<script id="oauth-template" type="text/x-handlebars-template">
<h2>oAuth info</h2>
<dl class="dl-horizontal">
<dt>Access token</dt><dd class="text-overflow">{{access_token}}</dd>
<dt>Refresh token</dt><dd class="text-overflow">{{refresh_token}}</dd>
</dl>
</script>
<script src="https://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0-alpha.1/handlebars.min.js"></script>
<script>
(function() {
/**
* Obtains parameters from the hash of the URL
* @return Object
*/
function getHashParams() {
var hashParams = {};
var e, r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
while ( e = r.exec(q)) {
hashParams[e[1]] = decodeURIComponent(e[2]);
}
return hashParams;
}
var userProfileSource = document.getElementById('user-profile-template').innerHTML,
userProfileTemplate = Handlebars.compile(userProfileSource),
userProfilePlaceholder = document.getElementById('user-profile');
var oauthSource = document.getElementById('oauth-template').innerHTML,
oauthTemplate = Handlebars.compile(oauthSource),
oauthPlaceholder = document.getElementById('oauth');
var params = getHashParams();
var access_token = params.access_token,
refresh_token = params.refresh_token,
error = params.error;
if (error) {
alert('There was an error during the authentication');
} else {
if (access_token) {
// render oauth info
oauthPlaceholder.innerHTML = oauthTemplate({
access_token: access_token,
refresh_token: refresh_token
});
$.ajax({
url: 'https://api.spotify.com/v1/me',
headers: {
'Authorization': 'Bearer ' + access_token
},
success: function(response) {
userProfilePlaceholder.innerHTML = userProfileTemplate(response);
$('#login').hide();
$('#loggedin').show();
}
});
} else {
// render initial screen
$('#login').show();
$('#loggedin').hide();
}
document.getElementById('obtain-new-token').addEventListener('click', function() {
$.ajax({
url: '/refresh_token',
data: {
'refresh_token': refresh_token
}
}).done(function(data) {
access_token = data.access_token;
oauthPlaceholder.innerHTML = oauthTemplate({
access_token: access_token,
refresh_token: refresh_token
});
});
}, false);
}
})();
</script>
</body>
</html>
index.html (app-root/client/public, works locally after auth and renders App component in app/client/src. This is the desired index.html) index.html(app-root / client / public,经过身份验证后在本地工作,并在app / client / src中呈现App组件。这是所需的index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Spotify app</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
index.js (app-root/client/src) index.js(app-root / client / src)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
package.json scripts (app-root/) package.json脚本(app-root /)
"scripts": {
"start": "concurrently \"cd client && npm start\" \"node app.js\"",
"heroku-postbuild": "cd client && npm install && npm run build",
"push": "concurrently \"git push heroku master\" \"git push origin master\"",
"app": "concurrently \"cd client && npm start\" \"node app.js\""
},
package.json scripts (app-root/client) package.json脚本(应用程序根/客户端)
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
I suggest you use a bundler like webpack or browserify so that everything in react gets bundled into one js file. 我建议你使用一个捆绑喜欢的WebPack或browserify让一切都在反应被捆绑成一个js文件。 This js file will be served by node and you can access it in the html for when you deploy to heroku.
该js文件将由node提供,当您部署到heroku时,您可以在html中访问它。 I've used them for the same purpose and it works.
我已经将它们用于相同的目的,并且有效。
If you use webpack, you have to add this to your scripts
in package.json: 如果使用webpack,则必须将其添加到package.json中的
scripts
中:
"postinstall": "webpack -p",
Also, I seem to have a different configuration for the procfile
, which you can also try: 另外,我似乎对
procfile
有不同的配置,您也可以尝试:
web: node app.js
So to answer my own question - it seems like the main issue was with messy configuration and bad routing. 因此,回答我自己的问题-似乎主要问题在于配置混乱和路由错误。 I followed this simple tutorial on deploying a create-react-app with Express backend to heroku and imported my own code, and the app started running fine in a few minutes- https://dev.to/nburgess/creating-a-react-app-with-react-router-and-an-express-backend-33l3
我遵循了这个简单的教程,将带有Express后端的create-react-app部署到heroku并导入了我自己的代码,该应用在几分钟内开始正常运行-https : //dev.to/nburgess/creating-a-react -app与-反应路由器和-一个快车-后端-33l3
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.