timarney / react-app-rewired

Override create-react-app webpack configs without ejecting
MIT License
9.79k stars 425 forks source link

Override the publicPath e historyApiFallback #535

Closed mutheusalmeida closed 3 years ago

mutheusalmeida commented 3 years ago

How could I override the publicPath e historyApiFallback to redirect all server requests to /index.html on development? I'm 1 day straight struggling with this. Could you help?

dawnmist commented 3 years ago

Can I ask what it is that you are finding is not working and that you are trying to solve?

Requests for a page that doesn't exist already fall back to showing the index.html page in the development server, so I am not sure from your description what the actual problem you are trying to solve is. The historyApiFallback value is set by react-scripts at: https://github.com/facebook/create-react-app/blob/fddce8a9e21bf68f37054586deb0c8636a45f50b/packages/react-scripts/config/webpackDevServer.config.js#L104-L109 with the value for the index set in paths at: https://github.com/facebook/create-react-app/blob/fddce8a9e21bf68f37054586deb0c8636a45f50b/packages/react-scripts/config/paths.js#L20-L30 and the getPublicUrlOrPath function defined at: https://github.com/facebook/create-react-app/blob/fddce8a9e21bf68f37054586deb0c8636a45f50b/packages/react-dev-utils/getPublicUrlOrPath.js#L25

From what I can see, it sets the historyApiFallback path from the environment variable PUBLIC_URL if present, else from your package.json file's homepage value, else it is set to the root directory of the site /. The server then interprets requests for a directory to return the index.html file in that directory, hence the fallback is actually being set to return the index.html file from the root directory of the site by default.

I don't think you actually need react-app-rewired if what you are trying to do is change the historyApiFallback index path - CRA provides the way to do that through the PUBLIC_URL environment variable (which you can put into the .env file) and/or through the homepage value in your package.json file.

If I have misunderstood what you are trying to do (which I think I may have misunderstood, since what you seem to be asking to do is what CRA does by default), Create React App uses a separate configuration for the dev server than the one used to compile the website itself. In order to modify the configuration of the Dev Server, you need to use the alternative object format for react-app-rewired's config-overrides.js file so that you can provide the separate function required to modify the dev server config. i.e.

module.exports = {
  // The Webpack config to use when compiling your react app for development or production.
  // This is the function that is typically returned if you were previously only using the function
  // return from config-overrides.js instead of the object format.
  webpack: (config, env) => {
    // ...add your webpack config
    return config;
  },
  // The function to use to create a webpack dev server configuration when running the development
  // server with 'npm run start' or 'yarn start'.
  devServer: (configFunction) =>
    (proxy, allowedHost) => {
      // Create the default webpack dev server config by calling configFunction with the provided
      // proxy/allowedHost parameters
      const config = configFunction(proxy, allowedHost);

      // Set historyApiFallback to something...
      config.historyApiFallback = {
        disableDotRule: true,
        index: '/',
      };

      // Return your customised Webpack Development Server config.
      return config;
    }
};
mutheusalmeida commented 3 years ago

Thanks for the reply!

I'm trying to solve the error when I refresh the page or type the url manually with React Router: https://ui.dev/react-router-cannot-get-url-refresh/.

The issue:

React Router can only load after the first successful GET request to your server (or /). The reason for the dreaded Cannot GET /* error is because, if you’re at /dashboard and then hit refresh, the browser will make a GET request to /dashboard which will fail since you have no logic on your server for handling that request (since React Router is supposed to do it).

The solution: Catch-all

If you already have a server you’re using, this is probably your best bet. The main idea here is that you redirect all of your server requests to /index.html. The outcome is similar to Hash History. Any request that is made to your server will respond with the index page (and then fetch any JS resources you need), React Router will then take over and load the appropriate view. The actual code for this varies on which type of server you have.

How to solve:

Just as above, what we need to do it tell Webpack Dev Server to redirect all server requests to /index.html. There are just two properties in your webpack config you need to set to do this, publicPath and historyApiFallback.

publicPath: '/',
historyApiFallback: true

publicPath allows you to specify the base path for all the assets within your application. historyAPIFallback will redirect 404s to /index.html.

As I am using CRA, I can only use eject or override the webpack-config.js file.

dawnmist commented 3 years ago

That's why I was confused. CRA already does this for the development server. The catch-all is already set.

What exactly happens when you refresh the page? Do you get a 404 Not Found error, does it redirect to the start page of the site, what actually happens?

For the development server, it is the webpackDevServer.config.js file that you would need to alter. But the alteration you want to make is already present in the current webpackDevServer.config.js file.

Please try creating a file named .env at the top level of your project (if you don't already have one), and try setting:

PUBLIC_URL='/'

in that .env file. Then restart the dev server.

mutheusalmeida commented 3 years ago

When I refresh the page /something my state is empty because the data was fetched and passed to state at /. Just as explained way better than me in the article above.

And the .env thing doesn't work here.

Thanks in advance.

dawnmist commented 3 years ago

Ok, that's starting to make more sense now as to why what you have been trying to do isn't fixing the issue you're seeing.

The state data being empty is not actually caused by the lack of a fallback url. The issue link you've provided is an error where the entire page is unable to be accessed. i.e. the web browser makes a request to the server, and gets a "404 page not found" error back from the server. What you've just described is that the page is downloaded, but is missing the additional data that is supposed to be fetched on first load - which is not the same thing as being unable to download the page itself at all.

If you're simply missing the data that would be fetched if you were on the index/root page of the site, you need to move the fetching of that data to a parent component's componentDidMount function that is a parent of all the page components of your site rather than being solely on the original index page. That data setup part has to be run on whatever url is visited first, rather than on any specific page. A typical component for react-router that might contain that kind of universal fetch of initial data would be the component that defines the routing of page urls to page components.

mutheusalmeida commented 3 years ago

A typical component for react-router that might contain that kind of universal fetch of initial data would be the component that defines the routing of page urls to page components.

I think I already have that.

If you're simply missing the data that would be fetched if you were on the index/root page of the site, you need to move the fetching of that data to a parent component's componentDidMount function that is a parent of all the page components of your site rather than being solely on the original index page.

Anyway, I tried to fetch the data on a parent component, aka, App.js, and it doesn't work either. It's the first time I heard that solution. Searching on the web, I only found the "catch all" solution. It seems like a common problem and an annoying one.

I just wanted to fix that in development to see how it works, but it has been a hard task. In production it seems a easy one.

I think that creating a React app from scratch and just changing webpack.config.js file would be much easier, but I'm not going for that.

dawnmist commented 3 years ago

Searching on the web, I only found the "catch all" solution.

The catch-all solution isn't the correct solution for your problem.

The only reason it is being "hard to fix in development" is because it is the wrong solution for your problem, and it is one that is already present in your environment.

Not doing the data fetch on initial page load is a code issue, not a build tools issue.

If you have an example repository, I can take a look at it and give you some feedback on the code side.

mutheusalmeida commented 3 years ago

Oh, thanks! I appreciate your time. It was a code issue indeed. I was passing the data fetched as props and then, when there was a page refresh, the props were empty and the data was only there when the page was fully loaded. So I just checked if the data was there with { data && <Component /> }.