I am willing to use React-router for my application, and I am trying the example given in the doc first, which I copied below. Now when I go to localhost:3000/, I see "App" as expected, but every other page, such as localhost:3000/inbox returns "Cannot GET /inbox". What am I missing here ?
var About = React.createClass({
render: function () {
return <h2>About</h2>;
}});
var Inbox = React.createClass({
render: function () {
return <h2>Inbox</h2>;
}});
var App = React.createClass({
render () {
return (
<div><h1>App</h1>
<RouteHandler/>
</div>
)}});
var routes = (
<Route path='/' handler={App}>
<Route path="about" handler={About}/>
<Route path="inbox" handler={Inbox}/>
</Route>
);
If you are using webpack-dev-server there is an option called history-api-fallback. If set to true 404s will fallback to /index.html.
Add the option to devServer section of the Webpack config like this:
devServer: {
contentBase: 'app/ui/www',
devtool: 'eval',
hot: true,
inline: true,
port: 3000,
outputPath: buildPath,
historyApiFallback: true,
},
Link to Webpack docs: https://webpack.js.org/configuration/dev-server/
webpack-dev-server docs on Github: https://github.com/webpack/webpack-dev-server
I believe the issue is that you are making a http resource request:
GET /inbox HTTP/1.1
Host: localhost:3000
but are using client-side only routing. Are you intending on doing server side rendering too? You might need to change your router location to be HistoryLocation instead of HashLocation (the default).
The location prop of Router.run tells it where to match the routes against. If you're not running server side React, I believe you have to use Router.HashLocation (or just leave it blank).
If not, you are accessing your component the wrong way. Try using http://localhost:3000/#/inbox. It can take a little to familiarize yourself with React-Router but it is definitely worth it!
React Router Documentation - HashLocation
Simplest option: use hash history instead. The urls stay are not very nice and if you need better SEO, I'd opt for server-side rendering as well.
If you're interested in detail about this, this answer was really helpful for me: React-router urls don't work when refreshing or writting manually
The request will be sent to the server, and the directory/file does not really exists so it will return 404, so you have to tell the server to return the index page for all requests, and react will handle the rooting :
if like me, you are hosting your react app 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 IIS server to return the main page to the client instead of 404 error.
I came across the same problem, if you have a server that responds to requests and a HashRouter if you are using a static file server. Instead of using BrowserRouter use HashRouter its not the perfect fix, but should solve the cannot GET "/path" error. be sure to import HashRouter from 'react-router-dom'
render() {
return (
<div>
<HashRouter>
<Blog />
</HashRouter>
</div>
);
}
source: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/guides/basic-components.md
To add to those not helped by the answer: historyApiFallback: true works only for the first level of the url. So site.com/about will be redirected to index but site.com/about/me won't be. In this case, you need to add the rewrite option:
historyApiFallback: {
rewrites: [
{ from: /./, to: '/index.html' }
]
}
This will match using a Regex expression all routes. Take care to also add this:
output: {
// ...
publicPath: '/'
//...
},
Otherwise, the redirect will work to index.html, but index.html may have just bundle.js as the webpacked script, and will not read correctly. It should instead be '/bundle.js', which this config option will do.
I had the same problem. Finely worked with webpack-dev-server, but not with express.
The problem was in version of Express.js. I haven't noticed that it was beta (5.0.0-beta.1)
The solution is upgrading / downgrading to stable version (4.18.2 works fine).
Hope it helps!
Related
I am trying to migrate an existing MVC application that uses razor pages into web API + ReactJS client. I'm doing this step by step which means I have migrated the homepage (Home/Index) plus a few other pages/features to react. There are links between razor pages and those that are implemented in react. This works perfectly in development environment which runs react development server.
For example in development environment, if I route to localhost:12345/Controller/Action (for an available Controller and action) server executes the respective action and returns the proper view and if I try to access a route which is unknown to server it returns index.html and react-router handles the routing from this point. This is the accepted behavior and works like a charm in the development environment.
But things change when I build the react app and run the application in production mode. The request never reaches the server and is handled on the client-side by react-router. When I hard-refresh the page it triggers the MVC actions as accepted though.
I would like to have the same behaviour it has in development for the production.
This is how the starup.cs file looks like.
// ConfigureServices
.
.
.
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ReactSource/build";
});
.
.
.
// Configure
.
.
.
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ReactSource";
// do not start the react development server in production
if (env.IsDevelopment()) { spa.UseReactDevelopmentServer(npmScript: "start"); }
});
.
.
Any Idea what I'm missing here? Should I modify the react-router for this?
I solved my problem by turning off (unregistering) the create-react-app out of the box service worker which tried to cache assets and was preventing the requests to reach the server.
In the index.js file, I simply imported { unregister } from "./registerServiceWorker" and replaced registerServiceWorker(); with unregister(); at the end of the auto generated index.js file.
To have a Progress Web Application, you must enable the Service worker in your React web app.
For solve cache problem in production mode you must enter following code in index.tsx file:
ReactDOM.render(
<BrowserRouter basename={baseUrl}>
<App />
</BrowserRouter>,
rootElement);
//index page is Route exact path.
if (window.location.href.includes('index'))
registerServiceWorker();
else
unregisterAndReload();
function unregisterAndReload() {
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
}
and App.tsx file should be like the following:
render() {
return (
<Layout>
<Route exact path='/index' component={Home} />
<Route path='/contact' component={ContactUs} />
</Layout>
);
}
I'm using this Router:
import {BrowserRouter as Router, Route, Switch } from "react-router-dom";
<Router basename={process.env.REACT_APP_ROUTER_BASE || '/MyApp'}>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/login" component={Login} />
{/*<Redirect path="*" to="/" />*/}
</Switch>
</Router>
When working with dev mode (npm start), it's working and redirecting as expected.
When working with production mode (npm build --> copy the output of build to WebContent folder on a war to be deployed on tomcat), it behaves differently:
http://host:port/MyApp is working fine, and routing using the app is fine too
routing manually by typing the route value on the browser, e.g.
http://host:port/MyApp/login is giving: Page can't be found.
(Note: typing the route manually in dev mode working fine)
Why it differs? How it can be solved?
What I had tried:
Reference: React routing not working while manually changing URL | React-router 4
adding devServer entry (with historyApiFallback: true) to my webpack.config.prod.json:
export default {
resolve: {
extensions: ['*', '.js', '.jsx', '.json'],
// To support react-hot-loader
alias: {
'react-dom': '#hot-loader/react-dom'
}
},
devtool: 'source-map', // more info:https://webpack.js.org/guides/production/#source-mapping and https://webpack.js.org/configuration/devtool/,
devServer: {
historyApiFallback: true,
},
What historyApiFallback: true does is it renders index.html if the requested file doesn't exist. Your production webserver is not doing this, so when it attempts to load /MyApp/login, it see that there is no file called /MyApp/login (or /MyApp/login/index.html depending on configuration) and returns a 404.
Since React Router is handling your routing logic client side, you need to set up your webserver to fallback to delivering index.html if it can't find a page.
I'm trying to use react-router to enable client side routing, but I can only get the routing to behave as expected at one level deep past "/". (i.e. localhost:8080/ works, localhost:8080/{id} works, but localhost:8080/vote/{id} does not)
Additionally, I am trying to develop locally using webpack-dev-server, and deploy to heroku using webpack -p and an expressjs server. My express server is set to have all routes default to index.html.
app.get('/*', (req, res) => {
res.sendFile(path.resolve(__dirname, './dist/index.html'));
});
When using npm start (express server) and I try to navigate to localhost:8080/vote/{id} the console says: SyntaxError: expected expression, got '<' meaning I have a situation like this issue. However, when using webpack-dev-server, I get a different error in the console that says: Loading failed for the with source “http://localhost:8080/vote/bundle.js”. I believe what I'm seeing is two different outputs for the same core problem, the difference being my environment or differences in how express/webpack-dev-server are serving up the content.
Here is my full expressJS server:
const express = require('express');
const path = require('path');
const port = process.env.PORT || 8080;
app = express();
// the __dirname is the current directory from where the script is running
app.use('/', express.static(path.resolve(__dirname, 'dist')));
// send the user to index html page inspite of the url
app.get('/*', (req, res) => {
res.sendFile(path.resolve(__dirname, './dist/index.html'));
});
app.listen(port);
console.log("server started on port " + port);
Here are the relevant parts of my webpack.config.js:
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: "/"
},
devServer: {
contentBase: path.resolve(__dirname, "dist"),
historyApiFallback: true
}
And here is my App.js with relevant routes (Home, RealtimeView, PollVote being custom React components):
export default class App extends Component {
render() {
return (
<BrowserRouter history={browserHistory}>
<div>
<Route exact path="/" component={Home} />
<Route exact path="/:id" component={RealtimeView} />
<Route exact path="/vote/:id" component={PollVote}/>
</div>
</BrowserRouter>
);
}
}
With this configuration, I can reliably get localhost:8080/ to work and localhost:8080/{whatever} to work but localhost:8080/vote/{id} or any route more complicated than localhost:8080/{something} fails with the errors I mentioned earlier depending on if I'm using webpack-dev-server or my expressjs server.
FWIW I'm relatively new to webdev (my experience so far is that it's a cluster-fck) and I can't go with a totally isomorphic app because my back-end is in java/spring and I'm not re-writing my whole back-end. I found this post to be helpful but it doesnt solve my problem. Please help this is making me crazy.
Try setting fallback file explicitly:
historyApiFallback:{
index: 'index.html'
}
I have a simple web-app made with create-react-app and express.
All of the pages made with react router work fine locally, as well as online on my own machine once deployed to Heroku.
But, after testing online on other machines, I can't access these pages - whenever I click the links to them it displays Cannot GET /*route*
I still have the *name*.herokuapp.com domain if that affects it in any way
The redirect code I use is as follows: (I use firebase and react-bootstrap as well)
class App extends Component {
render() {
return (
<div>
<MyNavbar/>
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/eateries" component={Eateries}/>
<Route exact path="/thank-you" component={ThankYou}/>
</Switch>
</div>
);
}
Redirecting to /thank-you:
componentWillMount() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
window.location = "thank-you"
}
})
}
So essentially when a user signs in through a modal component it should take them to /thank-you
Redirecting to /eateries:
<NavItem href="/eateries">
For Eateries
</NavItem>
Is there something wrong with the way I'm redirecting users or using react router in general?
It's hard to know without seeing your server code - but in order to support react-router's rendering mechanism, you need to use a wild card route in your server code:
app.get('*', (req, res) => res.sendFile(path.resolve('build', 'index.html'));
This basically means "for any route not already matched, send the index.html file", which will then load your webapp, which in turn will handle routing. Note that you need to add the static middleware serving your assets before this - that's a gotcha I've forgotten many times. Most of your server file would then look like this:
const express = require('express');
const app = express();
app.use(express.static('build'));
app.get('*', (req, res) => res.sendFile(path.resolve('build', 'index.html'));
app.listen(process.env.PORT, () => console.log('listening for connections'));
Now, this would seem to work either way locally, since your web app is already loaded, and handles routing for you.
However, I've noticed that you're using window.location when redirecting your user. This makes some browsers at least (probably all) request the new page from the server, instead of letting the app deal with it. Instead, use the provided history property, which contains a push method.
componentWillMount() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.props.history.push('/thank-you');
}
});
}
This adds a new entry to the history stack. If you want a regular redirect, you should use .replace instead.
Hope this helps!
When I try to refresh the site when the url have a parameter I get a blank page.
My routes.js file is as follows :
const routes = (
<Route path="/" component={App}>
<IndexRoute component={HomePage}/>
<Route path="registration/:id" component={hideIfLoggedIn(Registration)}/>
<Route path="registration" component={hideIfLoggedIn(Registration)}/>
<Route path="reset-password" component={PasswordReset} />
<Route path="portal" component={requireAuth(UserPage)} />
</Route>
);
When I'm on http://localhost:8001/registration everything works perfect. If I refresh the page it works without problems.
But the problem is when I'm on http://localhost:8001/registration/step-one . First time when it loads it works good, but when I try to refresh the site I get a blank page.
EDIT
gulp.task('connect', ['watch'], function () {
connect.server({
root: ['dist'],
port: config.port,
base: config.devBaseUrl,
livereload: true,
fallback: 'dist/index.html'
})
});
You have to configure your server to serve index html for any other route .
Browser request /registration/step-one , it does not found index.html at this path hence fails
Use connect-history-api-fallback.
The middleware is available through NPM and can easily be added.
npm install --save connect-history-api-fallback.
Import the library
var history = require('connect-history-api-fallback');
Now you only need to add the middleware to your application like so
var connect = require('connect');
var app = connect()
.use(history())
.listen(3000);
Of course you can also use this piece of middleware with express:
var express = require('express');
var app = express();
app.use(history());