12

I have gone through lot of similar questions in stack overflow like this one. Each one have different perspective of deploying the react app.

But this question is for those who use redux, react-router and react-router-redux. It took me lot of time to figure out the proper ways to host Single page application. I am aggregating all the steps here.

I have written the below answer that contains the steps to deploy react-redux app to S3.

Community
  • 1
  • 1
Lakshman Diwaakar
  • 6,002
  • 4
  • 40
  • 73

3 Answers3

25

There is a video tutorial for deploying react app to s3. This will be easy for deploying the app as such on s3 but we need to do some changes in our react app. Since it is difficult to follow, I am summarising as steps. Here it goes:

  1. Amazon AWS -> s3 -> create bucket.
  2. Add bucket policy, so that everyone can access the react app. click created bucket -> properties -> permissions. In that click Edit bucket policy. Here is an example for the bucket policy.

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "Allow Public Access to All Objects",
                "Effect": "Allow",
                "Principal": "*",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::<YOUR_BUCKET_NAME>/*"
            }
        ]
    }
    
  3. If you want logs, create another bucket and set the bucket name under logs section in properties.

  4. You need to have an index.html(which is the starting point of your app) and error.html and all the public assets(browserified reactJS app and other CSS, JS, img files) in a folder.

  5. Make sure you have relative reference to all these public files in index.html.

  6. Now, navigate to properties -> static website hosting. Enable the website hosting option. Add the index.html and error.html to the fields respectively. On saving, you will receive the Endpoint.

  7. Finally, your react-redux app is deployed. All your react routes will work properly. But when you reload the S3 will redirect to that specific route. As no such routes are already defined, it renders error.html

  8. We need to add some redirection rules under static web hosting. So, when 404 occurs, S3 needs to redirect to index.html, but this can be done only by adding some prefix like #!. Now, when you reload the page with any react route, instead of going to error.html, the same url will be loaded but with a prefix #!. Click Edit redirection rules and add the following code:

    <RoutingRules>
        <RoutingRule>
            <Condition>
                <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
            </Condition>
            <Redirect>
                <HostName> [YOUR_ENDPOINT] </HostName>      
                <ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
            </Redirect>
        </RoutingRule>
    </RoutingRules>
    
  9. In react, we need to remove that prefix and push the route to original route. Here is the change you need to do in react app. I have my main.js file, which is the starting point of react app. Add a listener to browserHistory like this:

    browserHistory.listen(location => {
      const path = (/#!(\/.*)$/.exec(location.hash) || [])[1];
      if (path) {
          history.replace(path);
       }
     });
    
  10. The above code will remove the prefix and push the history to the correct route (HTML 5 browser history). For eg: something like this http://s3.hosting/#!/event will change to http://s3.hosting/event. Doing that makes react-router to understand and change the route accordingly. Here is my main.js file for better understanding:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import {createStore, compose, applyMiddleware} from 'redux';
    import {Provider} from 'react-redux'
    import {Router, Route, IndexRoute, browserHistory, useRouterHistory} from 'react-router';
    import {syncHistoryWithStore} from 'react-router-redux';
    import createLogger from 'redux-logger';
    import thunk from 'redux-thunk';
    
    
    const logger = createLogger();
    const createStoreWithMiddleware = applyMiddleware(thunk, logger)(createStore);
    const store = createStoreWithMiddleware(reducers);
    const history = syncHistoryWithStore(browserHistory, store);
    
    
    browserHistory.listen(location => {
      const path = (/#!(\/.*)$/.exec(location.hash) || [])[1];
      if (path) {
          history.replace(path);
       }
     });
    

So uploading the browserified or webpacked react app(app.js file) will do the required need. Since I found difficult to gather all the stuff, I have aggregated all these as steps.

Lakshman Diwaakar
  • 6,002
  • 4
  • 40
  • 73
  • 1
    Could you pease help with browserHistory.listen part for React 4 / Router 4.1 version of libraries? – syscreat Jun 02 '17 at 08:57
  • Hi, I just followed these steps and got everything else working, except that now when I reload the page, the #! prefix is not replaced with /, and the page starts to do infine history.replace() calls. Are you able to provide any insight why this would happen? I am using createBrowserHistory function from the history library since recat-router 4 doesn't have browserHistory function anymore. – Mirko Flyktman Sep 03 '19 at 05:30
  • Actually got this working by removing the / part after #! from the ReplaceKeyPrefixWith tag in the redirection rules. Will upvote the answer though since no other SO aswer was this good to pointing out the issue. – Mirko Flyktman Sep 03 '19 at 05:52
  • @lakshmandiwaakar Actually struggling with this again, the hashed routes don't quite work. Would you be able to help me with this issue? The history.replace function gets called infinitely if I follow your instructions, and removing the / from #!/ brakes the thing. – Mirko Flyktman Sep 30 '19 at 11:45
  • 2
    I just went through some thrashing on this. It's 2020, and S3 allows custom index & error to resolve to index.html without RoutingRules (step 8) and React 16.12+ and React-Router 5.x. The one remaining issue is that the requested doc receives a 404 or other 4xx, even though the app works properly otherwise. If you have CloudFront set up, AND you ONLY have your React SPA on your CloudFront domain, you can set up error pages to respond with a 200 HTTP response code. – ryanm Apr 20 '20 at 17:48
0

If there is anyone who stumbles into this post, and the solution doesn't work because of a infinite loop in the history.listen function; For me the solution was to add a small timeout for the history.replace(path) call. So it looks like this:

    history.listen(location => {
      const path = (/#!(\/.*)$/.exec(location.hash) || [])[1];
        if (path) {
          setTimeout(() => {
            history.replace(path);
          }, 100);
        }
      });
Mirko Flyktman
  • 248
  • 2
  • 13
-2

You can ignore 9-th step by editing 8-th configuration as

 <ReplaceKeyPrefixWith>/</ReplaceKeyPrefixWith>

and use browserHistory in react-router