React Router cannot GET /route only after deployed to Heroku - reactjs

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!

Related

Asp.net core web application with react template: in development, server checks mvc routes first, but in prodcution the server only returns index.html

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>
);
}

Need help about nextjs dynamic url

I am having this problem that whenever i try to visit the page localhost:3000/blog/test directly it returns a 404 error. But whenever i try to visit it using <Link> component it works fine.
This is my code <Link href={{ pathname: '/blog', query: {slug: 'test'} }} as="/blog/test"><a className="nav__link">Blog</a></Link>
and i have a file blog.js in my pages folder.
What's happening is that on the client, with the Link component, you are creating a link to the blog.js page by setting "/blog" as the pathname.
When you go directly to the URL/blog/test, Next.js will try to render the page on the server and to do so will look for the file /pages/blog/test.js. That file doesn't exist, and Next.js doesn't know that you want to load the blog.js page and set query.slug to to the second part of the URL.
To do this, you need to map that route on the server to load the page you want, and pull the params you want out of the URL.
The Next.js docs cover this in Server Side Support for Clean URLs by using express to setup a custom server.
You can view the full code to get it working there, but your custom route will look something like this:
server.get('/blog/:slug', (req, res) => {
const actualPage = '/blog'
const queryParams = { slug: req.params.slug }
app.render(req, res, actualPage, queryParams)
})
You'll have to use now.json to set up your routes. Also it is important to note that it's now that builds the route so visiting it on the client side wont work if you are using localhost. Build your project with now and it should work.
Also the "as" parameter would be as={{ pathname:/user/manage/${variable}}}

React router behaviour with google cached pages.

We have recently ported our application from monolithic RubyOnRails to React/Redux Framework.
We are facing a problem with react-router and google cached pages in the results of google. In this case we have a SPA(single page application), which uses react-router (via browserHistory). The problem here is that: google cached page is a page wrapper, where the URL differs by the URL defined in the router of the SPA. So, in this case the routing of the application falls to the definition of a page not found. And the cached result of SPA page by google, instead showing the content of the page, page is reloaded to /search (wrapper path).
React Router Version: 3.0.3
React Version: 15.4.2
Here is how we have handled the generic Urls:
<Route onEnter={hitTheServer}>
<Route path="*" component={Home} />
</Route>
hitTheServer definition:
const hitTheServer = (nextState, replace, cb) => {
const url = `https://${config.server.public.host}${nextState.location.pathname}`;
// Hit the server only on client side rendering. A hack to handle unimplemented pages
if (typeof window !== 'undefined') {
window.location = url;
} else {
cb();
}
};
Here, we are doing page reload because there are some of the links are still not implement by out SPA, so, on full page reload, these links are router to their respective server (rails or node) via nginx.

Refreshing a react router page that has a dynamic url/params

I have several components displayed with react router that have dynamic url paths. An example path I have is
<Route path="/newproject/:id" onEnter={checkSesh} component= {ProjectDetails} />
When entering this component, I have a componentWillMount function that extract the id part of the url so that I can get the info for the correct project and render it on the ProjectDetails component.
componentWillMount() {
var id = this.props.router.params.id
this.props.teamDetails(id);
}
this.props.teamDetails(id) this calls a redux action creator that will make an axios request to an express route that will get the project info from the database.
export function teamDetails(id) {
return function(dispatch) {
axios.get('/getteaminfo/' + id)
.then(res => {
dispatch({ type: "SET_TEAM_DETAILS", payload: {
teamInfo: res.data.teamInfo,
admin: res.data.admin,
adminID: res.data.teamInfo.teamAdmin,
teamMembers: res.data.teamInfo.teamMembers
}
})
});
}
}
everything works fine upon visiting the page after already being logged in etc. But when I refresh the page /newproject/:id, i get an error Uncaught SyntaxError: Unexpected token <. An example url in my browser looks like http://localhost:3000/newproject/58df1ae6aabc4916206fdaae. When I refresh this page, I get that error. The error is complaining about my <!DOCTYPE html> tag at the very top of my index.html for some reason. This index.html is where all of React is being rendered.
When page is refreshed store state is not preserved. Make sure the state is not important to load the page, or at least initialized properly every time.
For e.g. login information if saved in store and not on browser with localStorage or cookies etc.. then on refresh, the error will come when trying to access /getteaminfo/ route through axios. The response will have error html and it can't be parsed by js.
Please check your web console on for more information. You can use chrome extension like https://github.com/zalmoxisus/redux-devtools-extension which will show your store and etc..
Make sure to check what /getteaminfo/ gives with id is not passed.
Also, make sure on your server side, did you route requests to react-router path through something like this?
e.g. express js,
app.get('*', function response(req, res) {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
be sure to sendFile with the real location of index.html
I found the answer here: react-router dynamic segments crash when accessed I added <base href="/" /> into the <head>of my index.html. You can also read more info here: Unexpected token < error in react router component

React-Router Broken after changing from hashLocation to browserLocation

I can't figure out why after changing from hashLocation to now using browserLocation that after I am able to click the link below to navigate to (render the interview) the interview, but now for some reason once I'm at the interview (I am redirected to the interview component that renders), if I refresh my browser and try to hit interviews/companies/:companyId again when I'm on that same page already, it instead hits my page not found in my express.js implementation.
So again, summarizing this: first time around when I click the link from my main landing page which is where that <Link> resides..when i first load the website, when I click that...it's able to hit interviews/companies/:companyId and render the interview component. All is good, until after you try to hit refresh, it bombs out. Not sure why
server.js
'use strict';
var express = require('express'),
app = module.exports = express(),
port = process.env.PORT || 3000,
path = require('path');
app.use(express.static('client'));
app.use(function (req, res) {
res.send('Sorry, Page Not Found');
});
console.log("port we're about to run on: " + port);
app.listen(port, function () {
console.log('Ready on port %d', port);
}).on('error', function (err) {
console.log(err);
});
On my main landing page, I click a link that's defined like this in one of my React Components:
<Link to={`/interviews/companies/${company.id}`}
params={{id: company.id}}
className="ft-company"
ref="link">
{company.name}
</Link>
which initially works fine. I am sent to /interviews/companies/6 for example and it renders my interview component just fine
Here's my route definitions:
const App = Component({
render() {
return (
<Router history={browserHistory} onUpdate={() => window.scrollTo(0, 0)}>
<Route path="/">
<IndexRoute component={HomePage}/>
<Route name="interview" path="interviews/companies/:companyId" component={Interview}/>
</Route>
<Route path="/" component={Container}></Route>
</Router>
);
}
})
You need to add entry in your webserver to serve the index.html for every get html request .
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());
https://github.com/bripkens/connect-history-api-fallback

Resources