1227

I'm using React-router and it works fine while I'm clicking on link buttons, but when I refresh my webpage it does not load what I want.

For instance, I am in localhost/joblist and everything is fine because I arrived here pressing a link. But if I refresh the webpage I get:

Cannot GET /joblist

By default, it didn't work like this. Initially I had my URL as localhost/#/ and localhost/#/joblist and they worked perfectly fine. But I don't like this kind of URL, so trying to erase that #, I wrote:

Router.run(routes, Router.HistoryLocation, function (Handler) {
 React.render(<Handler/>, document.body);
});

This problem does not happen with localhost/, this one always returns what I want.

This app is single-page, so /joblist doesn't need to ask anything to any server.

My entire router.

var routes = (
    <Route name="app" path="/" handler={App}>
        <Route name="joblist" path="/joblist" handler={JobList}/>
        <DefaultRoute handler={Dashboard}/>
        <NotFoundRoute handler={NotFound}/>
    </Route>
);

Router.run(routes, Router.HistoryLocation, function (Handler) {
  React.render(<Handler/>, document.body);
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DavidDev
  • 12,605
  • 4
  • 14
  • 15
  • unless you use htaccess to load your main touring page and tell your router to use location.pathname it won't work.. – CJT3 Jan 20 '15 at 16:44
  • How did you erase that `#` symbol? Thank you! – SudoPlz Jun 06 '15 at 11:57
  • 13
    If you are hosting your react app in an S3 bucket, you can simply set the error document to `index.html`. This will make sure `index.html` is hit no matter what. – Trevor Hutto Jul 30 '16 at 18:49
  • In my case, it works fine in windows but not in linux – Ejaz Karim Dec 04 '17 at 19:05
  • This is the reference that helped solve my problem: https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#serving-apps-with-client-side-routing – jimbotron Jul 09 '18 at 22:56

59 Answers59

1798

Server-side vs Client-side

The first big thing to understand about this is that there are now 2 places where the URL is interpreted, whereas there used to be only 1 in 'the old days'. In the past, when life was simple, some user sent a request for http://example.com/about to the server, which inspected the path part of the URL, determined the user was requesting the about page, and then sent back that page.

With client-side routing, which is what React Router provides, things are less simple. At first, the client does not have any JavaScript code loaded yet. So the very first request will always be to the server. That will then return a page that contains the needed script tags to load React and React Router, etc. Only when those scripts have loaded does phase 2 start. In phase 2, when the user clicks on the 'About us' navigation link, for example, the URL is changed locally only to http://example.com/about (made possible by the History API), but no request to the server is made. Instead, React Router does its thing on the client-side, determines which React view to render, and renders it. Assuming your about page does not need to make any REST calls, it's done already. You have transitioned from Home to About Us without any server request having fired.

So basically when you click a link, some JavaScript runs that manipulates the URL in the address bar, without causing a page refresh, which in turn causes React Router to perform a page transition on the client-side.

But now consider what happens if you copy-paste the URL in the address bar and e-mail it to a friend. Your friend has not loaded your website yet. In other words, she is still in phase 1. No React Router is running on her machine yet. So her browser will make a server request to http://example.com/about.

And this is where your trouble starts. Until now, you could get away with just placing a static HTML at the webroot of your server. But that would give 404 errors for all other URLs when requested from the server. Those same URLs work fine on the client-side, because there React Router is doing the routing for you, but they fail on the server-side unless you make your server understand them.

Combining server- and client-side routing

If you want the http://example.com/about URL to work on both the server- and the client-side, you need to set up routes for it on both the server- and the client-side. It makes sense, right?

And this is where your choices begin. Solutions range from bypassing the problem altogether, via a catch-all route that returns the bootstrap HTML, to the full-on isomorphic approach where both the server and the client run the same JavaScript code.

Bypassing the problem altogether: Hash History

With Hash History, instead of Browser History, your URL for the about page would look something like this: http://example.com/#/about

The part after the hash (#) symbol is not sent to the server. So the server only sees http://example.com/ and sends the index page as expected. React Router will pick up the #/about part and show the correct page.

Downsides:

  • 'ugly' URLs
  • Server-side rendering is not possible with this approach. As far as search engine optimization (SEO) is concerned, your website consists of a single page with hardly any content on it.

Catch-all

With this approach, you do use the Browser History, but just set up a catch-all on the server that sends /* to index.html, effectively giving you much the same situation as with Hash History. You do have clean URLs however and you could improve upon this scheme later without having to invalidate all your user's favorites.

Downsides:

  • More complex to set up
  • Still no good SEO

Hybrid

In the hybrid approach, you expand upon the catch-all scenario by adding specific scripts for specific routes. You could make some simple PHP scripts to return the most important pages of your site with content included, so Googlebot can at least see what's on your page.

Downsides:

  • Even more complex to set up
  • Only good SEO for those routes you give the special treatment
  • Duplicating code for rendering content on server and client

Isomorphic

What if we use Node.js as our server so we can run the same JavaScript code on both ends? Now, we have all our routes defined in a single react-router configuration and we don't need to duplicate our rendering code. This is 'the holy grail' so to speak. The server sends the exact same markup as we would end up with if the page transition had happened on the client. This solution is optimal in terms of SEO.

Downsides:

  • Server must (be able to) run JavaScript. I've experimented with Java in conjunction with Nashorn, but it's not working for me. In practice, it mostly means you must use a Node.js based server.
  • Many tricky environmental issues (using window on server-side, etc.)
  • Steep learning curve

Which should I use?

Choose the one that you can get away with. Personally, I think the catch-all is simple enough to set up, so that would be my minimum. This setup allows you to improve on things over time. If you are already using Node.js as your server platform, I'd definitely investigate doing an isomorphic app. Yes, it's tough at first, but once you get the hang of it it's actually a very elegant solution to the problem.

So basically, for me, that would be the deciding factor. If my server runs on Node.js, I'd go isomorphic; otherwise, I would go for the Catch-all solution and just expand on it (Hybrid solution) as time progresses and SEO requirements demand it.

If you'd like to learn more about isomorphic (also called 'universal') rendering with React, there are some good tutorials on the subject:

Also, to get you started, I recommend looking at some starter kits. Pick one that matches your choices for the technology stack (remember, React is just the V in MVC, you need more stuff to build a full app). Start with looking at the one published by Facebook itself:

Or pick one of the many by the community. There is a nice site now that tries to index all of them:

I started with these:

Currently, I am using a homebrewed version of universal rendering that was inspired by the two starter kits above, but they are out of date now.

Good luck with your quest!

Stijn de Witt
  • 40,192
  • 13
  • 79
  • 80
  • 4
    Great post Stijn! Would you recommend going with a starter kit for an Isomorphic react app? If so, could you give an example of one you would prefer? – Chris Apr 29 '16 at 09:40
  • @Chris Sorry for the delayed response, I was on a trip. I tried out a couple of isomorphic starter kits actually. I will add some info on it to the post. – Stijn de Witt May 06 '16 at 00:08
  • @Stijn - That really cleared a few things up for me, thanks. Can you clarify the 'catch-all' technique? This seems like the logical step for me at the moment, but I'm not sure where to find more detail on how to accomplish this... – Paulos3000 Oct 30 '16 at 18:51
  • 2
    @Paulos3000 It depends on what server you are using. Basically you define a route for `/*` and make it respond with your HTML page. The tricky thing here is to make sure that you don't intercept requests for the .js and .css files with this route. – Stijn de Witt Oct 30 '16 at 19:27
  • 2
    @Paulos3000 See here for some related questions: [for Apache/php](http://stackoverflow.com/a/5218298/286685), [for Express/js](http://stackoverflow.com/a/19313369/286685), [for J2E/Java](http://stackoverflow.com/a/9428075/286685). – Stijn de Witt Oct 30 '16 at 19:35
  • I'd be using an Express server. I've seen the code for it but I'm not entirely sure how to integrate it... How would I do it with webpack, for example? – Paulos3000 Oct 30 '16 at 20:50
  • @Paulos3000 Express actually makes this very simple. Just make sure you add something to this effect as the last handler: `app.use('*', function(req, res, next) {res.send(' – Stijn de Witt Oct 31 '16 at 21:28
  • ...but of course, as you are using express, you really should investigate the isomorphic/universal solution ;) – Stijn de Witt Oct 31 '16 at 21:29
  • Interestingly, since I first created this answer, Facebook itself introduced a starter kit. Maybe try this one out? https://github.com/facebookincubator/create-react-app – Stijn de Witt Oct 31 '16 at 21:35
  • Thank you so much--this clarified aspects of routing to me that much Googling and reading of official React docs hadn't. May I suggest you submit it to the React folks to include in their docs? – John McGrath Dec 16 '16 at 21:51
  • @JohnMcGrath Thanks and glad it helped you. The React Router docs would probably be more appropriate. They are open source so you could offer it up as a contribution... Maybe point them to this post first and ask them if they are interested? – Stijn de Witt Dec 20 '16 at 18:30
  • 2
    @Stijn de Witt This is a great clarification and well answered on this topic. However, what are the options when your app could be served within a sub directory (by iis for example). Ie: http://domain/appName/routeName or http://domain/appName/subfolder/routeName – Sagiv b.g Mar 19 '17 at 12:51
  • 1
    Hi, I'm trying the hash solution, however no luck. Getting `createMemoryHistory is not a function` error. Mind a look? https://stackoverflow.com/questions/45002573/how-to-have-urls-to-save-component-app-state-in-react – Leon Gaban Jul 10 '17 at 01:34
  • 6
    @LeonGaban It seems since this answer was written, React Router changed their implementation. They now have different Router instances for the different histories and they do the configuring of the history in the background. The basic principles are the same still. – Stijn de Witt Jul 17 '17 at 13:03
  • @YarinDekel Thanks! And yeah maybe you are right about create-react-app as CRA does not support SSR out of the box :( – Stijn de Witt Jan 08 '19 at 12:24
  • @Stijndewitt I stumbled [across a solution](https://stackoverflow.com/a/53550190) like the catch-all you mentioned that might eliminate the SEO downside. However it only involves tomcat. Might be worth updating your answer to include it? – ns533 Jul 07 '19 at 07:49
  • @ns533 Can you explain how it would eliminate the SEO downside? AFAICT, this is like rewrite rules in Apache... Meaning it still only ends up sending a nearly blank page that crawlers will not be able to find any content in. – Stijn de Witt Jul 08 '19 at 08:36
  • @StijndeWitt If the web crawler doesn't render the page with JavaScript enabled then I guess it wouldn't. I've [read](https://www.javascriptstuff.com/react-seo/) that google's web crawlers do see the whole page though. Combining that info with if a rule that redirects www.mysite.com/blogpost/* to my index.html (google none the wiser) then shouldn't google's web crawler see each blogpost as a unique page? – ns533 Jul 21 '19 at 03:15
  • @ns533 Yeah I think Google will be able to index it as it seems they indeed run javascript. However, I am not sure what e.g. Bing and Yahoo do... – Stijn de Witt Jul 31 '19 at 17:00
  • @StijndeWitt Very helpful post, is there any resource explaining this Catch All approach implemented in .net? I just figured that .Net Core React Web App template created by Visual Studio has this exact issue. Trying to hit the backend with the URL works for the first time. Second time it never goes to the backend again. Can't find a way to solve it. – Arturio Nov 08 '19 at 00:22
  • @StijndeWitt A really good comprehensive explanation. I think the catch-all solution is not a big deal and easy to setup for back-ends like Django – Mohammed Shareef C Dec 14 '19 at 13:56
  • @ns533 I don't think that solution is limited to Tomcat. `Nginx` can easily handle this. Anyway it is the same catch-all solution but handled by the web server (or reverse proxy server) itself – Mohammed Shareef C Dec 14 '19 at 14:00
  • Great answer! I've recently posted an article, which has some visualizations of what is happening during routing. Hope it will help https://valerii-udodov.com/2020/01/24/single-page-application-react-example/ Cheers – Val Jan 28 '20 at 23:01
  • One thing that was failed to be pointed out was that if you load the app and then manually change the url in the browser to a url that the router does support, the entire app will be refreshed. I was under the impression that the browser would route this into the app but this is not the case. The browser knows nothing of React and just does what it always does, which is to load the content for the url from the backend. – Johann Apr 10 '20 at 17:15
  • Yeah, the way to manipulate the URL without causing it to fetch a new page from the server is to use the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API). You can open a dev console and run a line of JS there if you want to test client-side navigation. – Stijn de Witt Apr 12 '20 at 12:29
  • @StijndeWitt Just voted up this well-written answer. :) Just one thing though: in the **Catch-all** approach, what does it mean by "without having to invalidate all your user's favorites."? – Glenn Mohammad Apr 12 '20 at 13:40
  • 1
    @GlennMohammad Imagine you start with the Hash History approach and users make a favorite (bookmark) to `https://example.com/#/about`, if you then later change to server side rendering their bookmarks will no longer work. If you start with Catch All and make `https://example.com/about` return index.html, you can later implement server side rendering and the user's bookmarks will still work. – Stijn de Witt Apr 13 '20 at 20:32
  • I found a funny issue with HashRouter: The first time I type in the url it works ok, but if I type the same thing again it does a whole reload of the app. For example being in `locahost:3000/` I type `locahost:3000/test` which does not exist, then HashRouter does not reload the page. But if try to send the same request again `locahost:3000/test` the app reloads... I wonder why... – Watchmaker Apr 20 '20 at 17:37
  • the answer to this question is very well explained but my solution was add import * as serviceWorker from "./serviceWorker"; and serviceWorker.register() in App.js and I can refresh the pages without ugly urls references: https://medium.com/@krishnarai1985/static-deployment-of-react-redux-router-based-web-app-to-ms-azure-f2aa3fd2b9aa – EduardoUstarez Sep 16 '20 at 23:13
  • @EduardoUstarez Your solution doesn't work in the general case. Just e-mail a link to a friend to test it. Service workers are a client-side thing. They only work on clients that have already loaded the page in the past and have the service worker installed. You do actually need some server-side solution if you want your urls's to work in all cases. – Stijn de Witt Dec 28 '20 at 16:14
  • wouldn't just simply redirecting 404 and 403 responses to /index.html resolve the issue? – tymik Dec 30 '20 at 12:41
  • @tymik I would consider that a dirty hack. I *want* my website to return 404 for URLs that don't exist. With such a solution *all* URLs will redirect to index.html but that would not be what I want my website to do. It may however be acceptable for you. – Stijn de Witt Jan 03 '21 at 15:19
  • How do you begin with Catch-all approach? – Code Ninja Jan 04 '21 at 10:14
  • @StijndeWitt but you would handle then 404 in your router and return whatever you wish inside your application - this seems to be pretty good solution for single page applications. Works pretty well with CloudFront that way. – tymik Jan 04 '21 at 14:24
  • @YashKaranke What is your server? You have to configure it to return index.html for all URLs your app will be using. But make sure it only does this for HTML, not for images, scripts etc. – Stijn de Witt Jan 05 '21 at 21:42
  • @StijndeWitt Apache, I added a `.htaccess` file in the public folder and it works perfectly now – Code Ninja Jan 06 '21 at 05:46
  • I have the solution for that, recently faced this problem in a project, will probably write an answer later when I have time – Inzamam Malik Jul 09 '21 at 22:17
  • 1
    It should be noted that at least Google can now parse single page applications quite well, and so the SEO concerns are to some extent outdated. This holds as long as the given page has a different URL and loads the content fast enough (a safe number that seems to be mentioned often is 200ms). – theberzi Sep 09 '21 at 11:51
  • Not sure if I agree. Google Search is just one of the search engines and there are a myriad of other scenario's where it can help to have the content server-side generated. For example, many websites will show a preview of a linked page, based on metadata like [Open Graph](https://developers.facebook.com/docs/sharing/webmasters/). IMHO, sending empty pages to the browser is still not good practice in 2021 – Stijn de Witt Sep 10 '21 at 07:52
  • 3
    [@peter-mortensen](https://stackoverflow.com/users/63550/peter-mortensen) Can you please stop editing my answer now? You are only changing style and I don't agree with all your choices. For example, when I link to the article *'The Pain and the Joy of creating isomorphic apps in ReactJS'* I am using that title very intentionally. It is the title the author chose for their work. I feel it is important to respect that. You have edited it to suit your preferred style **twice** now and I have now reverted that edit twice. Can you leave it now? What you are doing amounts to starting an edit-war. – Stijn de Witt May 11 '22 at 08:38
243

If you are using Apache as your web server, you can insert this into your .htaccess file:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-l
  RewriteRule . /index.html [L]
</IfModule>

I am using react: "^16.12.0" and react-router: "^5.1.2" This method is the Catch-all and is probably the easiest way to get you started.

Alan W. Smith
  • 24,647
  • 4
  • 70
  • 96
BrahimS
  • 2,461
  • 1
  • 8
  • 2
  • 9
    Dont forget RewriteEngine On as the first line – Kai Qing Oct 31 '18 at 22:50
  • 41
    Note this answer is referring to config on an Apache server. This is not part of a React app. – user1847 Jul 16 '19 at 22:52
  • I didn't understand the answers above. None of the tutorrials said my links wouldn't work live at all. However this simple htaccess file worked. Thanks – Thomas Williams Mar 26 '20 at 18:33
  • This works for next.js static export too. As the tomcat is not similar to node we need this additional configuration for the next.js static export. – giri-jeedigunta Apr 04 '20 at 02:15
  • 1
    Can anyone help me? Where is this htaccess put? Do you need to npm run build after adding the file? – Muteshi Sep 05 '20 at 16:46
  • I ```npm run build``` and then added this in ```.htacess``` in the build folder and uploaded on the cPanel, still not working – Tayyab Ferozi Dec 09 '20 at 21:50
  • 1
    @TayyabFerozi You have to make sure the path after `RewriteBase` and after the last `RewriteRule` matches the folder that the app lives (if it's a subfolder) – Jason Ellis Dec 22 '20 at 10:39
  • I had this problem on the serwer and also locally. This fixed for the server. devServer config fixed it locally. – Izabela Furdzik Jun 05 '21 at 09:36
  • If this .htaccess doesn't work, make sure to change AllowOverride None to AllowOverride All in httpd.conf – Sabeer Sep 06 '21 at 05:00
  • This works for cPanel also. – Pulsara Sandeepa Sep 21 '21 at 13:44
  • @JasonEllis Thanks for this. I have a react app inside a react app and would not have fixed it without your help. mywebsite.com/apps/my-app is the root of the inner react app. added "/apps/my-app" to the .htaccess lines and it worked immediately. – Sean Mizen Nov 28 '21 at 20:21
  • It worked in root directory of my domain. But when I created a subdomain for a folder named **estate** and uploaded my files in that folder, there it didn't worked. – Riosant Jan 11 '22 at 17:22
  • .htaccess file on what kind of system? [Apache](https://en.wikipedia.org/wiki/Apache_HTTP_Server) on Linux? Or something else? What versions was it tested with? On what kind of system was it tested? Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/40591955/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Apr 24 '22 at 23:52
  • Thx Alan it works for me, this is solution that I need on Apache server ;) – kkatusic Nov 23 '22 at 18:00
  • Works like magic! Using cpanel too – Alexander Feb 02 '23 at 17:52
198

The answers here are all extremely helpful. Configuring my Webpack server to expect the routes worked for me.

devServer: {
   historyApiFallback: true,
   contentBase: './',
   hot: true
},

The historyApiFallback is what fixed this issue for me. Now routing works correctly and I can refresh the page or type in the URL directly. There isn't any need to worry about workarounds on your Node.js server. This answer obviously only works if you're using Webpack.

See my answer to React-router 2.0 browserHistory doesn't work when refreshing for a more detailed reason why this is necessary.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jmancherje
  • 6,439
  • 8
  • 36
  • 56
99

For React Router V4 users:

If you try to solve this problem by the Hash History technique mentioned in other answers, note that

<Router history={hashHistory} >

does not work in V4. Please use HashRouter instead:

import { HashRouter } from 'react-router-dom'

<HashRouter>
  <App/>
</HashRouter>

Reference: HashRouter

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user2875289
  • 2,799
  • 1
  • 22
  • 25
  • Could you give any detail as to why the HashRouter fixes this issue? The link you provided doesn't explain it for me. Also, is there a way to hide the hash in the path? I was using the BrowserRouter but ran into this 404 issue with it.. – Ian Smith Jun 07 '20 at 00:15
  • i was using browser router and it was causing 404 error on refresh, but then i replaced browser router with hash router and not it works fine. Thanks – Nabeel Hussain Shah Jul 31 '20 at 18:29
  • Don't want a hash in my URLs – Philip Rego Jul 22 '22 at 04:21
84

I used Create React App to make a website just now and had the same issue presented here.

I use BrowserRouting from the react-router-dom package. I am running on a Nginx server and adding the following to /etc/nginx/yourconfig.conf solved it for me:

location / {
  if (!-e $request_filename){
    rewrite ^(.*)$ /index.html break;
  }
}

Which corresponds to adding the following to the .htaccess in case you are running Apache:

Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]

This also seems to be the solution suggested by Facebook themselves and can be found here.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Aidin
  • 1,230
  • 2
  • 11
  • 16
54

In your index.html file's head, add the following:

<base href="/">
<!-- This must come before the CSS and JavaScript code -->

Then, when running with the Webpack development server, use this command.

webpack-dev-server --mode development --hot --inline --content-base=dist --history-api-fallback

--history-api-fallback is the important part

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Efe Ariaroo
  • 1,052
  • 10
  • 10
  • It's worth mentioning that if you're using a base href that isn't `/` then `HashRouter` won't work (if you're using hash routing) – scrowler Jan 17 '19 at 16:06
  • The tag did it for me. Thanks for this. Webpack dev server kept trying to grab the bundle from the path/ and was failing. – Malisbad Mar 26 '19 at 07:30
  • I'm using React.js + Webpack mode. I added --history-api-fallback parameter in package.json file. Then page refreshing is working correctly. Each when I change code, web page is refresh automatically. ``` "scripts": { "start": "rimraf build && cross-env NODE_ENV='development' webpack --mode development && cross-env NODE_ENV=development webpack-dev-server --history-api-fallback", ... } ``` – NinjaDev Aug 12 '20 at 21:49
42

If you are using Create React App:

There's a great walkthrough of this issue with solutions for many major hosting platforms that you can find here on the Create React App page. For example, I use React Router v4 and Netlify for my frontend code. All it took was adding one file to my public folder ("_redirects") and one line of code in that file:

/*  /index.html  200

Now my website properly renders paths like mysite.com/pricing when entered into the browser or when someone hits refresh.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user1847
  • 3,571
  • 1
  • 26
  • 35
  • 2
    if you are using [Apache HTTP Server](https://httpd.apache.org/), then .htaccess doesn't work, check the apache version because in [version 2.3.9 and later](https://httpd.apache.org/docs/2.4/mod/core.html#allowoverride) the default **AllowOverride** is **None** try to change **AllowOverride** to **All**. [check this answer](https://stackoverflow.com/a/22526144/11587161) – Kevin Apr 13 '21 at 06:49
  • Same, adding _redirects file worked for me with netlify and react router 6 – Reno Sep 28 '22 at 22:01
  • Thank you, I am also using Netlify, to host my front end code. This worked for me. Just to clear up the confusion I had, you need to create the folder "_redirects" inside your public folder then inside that new file you created add the line of code "/* /index.html 200" . Just to be clear do not include the quotations in the filename or the content that goes in the file. This was a life saver. – Joey Phillips Feb 08 '23 at 23:09
37

The router can be called in two different ways, depending on whether the navigation occurs on the client or on the server. You have it configured for client-side operation. The key parameter is the second one to the run method, the location.

When you use the React Router Link component, it blocks browser navigation and calls transitionTo to do a client-side navigation. You are using HistoryLocation, so it uses the HTML5 history API to complete the illusion of navigation by simulating the new URL in the address bar. If you're using older browsers, this won't work. You would need to use the HashLocation component.

When you hit refresh, you bypass all of the React and React Router code. The server gets the request for /joblist and it must return something. On the server you need to pass the path that was requested to the run method in order for it to render the correct view. You can use the same route map, but you'll probably need a different call to Router.run. As Charles points out, you can use URL rewriting to handle this. Another option is to use a Node.js server to handle all requests and pass the path value as the location argument.

In Express.js, for example, it might look like this:

var app = express();

app.get('*', function (req, res) { // This wildcard method handles all requests

    Router.run(routes, req.path, function (Handler, state) {
        var element = React.createElement(Handler);
        var html = React.renderToString(element);
        res.render('main', { content: html });
    });
});

Note that the request path is being passed to run. To do this, you'll need to have a server-side view engine that you can pass the rendered HTML to. There are a number of other considerations using renderToString and in running React on the server. Once the page is rendered on the server, when your app loads in the client, it will render again, updating the server-side rendered HTML as needed.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Todd
  • 12,995
  • 3
  • 30
  • 25
  • 2
    excuse Excuse me, may you explain pls the next statement "when you hit refresh, you bypass all of the React and React Router code"? Why does it happen? – VB_ Sep 08 '15 at 11:24
  • React router is normally used to handle different 'paths' within the browser only. There are two typical ways do do this: the older style hash path and the newer history API. A browser refresh will make a server request, which will bypass your client-side react router code. You will need to handle the path on the server (but only if you're using the history API). You can use react router for this, but you'll need to do something similar to what I described above. – Todd Sep 09 '15 at 16:26
  • got it. But how to be if the server is on non-JS language (let's say Java)? – VB_ Sep 09 '15 at 18:44
  • Things get more difficult in that case. The promise of isomorphic apps is best realized by using JS on the client and the server. Unless you can run JS within your Java framework, you won't really have isomorphism, nor will you be able to use react router. There are some server-side solutions, like ReactJS.NET which can do this sort of thing. It's easier to use Node.js, though. – Todd Sep 11 '15 at 00:04
  • what is the first parameter routes in this answer? Because when you include something like you get an error of "SyntaxError: Unexpected token <" – Code Uniquely Oct 15 '15 at 09:59
  • 2
    sorry… you mean that you require an express server to render a route that is "non root" ? I was first amazed that the isomorphism's definition was not including the data fetch in its concept (things are desperately complex if you want to render view WITH data serverside, although it would be an obvious SEO need — and isomorphism claims it). Now I'm like "WTF react", seriously… Correct me if I'm wrong, does ember/angular/backbone require a server thing to render a route ? I really don't understand this bloated requirement to use routes – Ben Oct 17 '15 at 08:41
  • @Ben "Correct me if I'm wrong, does ember/angular/backbone require a server thing to render a route ?" Yes, in fact they do when you use history location ('real urls'). All Javascript frameworks do. Remember, when you request a URL, you hit the server first. What is happening is that OP is not routing correctly *at the server side*. He should either just use hash location, or configure the server to always serve HTML at all possible routes. – Stijn de Witt Nov 17 '15 at 01:29
  • 1
    Does it mean that refresh in react-router doesn't work if my react application is not isomorphic? – Matt Dec 07 '15 at 14:50
  • To get refresh to work, you have to call the router from the client code (default React environment) and from the server code (Node.js). If you can do this, then your app is isomorphic. Note that the code in this this answer does not reflect the changes in the new React Router 1.0 version (as of now). – Todd Dec 08 '15 at 16:23
  • @Matt If your react app is not isomorphic (or maybe you aren't even running JS on the server side, but, say, PHP), your best bet is to just have a server-side script that returns the same page for all possible routes. Only some generic bootstrap HTML will be sent to the client. Once on the client, React-Router looks at the URL and stuff will work as expected. But the downside is SEO related. Google will only see that bootstrap HTML and will not index your website. – Stijn de Witt Apr 14 '16 at 11:35
35

If you're hosting a React app via AWS Static S3 Hosting and CloudFront

This problem presented itself by CloudFront responding with a 403 Access Denied message, because it expected /some/other/path to exist in my S3 folder, but that path only exists internally in React's routing with React Router.

The solution was to set up a distribution Error Pages rule. Go to the CloudFront settings and choose your distribution. Next, go to the "Error Pages" tab. Click "Create Custom Error Response" and add an entry for 403 since that's the error status code we get.

Set the Response Page Path to /index.html and the status code to 200.

The end result astonishes me with its simplicity. The index page is served, but the URL is preserved in the browser, so once the React application loads, it detects the URL path and navigates to the desired route.

Error Pages 403 Rule

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
th3morg
  • 4,321
  • 1
  • 32
  • 45
  • So.. all your urls work, but return status code 403? It is not correct. Expected status codes should be in the 2xx range. – Stijn de Witt Oct 21 '19 at 18:35
  • @StijndeWitt I'm not sure you're understanding correctly. CloudFront will handle everything - the client will not see any 403 status codes. The re-routing happens server-side, renders the index page, maintains the url, and provides the correct route as a result. – th3morg Oct 22 '19 at 01:17
  • @th3morg Ah yes I see now. I missed that it also allows you to fill in the response status code as 200. So basically you are mapping status 403 to status 200... Looks fine... except that maybe if you get 403 as a result due to some other reason you won't be able to see it because of this. – Stijn de Witt Oct 23 '19 at 08:09
  • @StijndeWitt It’s specifically a 403 from CloudFront, so if you were say making an API call that resulted in an asynchronous 403 response from your site, that would behave normally. I can’t imagine a scenario where you’d want to expose a 403 behavior from CloudFront itself, though perhaps there is one. This solution though should work for 99+% of use cases. – th3morg Oct 23 '19 at 16:23
  • 2
    thanks for this @th3morg. Does this setup also work for multi-level paths? It works great for me if any path at the root level, like domain/signup, domain/login, domain/, but it doesn't work for multi-level paths, like domain/document/. – Pargles Feb 03 '20 at 13:58
  • It fixed googlebot issue, thanks a lot. I was getting Forbidden Access (403) – Minot Jun 11 '22 at 17:01
  • Works like a charm. Customize error response (Send a custom error response instead of the error received from the origin. ) Select Yes, and provide the above said path and response code. – Nidhin Nov 25 '22 at 18:07
33

If you are hosting your React application on IIS, just add a web.config file containing:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.webServer>
        <httpErrors errorMode="Custom" existingResponse="Replace">
            <remove statusCode="404" subStatusCode="-1" />
            <error statusCode="404" path="/" responseMode="ExecuteURL" />
        </httpErrors>
    </system.webServer>
</configuration>

This will tell the IIS server to return the main page to the client instead of a 404 error and there isn't any need to use the hash history.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Chtioui Malek
  • 11,197
  • 1
  • 72
  • 69
28

This can solve your problem.

I also faced the same problem in the React application in Production mode. Here are the two solutions to the problem.

Solution 1. Change the routing history to "hashHistory" instead of browserHistory in the place of

<Router history={hashHistory} >
   <Route path="/home" component={Home} />
   <Route path="/aboutus" component={AboutUs} />
</Router>

Now build the app using the command

sudo npm run build

Then place the build folder in your var/www/ folder. Now the application is working fine with the addition of # tag in each and every URL. Like

localhost/#/home
localhost/#/aboutus

Solution 2: Without the # tag using browserHistory,

Set your history = {browserHistory} in your router. Now build it using sudo npm run build.

You need to create the "conf" file to solve the 404 not found page. The conf file should be like this.

Open your terminal type the below commands

cd /etc/apache2/sites-available
ls
nano sample.conf

Add the below content in it.

<VirtualHost *:80>
    ServerAdmin admin@0.0.0.0
    ServerName 0.0.0.0
    ServerAlias 0.0.0.0
    DocumentRoot /var/www/html/

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    <Directory "/var/www/html/">
            Options Indexes FollowSymLinks
            AllowOverride all
            Require all granted
    </Directory>
</VirtualHost>

Now you need to enable the sample.conf file by using the following command:

cd /etc/apache2/sites-available
sudo a2ensite sample.conf

Then it will ask you to reload the Apache server, using

sudo service apache2 reload or restart

Then open your localhost/build folder and add the .htaccess file with the content of the below.

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^.*$ / [L,QSA]

Now the app is working normally.

Note: change the 0.0.0.0 IP address to your local IP address.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Venkatesh Somu
  • 4,710
  • 4
  • 23
  • 22
18

The Webpack Dev Server has an option to enable this. Open up package.json and add --history-api-fallback. This solution worked for me.

react-router-tutorial

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
sree
  • 3,113
  • 2
  • 18
  • 13
18

Add this to webpack.config.js:

devServer: {
    historyApiFallback: true
}
Sebastian Kaczmarek
  • 8,120
  • 4
  • 20
  • 38
suraj
  • 326
  • 4
  • 6
16

Production stack: React, React Router v4, BrowswerRouter, Express.js, Nginx

  1. User BrowserRouter for pretty URLs

    File app.js

     import { BrowserRouter as Router } from 'react-router-dom'
    
     const App = () {
       render() {
         return (
             <Router>
                // Your routes here
             </Router>
         )
       }
     }
    
  2. Add index.html to all unknown requests by using /*

    File server.js

     app.get('/*', function(req, res) {
       res.sendFile(path.join(__dirname, 'path/to/your/index.html'), function(err) {
         if (err) {
           res.status(500).send(err)
         }
       })
     })
    
  3. bundle Webpack with webpack -p

  4. run nodemon server.js or node server.js

You may want to let nginx handle this in the server block and disregard step 2:

location / {
    try_files $uri /index.html;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Isaac Pak
  • 4,467
  • 3
  • 42
  • 48
13

Try adding a ".htaccess" file inside the public folder with the below code.

RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]

RewriteRule ^ /index.html [L]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kiran Joshi
  • 131
  • 1
  • 6
  • Thanks, this was what worked for me on Fedora 28 with apache. None of the above rewrite rules nor the ones on the CRA page worked for me. I added them to the virtual host setup instead of in a separate .htaccess file. – boerre Nov 25 '19 at 14:44
10

If you are hosting using nginx and need a quick fix...

Add the following line to your nginx configuration inside the location block:

location / {
  try_files $uri /index.html;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
BeK
  • 171
  • 1
  • 7
9

If you're using Firebase, all you have to do is make sure you've got a rewrites property in your firebase.json file in the root of your app (in the hosting section).

For example:

{
  "hosting": {
    "rewrites": [{
      "source":"**",
      "destination": "/index.html"
    }]
  }
}

Further reading on the subject:

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Joshua Dyck
  • 2,113
  • 20
  • 25
8

I found the solution for my SPA with React Router (Apache). Just add this in file .htaccess:

<IfModule mod_rewrite.c>

  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-l
  RewriteRule . /index.html [L]

</IfModule>

Source: Apache configuration for React Router

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
7

If you are using Express.js or some other framework in the backend, you can add the similar configuration as below and check out the Webpack public path in the configuration. It should work fine even on reload if you are using BrowserRouter.

expressApp.get('/*', (request, response) => {
    response.sendFile(path.join(__dirname, '../public/index.html'));
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
neeraj-dixit27
  • 2,812
  • 2
  • 15
  • 6
  • this is the simplest solution. note this route should go *after* any other routes as it's a catch all – dcsan Feb 14 '20 at 04:01
  • changing from "*" to "/*" solved a similar problem for me. But rather than getting the error "cannot GET" as the OP, the browser received an empty HTML on refresh or URL-navigation. – Simon Nov 19 '21 at 15:20
7

For those who are using IIS 10, this is what you should do to make this right.

Be sure that you are using browserHistory with this. As for reference, I will give the code for the routing, but this is not what matters. What matters is the next step after the component code below:

class App extends Component {
    render() {
        return (
            <Router history={browserHistory}>
                <div>
                    <Root>
                        <Switch>
                            <Route exact path={"/"} component={Home} />
                            <Route path={"/home"} component={Home} />
                            <Route path={"/createnewproject"} component={CreateNewProject} />
                            <Route path={"/projects"} component={Projects} />
                            <Route path="*" component={NotFoundRoute} />
                        </Switch>
                    </Root>
                </div>
            </Router>
        )
    }
}
render (<App />, window.document.getElementById("app"));

Since the problem is IIS receives requests from client browsers, it will interpret the URL as if it is asking for a page, then returns a 404 page since there isn't any available page. Do the following:

  1. Open IIS
  2. Expand Server, and then open the Sites Folder
  3. Click the website/application
  4. Go to the Error Pages
  5. Open the 404 error status item in the list
  6. Instead of the option "Insert content from static file into the error response", change it to "Execute a URL on this site" and add "/" slash value to the URL.

And it will now work fine.

Enter image description here

Enter image description here

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
6

If you do have a fallback to your index.html, make sure that in your index.html file you have this:

<script>
  System.config({ baseURL: '/' });
</script>

This may differ from project to project.

Matt Goo
  • 1,118
  • 1
  • 11
  • 12
5

Using HashRouter worked for me with Redux also. Just simply replace:

import {
    Router //replace Router
} from "react-router-dom";

ReactDOM.render(
    <LocaleProvider locale={enUS}>
        <Provider store={Store}>
            <Router history={history}> // Replace here saying Router
                <Layout/>
            </Router>
        </Provider>
    </LocaleProvider>, document.getElementById("app"));

registerServiceWorker();

with:

import {
    HashRouter // Replaced with HashRouter
} from "react-router-dom";

ReactDOM.render(
    <LocaleProvider locale={enUS}>
        <Provider store={Store}>
            <HashRouter history={history}> //replaced with HashRouter
                <Layout/>
            </HashRouter>
        </Provider>
    </LocaleProvider>, document.getElementById("app"));

registerServiceWorker();
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alireza
  • 100,211
  • 27
  • 269
  • 172
4

Fixing the "cannot GET /URL" error on refresh or on calling the URL directly.

Configure your webpack.config.js to expect the given link the routes like this.

module.exports = {
  entry: './app/index.js',
  output: {
       path: path.join(__dirname, '/bundle'),
       filename: 'index_bundle.js',
       publicPath: '/'
  },
ravibagul91
  • 20,072
  • 5
  • 36
  • 59
Lalit Tyagi
  • 264
  • 2
  • 10
4

If you are using the "create-react-app" command,

to generate a React application then the package.json file needs to have one change for a properly running production build React SPA in a browser. Open up file package.json and add the following code segment to that,

"start": "webpack-dev-server --inline --content-base . --history-api-fallback"

Here the most important part is the "--history-api-fallback" to enable the history API call back.

Sometimes you will get a 404 error if you use Spring or any other back-end API. So in such a situation, you need to have a controller in the back-end to forward any request (you desired) to the index.html file to handle by react-router. The following demonstrates an example controller written using Spring.

@Controller
public class ForwardingController {
    @RequestMapping("/<any end point name>/{path:[^\\.]+}/**")
    public String forward(HttpServletRequest httpServletRequest) {
        return "forward:/";
    }
}

For example, if we take a back-end API REST endpoint as "abc" (http://localhost:8080/abc/**), any request coming to that endpoint will redirect to the React application (index.html file), and react-router will handle that afterwards.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Malinda
  • 524
  • 2
  • 5
  • 11
  • You can also add multiple mappings like this: @RequestMapping({"/myurl1/**", "/myurl2/**"}) – Craigo Dec 16 '20 at 23:11
3

I'm not using server-side rendering yet, but I hit the same problem as the OP where Link seemed to work fine most of the time, but failed when I had a parameter. I'll document my solution here to see if it helps anyone.

My main JSX content contains this:

<Route onEnter={requireLogin} path="detail/:id" component={ModelDetail} />

This works fine for the first matching link, but when the :id changes in <Link> expressions nested on that model's detail page, the URL changes in the browser bar, but the content of the page did not initially change to reflect the linked model.

The trouble was that I had used the props.params.id to set the model in componentDidMount. The component is just mounted once, so this means that the first model is the one that sticks on the page and the subsequent Links change the props, but leave the page looking unchanged.

Setting the model in the component state in both componentDidMount and in componentWillReceiveProps (where it is based on the next props) solves the problem and the page content changes to reflect the desired model.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Paul Whipp
  • 16,028
  • 4
  • 42
  • 54
  • 1
    It might be better to use the component constructor (which also has access to `props`) i.s.o. `componentDidMount` if you ever want to try for server-side rendering. Because `componentDidMount` is only called in the browser. It's purpose is to do stuff with the DOM, such as attaching event listeners to `body` etc that you can't do in `render`. – Stijn de Witt Dec 20 '16 at 18:38
3

Here is a simple, clear and better solution. It works if you use a web server.

Each web server has an ability to redirect the user to an error page in case of HTTP 404. To solve this issue, you need to redirect the user to the index page.

If you use a Java base server (Tomcat or any Java application server), the solution could be the following:

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <!-- WELCOME FILE LIST -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!-- ERROR PAGES DEFINITION -->
    <error-page>
        <error-code>404</error-code>
        <location>/index.jsp</location>
    </error-page>

</web-app>

Example:

  • GET http://example.com/about
  • The web server throws HTTP 404 because this page does not exist on the server side
  • the error page configuration tells to the server that send the index.jsp page back to the user
  • then JavaScript will do the rest of the job on the client side, because the URL on the client side is still http://example.com/about.

That is it. No more magic needs:)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
zappee
  • 20,148
  • 14
  • 73
  • 129
  • This was awesome! I am using Wildfly 10.1 server and made this update to my web.xml file, except I had location set to just '/'. This was because in my react code I used browser history like this: `const browserHistory = useRouterHistory(createHistory)({ basename: '/' });` – Chuck L Jun 01 '17 at 21:40
  • 1
    Are you sure that this doesn't impact SEO? You are giving a 404 status to pages that actually exist. The user might never realise this, but bots do pay a lot of attention, in fact so much, that they will not scrape your page. – subharb Dec 15 '17 at 07:53
3

I solved this problem by changing file webpack.config.js.

My new configuration looks like:

Before

output: {
  path: path.join(__dirname, '/build/static/js'),
  filename: 'index.js'
},


devServer: {
  port: 3000
}

After

output: {
  path: path.join(__dirname, '/build/static/js'),
  filename: 'index.js',
  publicPath: '/'
},


devServer: {
  historyApiFallback: true,
  port: 3000
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
3

I was facing this issue in Electron when I was using React for front-end and react-router-dom for routing.

I replaced BrowserRouter with HashRouter and it was fixed.

Here is a simple example:

import {
  HashRouter as Router,
  Switch,
  Route,
} from "react-router-dom";
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sanan Ali
  • 2,349
  • 1
  • 24
  • 34
  • 1
    This is a live-saver, after days of research, just changing `BrowerRouter` with `HashRouter`, solves the Link or ` – Godstime Oct 07 '21 at 12:57
3

I faced same issue when i used apache(Httpd) server. I solved bellowing this way and works 100% for me.

step-1:

  • Go /etc/httpd/conf/httpd.conf / for new version go to etc/apache2/apache2.conf
  • Change the AllowOverride None to AllowOverride All.
  • Restart the apache server.

step-2:

After build make a .htaccess file into root folder.

Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]
Md. Shafiqul Islam
  • 196
  • 1
  • 4
  • 9
2

If you are hosting in IIS: Adding this to my webconfig solved my problem

<httpErrors errorMode="Custom" defaultResponseMode="ExecuteURL">
    <remove statusCode="500" subStatusCode="100" />
    <remove statusCode="500" subStatusCode="-1" />
    <remove statusCode="404" subStatusCode="-1" />
    <error statusCode="404" path="/" responseMode="ExecuteURL" />
    <error statusCode="500" prefixLanguageFilePath="" path="/error_500.asp" responseMode="ExecuteURL" />
    <error statusCode="500" subStatusCode="100" path="/error_500.asp" responseMode="ExecuteURL" />
</httpErrors>

You can make a similar configuration for any other server.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Barny
  • 383
  • 1
  • 3
  • 13
2

A solution in React for a JavaScript SPA with Laravel

The accepted answer is the best explanation of why such problems happen. As already explained, you have to configure both the client side and server side.

In your blade template, include the JavaScript bundled file, make sure to use URL facade like this:

<script src="{{ URL::to('js/user/spa.js') }}"></script>

In your routes, make sure to add this to the main endpoint where the blade template is. For example,

Route::get('/setting-alerts', function () {
   return view('user.set-alerts');
});

The above is the main endpoint for the blade template. Now add an optional route too,

Route::get('/setting-alerts/{spa?}', function () {
  return view('user.set-alerts');
});

The problem that happens is that first the blade template is loaded, then the React router. So, when you're loading '/setting-alerts', it loads the HTML content and the JavaScript code.

But when you load '/setting-alerts/about', it first loads on the server side. Since it is on the server side, there isn't anything on this location, and it returns not found. When you have that optional router, it loads that same page and React Router is also loaded, then the React loader decides which component to show.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Koushik Das
  • 9,678
  • 3
  • 51
  • 50
  • Is it possible to load to one exact URI to server then the server make a redirect to React? Like example, when I load `/user/{userID}`, I just need to return a universal `/user` HTML view, bring the ID and call it using AJAX or axios. Is it possible to do that? are they SEO friendly? – Ryuujo Feb 22 '19 at 04:24
  • I use this code for my SPA's with a Laravel backend: ```Route::any('{any?}', 'MyController@index');``` It matches to any route and passes it to the SPA – Felix Geenen Sep 07 '20 at 21:00
2

Adding more information to Joshua Dyck's answer.

If you are using Firebase and want to use both the root route and a sub-directory route you need to add the following code in your firebase.json:

{
  "hosting": {
    "rewrites": [
      {
        "source": "*",
        "destination": "/index.html"
      },
      {
        "source": "/subdirectory/**",
        "destination": "/subdirectory/index.html"
      }
    ]
  }
}

Example:

You are building a website for a client. You want the owner of the website to add information in https://your.domain.com/management while the users of the website will navigate to https://your.domain.com.

In this case your firebase.json file will look like that:

{
  "hosting": {
    "rewrites": [
      {
        "source": "*",
        "destination": "/index.html"
      },
      {
        "source": "/management/**",
        "destination": "/management/index.html"
      }
    ]
  }
}
Agney
  • 18,522
  • 7
  • 57
  • 75
neomib
  • 3,503
  • 4
  • 17
  • 27
2

We used Express.js' 404 handling approach.

// Path to the static React build directory
const frontend = path.join(__dirname, 'react-app/build');

// Map the requests to the static React build directory
app.use('/', express.static(frontend));

// All the unknown requests are redirected to the React SPA
app.use(function (req, res, next) {
    res.sendFile(path.join(frontend, 'index.html'));
});

It works like a charm. A live demo is our site.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Zameer Ansari
  • 28,977
  • 24
  • 140
  • 219
2

If trying to serve a React app from an IIS Virtual Directory (not the root of a website):

When setting up your redirects, '/' won’t work on its own. For me, it needed the virtual directory name in there too. Here is what my web configuration looked like:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <defaultDocument>
            <files>
                <remove value="default.aspx" />
                <remove value="iisstart.htm" />
                <remove value="index.htm" />
                <remove value="Default.asp" />
                <remove value="Default.htm" />
            </files>
        </defaultDocument>
        <rewrite>
            <rules>
                <rule name="React Routes" stopProcessing="true">
                    <match url=".*" />
                    <conditions logicalGrouping="MatchAll">
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                        <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="/YOURVIRTUALDIRECTORYNAME/" />
                </rule>
            </rules>
        </rewrite>
        <directoryBrowse enabled="false" />
        <httpErrors errorMode="Custom" defaultResponseMode="ExecuteURL">
            <remove statusCode="500" subStatusCode="100" />
            <remove statusCode="500" subStatusCode="-1" />
            <remove statusCode="404" subStatusCode="-1" />
            <remove statusCode="403" subStatusCode="18" />
            <error statusCode="403" subStatusCode="18" path="/YOURVIRTUALDIRECTORYNAME/" responseMode="ExecuteURL" />
            <error statusCode="404" path="/YOURVIRTUALDIRECTORYNAME/" responseMode="ExecuteURL" />
            <error statusCode="500" prefixLanguageFilePath="" path="/YOURVIRTUALDIRECTORYNAME/" responseMode="ExecuteURL" />
            <error statusCode="500" subStatusCode="100" path="/YOURVIRTUALDIRECTORYNAME/" responseMode="ExecuteURL" />
        </httpErrors>
    </system.webServer>
</configuration>

In addition to the web.config file, the React app itself needed some changes:

In file package.json, you need to add a 'homepage' entry:

{
  "name": "sicon.react.crm",
  "version": "0.1.0",
  "private": true,
  "homepage": "/YOURVIRTUALDIRECTORYNAME/",
  "dependencies": {
...

I added the basename to my browser history object that I pass into the router to get access to history:

import  {createBrowserHistory } from 'history';

export default createBrowserHistory({
    //Pass the public URL as the base name for the router basename: process.env.PUBLIC_URL
});

I also added this property on my React router in file App.js:

<Router history={history} basename={process.env.PUBLIC_URL}>

Finally, in file index.html I added the following tab above the 'title' tag:

<base href="%PUBLIC_URL%/">

It may be that some steps where not required, but this seems to have done the job for me. I don't know how to set it up to run either in the root of a site or a virtual directory without a recompile though, as the homepage in the package.json can't be swapped after a build as far as I'm aware.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
WraithNath
  • 17,658
  • 10
  • 55
  • 82
2

I am using ASP.NET Core and React. The solution for the problem of manual routing and refreshing routes in production environment was to create web.config file in the root of the main project of ASP.NET Core which will configure routing on the production server.

Location of the file inside a project:

enter image description here

Content of the web.config file:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Rewrite Text Requests" stopProcessing="true">
                    <match url=".*" />
                    <conditions>
                        <add input="{HTTP_METHOD}" pattern="^GET$" />
                        <add input="{HTTP_ACCEPT}" pattern="^text/html" />
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="/index.html" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>
Dušan
  • 269
  • 1
  • 11
2

With the following simple change in my nginx configuration, I was able to overcome hard refresh web application break and manual URL typing web application breaking.

Before

location / {
    try_files $uri $uri/ =404;
}

After

location / {
    try_files $uri /index.html;
}

There might be other solutions, but this was really quick and time saver for me.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Channaveer Hakari
  • 2,769
  • 3
  • 34
  • 45
1

Solution for Preact with preact-router

Works with refresh and direct access

For those discovering this via Google, here's a demo of preact-router + hash history:

const { h, Component, render } = preact; /** @jsx h */
const { Router } = preactRouter;
const { createHashHistory } = History;
const App = () => (
    <div>
        <AddressBar />

        <Router history={createHashHistory()}>
            <div path="/">
                <p>
                    all paths in preact-router are still /normal/urls.
                    using hash history rewrites them to /#/hash/urls
                </p>
                Example: <a href="/page2">page 2</a>
            </div>
            <div path="/page2">
                <p>Page Two</p>
                <a href="/">back to home</a><br/>
            </div>
        </Router>
    </div>
);

jsfiddle

Edgar
  • 6,022
  • 8
  • 33
  • 66
keemor
  • 1,149
  • 15
  • 16
1

I am using Webpack, and I had the same problem.

Solution:

In your server.js file:

const express = require('express');
const app = express();

app.use(express.static(path.resolve(__dirname, '../dist')));
  app.get('*', function (req, res) {
    res.sendFile(path.resolve(__dirname, '../dist/index.html'));
    // res.end();
  });

Why doesn't my application render after refreshing?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • This did it for me! Initially I had res.sendFile(path.JOIN(publicPath, "index.html")); I changed "join" to "resolved" like in the example above to: res.sendFile(path.resolve("./dist", "index.html")); I was also playing around with __dirname but could not really understand it or get it working so I manually copied in "./dist" because this is where my index.html is served from. I also declared it like so earlier: app.use(express.static("./dist")); – logixplayer May 10 '20 at 06:48
1

As I am using ASP.NET Core, something like this helped me:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        var url = Request.Path + Request.QueryString;
        return App(url);
    }

    [Route("App")]
    public IActionResult App(string url)
    {
        return View("/wwwroot/app/build/index.html");
    }

}

Basically on the ASP.NET MVC side, all the routes not matching will fall into to Home/Index as it specified in startup.cs. Inside Index it is possible to get the original request URL and pass it wherever needed.

File startup.cs

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");

    routes.MapSpaFallbackRoute(
        name: "spa-fallback",
        defaults: new { controller = "Home", action = "Index" });
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Elnoor
  • 3,401
  • 4
  • 24
  • 39
  • If the web api is separated from the app how do you will handle this? – Dayán Ruiz May 01 '19 at 17:18
  • @dayanrr91 if your project has web api, you won't need server side routing anyways. And your react-router i believe should read the url and do appropriate routing. Nothing should block it – Elnoor May 01 '19 at 17:28
  • your comment is very appreciated, but then I have a question, I just added HashRouter, but now when I enter to any URL manually it will redirect to my home component instead of redirecting to my home and then to the component that I was trying to getting in, is there any workaround for that? – Dayán Ruiz May 01 '19 at 17:33
  • @dayanrr91 no idea really, haven't tried hashrouter ever but i think should be something to do with your code. The way hashrouter works should be similar to browserrouter. Also i just tested here in this sandbox, it works fine https://codesandbox.io/s/25okp1mny, test the url https://25okp1mny.codesandbox.io/#/roster/5 – Elnoor May 01 '19 at 17:47
1

If you are coming here and you are using Apache and don’t have a .htaccess file, this is a configuration file that worked for me:

sites-enabled/somedomain.com.conf

<VirtualHost *:80>
    ServerName somedomain.com
    ServerAlias *.somedomain.com
    DocumentRoot /www/somedomain.com/build

    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /www/somedomain.com/build/index.html [L,NC,QSA]

</VirtualHost>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DevB2F
  • 4,674
  • 4
  • 36
  • 60
1

In case you are running it on a Google Bucket, the simple solution to this is to consider 'index.html' for Error (404 not found) Page.

To do so:

  1. In the list of buckets, find the bucket you created.
  2. Click the Bucket overflow menu (...) associated with the bucket and select Edit website configuration.
  3. In the website configuration dialog, specify the main page as the error page too.
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sansei
  • 93
  • 10
1

Using Express.js on the backend and React on the frontend (without react-create-app) with reach/router, the correct reach/router route React component is shown and the menu link is set to the active style when hitting Enter in the address bar, e.g., http://localhost:8050/pages.

Please checkout the below, or go straight to my repository https://github.com/nickjohngray/staticbackeditor. All the code is there.

Webpack:

Setup proxy. This allows any calls from port 3000 (React) to call the server, including the call to get index.html or anything in the address bar when the Enter key is hit. It also allows calls to the API route, to get JSON data.

Like await axios.post('/api/login', {email, pwd}):

devServer: {
    port: 3000,
    open: true,
    proxy: {
      '/': 'http://localhost:8050',
    }
  }

Setup Express.js routes

app.get('*', (req, res) => {
    console.log('sending index.html')
    res.sendFile(path.resolve('dist', 'index.html'))

});

This will match any request from React. It just returns the index.html page, which is in my dist folder. This page, of course, has a more single-page React app. (Note any other routes should appear above this, and in my case these are my API routes.)

React Routes

<Router>
    <Home path="/" />
    <Pages path="pages"/>
    <ErrorPage path="error"/>
    <Products path="products"/>
    <NotFound default />
</Router>

These routes are defined in my Layout component that will load the corresponding component when the path matches.

React Layout constructor

constructor(props) {
    super(props);

    this.props.changeURL({URL: globalHistory.location.pathname});
}

The Layout constructor is called as soon as it loads. In here, I call my redux action changeURL that my menu listens to, so it can highlight the correct menu item, like below:

Menu code

<nav>
    {this.state.links.map( (link) =>
    <Link className={this.getActiveLinkClassName(link.path) } to={link.path}>
      {link.name}
    </Link>)}
</nav>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
nick
  • 601
  • 7
  • 7
1

I'm using React.js + Webpack mode. I added --history-api-fallback parameter in package.json file. Then page refreshing is working correctly.

Every time when I change the code, the web page is refreshed automatically.

"scripts": {
  "start": "rimraf build && cross-env NODE_ENV='development' webpack --mode development && cross-env NODE_ENV=development webpack-dev-server --history-api-fallback",
  ...
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
NinjaDev
  • 1,228
  • 11
  • 21
1

In my case the URL was not loading when I was using a parameter in it.

As a quick fix, I added <base href="<yourdomain/IP>"></base> under the <title> tag of the index.html file in the build folder.

And this just fixed my problem.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1

if you are using Hostringer

create a .htaccess (make sure there's no file extension like .txt) in root file of ur file manager

paste the below code

RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

refresh the website

balaji gv
  • 31
  • 5
0

I had the same problem and this solution worked for us...

Background:

We are hosting multiple applications on the same server. When we would refresh the server, it would not understand where to look for our index in the destination folder for that particular application. The above link will take you to what worked for us...

We are using:

File package.json:

"dependencies": {
  "babel-polyfill": "^6.23.0",
  "ejs": "^2.5.6",
  "express": "^4.15.2",
  "prop-types": "^15.5.6",
  "react": "^15.5.4",
  "react-dom": "^15.5.4",
  "react-redux": "^5.0.4",
  "react-router": "^3.0.2",
  "react-router-redux": "^4.0.8",
  "redux": "^3.6.0",
  "redux-persist": "^4.6.0",
  "redux-thunk": "^2.2.0",
  "webpack": "^2.4.1"
}

My webpack.config.js file:

/* eslint-disable */
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const babelPolyfill = require('babel-polyfill');
const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
  template: __dirname + '/app/views/index.html',
  filename: 'index.html',
  inject: 'body'
});

module.exports = {
  entry: [
    'babel-polyfill', './app/index.js'
  ],
  output: {
    path: __dirname + '/dist/your_app_name_here',
    filename: 'index_bundle.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      loader: 'babel-loader',
      query : {
          presets : ["env", "react", "stage-1"]
      },
      exclude: /node_modules/
    }]
  },
  plugins: [HTMLWebpackPluginConfig]
}

