简体   繁体   English

带有Express后端的Create-react-app在本地工作,但是在部署到heroku时路由不正确

[英]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. 我建议你使用一个捆绑喜欢的WebPackbrowserify让一切都在反应被捆绑成一个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.

相关问题 使用express后端将create-react-app部署到heroku会在浏览器中返回无效的主机头 - deploying create-react-app to heroku with express backend returns invalid host header in browser 带有 Node-SASS 和 CSS 模块的 Create-React-App v2 在本地工作但在 Heroku 中崩溃 - Create-React-App v2 with Node-SASS and CSS Modules works locally but crashes in Heroku create-react-app 部署在 Heroku 但页面为空白 - create-react-app is deployed on Heroku but page is blank Express React应用程序可在本地运行,但在部署Heroku时出错 - Express React app works locally but gives error on heroku deploy React 应用程序在本地工作,但部署到 heroku 时出现应用程序错误 - React app working locally but application error when deployed to heroku 在单个 Azure 应用程序中使用 Express 后端反应应用程序(通过 create-react-app)? - React app (via create-react-app) with Express backend in single Azure app? Heroku-Express和React应用程序在部署时显示空白屏幕 - Heroku - Express and React app showing blank screen when deployed 在 Heroku 上部署 create-react-app 时出现无效主机错误 - Invalid Host error when deploying a create-react-app on Heroku Netlify/React 前端未连接到 Node.js/Express/MongoDB Atlas/Heroku 后端,但在开发/本地工作 - Netlify / React front end not connecting to Node.js / Express / MongoDB Atlas / Heroku backend but works in development/locally 构建后无法将照片从 React 前端发送到表达后端(create-react-app) - Cant send photo from React frontend to express backend after build (create-react-app)
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM