使用快速后端创建反应应用程序在本地工作,但在部署到 heroku 时路由不正确



我关注了这篇文章,介绍如何使用具有快速后端和spotify API的create-react-app创建应用程序,并且从那时起一直尝试将其部署到heroku: https://medium.com/@jonnykalambay/now-playing-using-spotifys-awesome-api-with-react-7db8173a7b13

一切似乎在本地运行良好,授权流程良好,一旦完成,express 就会提供一个静态索引.html其中包含客户端具有"根"ID 的div,索引.js在其中呈现反应"App"组件。

但是,当应用程序部署到 heroku 时,服务器端进程仍然有效,授权和重定向成功,但索引.html Express 服务是上层服务器端服务(在项目的根"public"目录中(,其中没有运行反应脚本。 即使试图通过向该索引添加 react、babel、index.js脚本和"root"div 来"强制"脚本运行.html,似乎也没有任何效果。

可以找到 Github 存储库 @ https://github.com/Johnnybar/spot.stats 和应用程序可以找到 @ https://spot-stats.herokuapp.com/

感觉我在这里缺少一些简单的东西,但是在尝试破解部署方式一段时间后,我真的很感激任何提示,谢谢!

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,在成功身份验证后显示在 Heroku 上 - 这不是所需的索引.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,在身份验证后在本地工作,并在app/client/src中呈现App组件。这是所需的索引.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(

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/(

"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(

"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},

我建议你使用像 webpack 或 browserify 这样的捆绑器,这样 react 中的所有内容都被捆绑到一个 js 文件中。这个js文件将由node提供,你可以在html中访问它,当你部署到heroku时。我已经将它们用于相同的目的并且有效。

如果你使用 webpack,你必须把它添加到 package.json 中的scripts

"postinstall": "webpack -p",

另外,我似乎对procfile有不同的配置,您也可以尝试:

web: node app.js

所以回答我自己的问题 - 似乎主要问题是混乱的配置和糟糕的路由。 我按照这个简单的教程将带有 Express 后端的 create-react-app 部署到 heroku 并导入了我自己的代码,该应用程序在几分钟内开始运行良好- https://dev.to/nburgess/creating-a-react-app-with-react-router-and-an-express-backend-33l3

最新更新