My index.js file:

import React from 'react'
import ReactDOM from 'react-dom'
import Routes from './Routes'
import { Provider } from 'react-redux'
import { createHistory } from 'history'
import { useRouterHistory } from 'react-router'
import configureStore from './store/configureStore'
import { syncHistoryWithStore } from 'react-router-redux'
import { persistStore } from 'redux-persist'

const store = configureStore();

const browserHistory = useRouterHistory(createHistory) ({
  basename: '/your_app_name_here'
})
const history = syncHistoryWithStore(browserHistory, store)

persistStore(store, {blacklist: ['routing']}, () => {
  console.log('rehydration complete')
})
// persistStore(store).purge()

ReactDOM.render(
    <Provider store={store}>
      <div>
        <Routes history={history} />
      </div>
    </Provider>,
  document.getElementById('mount')
)

My app.js file:

var express = require('express');
var app = express();

app.use(express.static(__dirname + '/dist'));
// app.use(express.static(__dirname + '/app/assets'));
app.set('views', __dirname + '/dist/your_app_name_here');
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');

app.get('/*', function (req, res) {
    res.render('index');
});

app.listen(8081, function () {
  console.log('MD listening on port 8081!');
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
J. Parrish
  • 36
  • 2
0

I like this way of handling it. Try adding: yourSPAPageRoute/* on the server side to get rid of this problem.

I went with this approach, because even the native HTML5 History API doesn't support correct redirection on page refresh (as far as I know).

Note: The selected answer has already addressed this, but I'm trying to be more specific.

Express Route

Test - History API

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rehan H
  • 314
  • 3
  • 13
0

Assume you have the following Home route definition:

<Route exact path="/" render={routeProps => (
   <Home routeProps={routeProps}/>
)}/>

{/* Optional catch-all router */}
<Route render={routeProps => (
       <div><h4>404 not found</h4></div>
)}/>

At your Home component, you can intercept the request at ComponentWillMount event,

const searchPath = this.props.routeProps.location.search;

if (searchPath){
    this.props.routeProps.history.push("/" + searchPath.replace("?",""));
}
else{
    /*.... originally Home event */
}

Now, instead of calling /joblist at the URL, you can request /?joblist, and the <Home> Component will auto redirect the request to /joblist (take note the extra question mark in the path).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

Here is a frontend workaround I discovered that does not require modifying anything on the server.

Let's say your site is mysite.com and you have a React Route to mysite.com/about. In index.js, where you mount your top-level component, you can put another Router like:

ReactDOM.render(
<Router>
    <div>
        <Route exact path="/" component={Home} />
        <Route exact path="/about"
            render={(props) => <Home {...props} refreshRout={"/about"}/>}
        />
    </div>
</Router>,

I'm assuming you have the original Router located somewhere below the top-level component in the virtual DOM. You also have to catch the url in your .urls if you are using Django like:

urlpatterns = [
       path('about/', views.index),
]

This will depend on what backend you're using, however. Requesting mysite/about will get you into index.js (where you mount the top-level component) where you can use the render prop of the Route, rather than the component prop, and pass '/about' as a prop to, in this example, the Home component.

Within Home, in either componentDidMount() or the useEffect() hook, do:

useEffect() {
   //check that this.props.refreshRoute actually exists before executing the
   //following line
   this.props.history.replace(this.props.refreshRoute);
}

I've assumed your Home component is rendering something like:

<Router>
   <Route exact path="/" component={SomeComponent} />
   <Route path="/about" component={AboutComponent} />
</Router>

Credit to (Pass props to a component rendered by React Router) for how to pass props to components in Routes.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Stevie
  • 326
  • 3
  • 16
0

I am using .NET Core 3.1 and just added the extension MapFallbackToController:

File Startup.cs

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");

        endpoints.MapFallbackToController("Index", "Home");
    });
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sergio
  • 71
  • 1
  • 3
