1

AWS S3 and React Router don't play well together. There is a good solution that works with older versions of React Router as found in https://stackoverflow.com/a/61375930/15019760

Right now, my AWS Redirect config is:

[
    {
        "Condition": {
            "HttpErrorCodeReturnedEquals": "404"
        },
        "Redirect": {
            "HostName": "myhostname.com",
            "ReplaceKeyPrefixWith": "#!/"
        }
    },
    {
        "Condition": {
            "HttpErrorCodeReturnedEquals": "403"
        },
        "Redirect": {
            "HostName": "myhostname.com",
            "ReplaceKeyPrefixWith": "#!/"
        }
    }
]

This adds "#!" to the URL, so we need the router to get rid of that.

In the old React Router, we could do this by

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import { render } from 'react-dom';
import { createBrowserHistory } from 'history';

import App from './App';

const history = createBrowserHistory();

let app = document.getElementById('app');
if (app) {
    // 1. Set up the browser history with the updated location
    // (minus the # sign)
    const path = (/#!(\/.*)$/.exec(location.hash) || [])[1];
    if (path) {
        history.replace(path);
    }

    // 2. Render our app
    render(<App />, app);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App/>
    </BrowserRouter>
  </React.StrictMode>
);

However, "history" has now been replaced with useNavigate(), as far as I'm aware.

So then, I tried something like this:

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, useNavigate } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';


const Index = () => {
  const navigate = useNavigate();

  React.useEffect(() => {
    // 1. Set up the browser history with the updated location
    // (minus the # sign)
    const locationHash = window.location.hash;
    const path = (/#!(\/.*)$/.exec(locationHash) || [])[1];
    if (path) {
      navigate(path, { replace: true });
    }
  }, [navigate]);

  return (
    <React.StrictMode>
      <BrowserRouter>
        <App/>
      </BrowserRouter>
    </React.StrictMode>
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(<Index />);

However, this doesn't work because "useNavigate() may be used only in the context of a component."

How should I proceed? Is there some other solution I should try?

(Note, I want to avoid the "hacky" solution of setting the Error page to index.html, given the concerns with SEO mentioned by others.

DDD
  • 31
  • 1
  • Is there any reason you couldn't import `createBrowserHistory` from `history` and issue the `history.replace? – Drew Reese Mar 02 '23 at 05:05
  • While it's true that it could be done this way (npm install history, with an import) I think there is some reason that for React Router it's recommended to use the useNavigate() hook instead. Might have to do with re-rendering components as opposed to a full page reload, though I don't know enough about the topic to know for sure. – DDD Mar 02 '23 at 17:33
  • The `useNavigate` hook would be used *inside* a React component rendered within the routing context provided by the `BrowserRouter`. *Sometimes* though you need to issue imperative navigation actions from *outside* a React component though, like in service modules and your case. – Drew Reese Mar 03 '23 at 00:18
  • Ok, thanks for confirming this. So is it fair to say that it's just not possible to do it with useNavigate? Or would it be possible through the use of some kind of wrapper component, with that being very impractical ? – DDD Mar 04 '23 at 06:01
  • 1
    Yes, if you hoisted the router component/context higher in the ReactTree than the component using `useNavigate` it would be accessible. Or you could just move that `useNavigate` and `useEffect` logic down into the `App` component. – Drew Reese Mar 04 '23 at 06:09

1 Answers1

1

The solution is to go to your Cloudfront distribution, in the section: "Error pages" and create an Error Page with these settings:

AWS Error Page Configuration