0

The other way of requesting data, even though you are directing to URLs immediately, is to make every component have a method that calls to that last parameters, like /about/test.

Then to your State Provider, you have the function that connects to the component you want to request data with.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Vince
  • 776
  • 11
  • 21
0

The previous answers don't solve the problem where you want to use your browser router with proxy pass, and where you can't use root.

For me the solution is pretty simple.

Say you have a URL that's pointing to some port.

location / {
  proxy_pass http://127.0.0.1:30002/;
  proxy_set_header    Host            $host;
  port_in_redirect    off;
}

And now because of the browser router, sub paths are broken. However, you know what the sub paths are.

What is the solution to this? For sub path /contact

# Just copy paste.
location /contact/ {
  proxy_pass http://127.0.0.1:30002/;
  proxy_set_header    Host            $host;
}

Nothing else I've tried works, but this simple fix works.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
danieltan95
  • 810
  • 7
  • 14
0

Use useEffect to read the URL and apply the changes you want.


For example - a page has a button that opens a slider. When the button is clicked, a query param for the slider is added to the URL and the slider is opened.

Problem - on refreshing the page with the slider opened, the loaded page does not show the opened slider, even if the query param is present.

Solution: add a useEffect that reads the "slider" query param, and runs the button handler. Add a state variable for the slider query param, and also include it in the useEffect's dependency array.

You don't need to worry about backend request, assuming there is one in the slider component.

sanjarcode
  • 437
  • 5
  • 15
0

In case your URLs work OK locally, but you get a blank page with the error Not found when deploying your React App in Render Cloud, it is possible that you did not set up client side routing

enter image description here

Esteban
  • 101
  • 3
  • 6
0

Just add this server.js in the root directory.

const express = require('express');
const path = require('path');

const app = express();

app.use(express.static(path.join(__dirname, 'build')));

app.get('/*', function(req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(3000);
console.log('Server started on port 3000');

and change package.json

    "start": "node server.js",
Shubham
  • 9
  • 5
-1

You can use Vercel's hosting for your React app and with the same old way of routing in your React application with using BrowserRouting.

You need to add a vercel.json file at the root of your project and add this code to it:

{
  "rewrites": [
    {
      "source": "/((?!api/.*).*)",
      "destination": "/index.html"
    }
  ]
}

This works perfectly fine.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mehdi Faraji
  • 2,574
  • 8
  • 28
  • 76
-2

It’s pretty simple when you got cannot get a 403 error after refreshing a DOM component.

Just add this one line in your Webpack configuration, 'historyApiFallback: true '. This saved my whole day.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
praveenkumar s
  • 325
  • 4
  • 16
-2

HashRouter will be an easy implementation,

import {HashRouter as Router,Switch,Route,Link} from 'react-router-dom';


  function App() {
  return (
    <Router>
        <Switch>
          <Route path="/" exact component={InitialComponent} />
          <Route path="/some" exact component={SomeOtherComponent} />
        </Switch>
      </Router>
  );
}

It will be something like this in the browser - http:localhost:3000/#/ , http:localhost:3000/#/some

-2

Solution for react-router-dom: 6

Replace BrowserRouter with HashRouter

Using HashRouter will add /# in URLs but this will solve most of the problems of refresh/reload.

yogesh kumar
  • 103
  • 10