Serving react over static server without having people running into 404 - node.js

I have a react site serving over: https://aero.mysite.com/profile, and I use router and history.push/replace in my react app, making it possible to have pathname such as https://aero.mysite.com/profile/path?query=number. However, say if someone copy and paste this url to another person, he or she would get 404 because https://aero.mysite.com/profile/path doesn't actually exist over the static server... (I am using koa + file serving middleware I made). What are the solutions to this challenge?

If you are using BrowserRouter or something similar, replace it with hash router which adds a "#" between the web server path and frontend route
Browser router looks like this
http://example.com/about
while the hash router looks like this
http://example.com/#/about
This will prevent the UI routing from being processed by the web server
For more info read this article
import { HashRouter as Router, Route } from 'react-router-dom';
import App from './components/App';
import Home from './components/Home';
import About from './components/About';
import Services from './components/Services';
render((
<Router>
<div>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/courses" component={Services} />
</div>
</Router>
), document.getElementById('root'));

Related

Why cant my React App handle links to one of its routes?

Edit: For anyone coming here from Google, here is a TLDR: The reason for this "issue" is that React uses client side rendering. A quick solution is to use the HashRouter component, an SEO friendly solution is to use server side rendering(SSR). I switched to using NextJS, an SSR React framework, and my concern is resolved. Pages refresh like normal, and favorites work as intended. Thanks to all in the comments.
Original Question:
I've tried to find the answer to this but maybe i'm not googling well enough. So I have a react app with multiple routes, one of the routes is /reset, I have routing set up in the App.tsx like so:
import { BrowserRouter as Router, Link, Route, Switch} from 'react-router-dom';
class App extends Component<IAppProps, IAppState> {
constructor(props: IAppProps) {
super(props);
this.state = {
loggedin: "false",
user: {
username: "",
email: ""
}
};
}
<Router>
<nav className="topnav">
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About Us</Link></li>
<li><Link to="/faq">FAQ</Link></li>
<li><Link to="/login">Login</Link></li>
</ul>
</nav>
<Switch>
<Route exact path="/">
<Home
text="This text was passed in as a prop"
/>
</Route>
<Route path="/login">
<Login
changeHandler={this.updateLoginInput}
/>
</Route>
<Route path="/register">
<Register
formSubmit={null}
/>
</Route>
<Route path="/forgotpassword">
<ForgotPassword
stateSetter={this.handleChange}
/>
</Route>
<Route path="/reset">
<Home/>
</Route>
</Route>
</Switch>
</Router>
Sorry for the bad indenting, the editor doesn't like tabs.
The links work fine but, when ever i manually type in the address bar localhost:3000/reset it just keeps spinning and eventually times out. Same with every other route in fact. I need this to work because im sending a password reset email that will contain a link to use, and currently it just keeps spinning when the link is clicked. What if someone were to favorite a route on my page like mysite.com/page1? It would keep spinning and then eventually time out.
I've tried to add a route on the server side with app.get() and a response.redirect() and redirect to the reset route, but Chrome blocked it as an unsafe redirect.
Does this functionality not work on localhost or is there something else i'm doing wrong?

Express not routing javascript files when URL has multiple directories

I'm setting up a server backend in express for a React application which is using React-Router v4. So far, I've set up Express so that any request is redirected to index.html, allowing React Router to do the rest of the work. This works for the routes '/beats', '/loops', '/kits', etc. The problem arises when there are multiple directories in the url, such as with the url '/beats/1/name-of-beat' (which is parsed as '/beats/:id/:name?' in react-router).
Using this express code:
app.use(express.static(path.join(__dirname, '../frontend/build')));
app.get('/*', (req, res) => {
res.sendFile(path.join(__dirname, '../frontend/build/index.html'));
});
This will redirect all requests to /kit, /beats, and the like to index.html, and all the <script> sources will still work. However, when trying to reach '/beats/1/name', index.html is served, but the <script> tags are also served the index.html, resulting in index.html loading in the browser but none of the javascript.
This happens if I add any other directories to the url in addition to the first, so '/beats/example' or '/beats/1/2/3' but not for '/example' (which would correctly show the 404 configured in react-router).
If it's needed, here's the router code:
<Router>
<div className="App">
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/kits" component={Kits} />
<Route exact path="/beats" component={Beats} />
<Route path="/beats/:id/:beatName?" component={SoundPage} />
<Route path="/loops" component={Loops} />
<Route path="/contact" component={Contact} />
<Route component={NoMatch} />
</Switch>
</div>
</Router>
Here's the built index.html script tags:
<script src="./static/js/1.731b4af1.chunk.js"></script>
<script src="./static/js/main.498cadba.chunk.js"></script>
And here's the project structure:
-frontend
-build
-static
-index.html
-src
-public
-backend
-server.js
Long story short, your nested routes will need to use the match property. Your routes will be nested in a drop-down structure, where each successive Route will be included within the next matching Component's render method. Watch this video to understand what I mean:
React Router V4 Nested Routes
An alternative to using dynamic nested routes, would be to use a query parameter structure, where /beats/:query would be /beat/find?id=12345&beatName="Example Beat", which you can find an example here:
React Router V4 Nested Routes Match Params Not Accessible At Root Level
The first example is more common in forums (like Reddit), while the second example is more common in web store-fronts (like Amazon).

React routing with express is working on page refresh

My project using express for server and React for frontEnd. Routes are like this
<Router >
<Switch>
<Route exact path="/" component={HomeContainer} />
<Route path="/women" component={SectionContainer} />
</Switch>
</Router>
To serve these routes my server js has
server.get('*', function(request, response) {
response.sendFile(path.resolve(__dirname, '../public', 'index.html'));
});
Page url http://localhost:3000/women is working only on page refresh, first time click on url is just changing the browser url with no page update. But on page refresh it is working perfectly fine.
Please suggest what i am missing.
I was having similar issue. I found HashRouter helpful than the BrowserRouter:
import { HashRouter as Router } from 'react-router-dom'
Using HasRouter will be working fine as it keeps state on every history data changes.

Providing a React Router component with state variables after being redirected from the server (using Redux for state-handling)

I am building a client application using React and Redux, with server-side API endpoints set-up using Node.
For one feature I would like to send out a token in an email, then upon clicking a link (something like website.com/token?email=dave.example#something.com&token=3ad56gyhg) verify their token/email using a server-side API, before redirecting them to a particular page in React (using React Router).
I anticipate the Node API would look something like this:
app.get('/token', (req, res, next) => {
//code here.....
//goes and checks the email and token code match what is in the database
if (success) {
res.redirect('/welcome');
}
}
Once I've redirected to the appropriate React router endpoint, how do I provide state/props relevant to the user to any components? For example, I might want to use their email address on a page once their token has been verified.
ReactDOM.render(
<Provider store={store}>
<Router history={hashHistory}>
<Route component={App}>
<Route path="/" component={EntryPoint} />
<Route path="/welcome" component={WelcomeContainer} />
</Route>
</Router>
</Provider>,
document.getElementById('root')
);
Would I have to go down the isomorphic route and create a store on the server? Would the component need to go back and get an 'initial state' from the server?
You have a static HTML/CSS/JS server and you have a Node API. In this case, you can't 'template' the HTML that you send to the client. That means that you can only pass data to your react application via URL params.
app.get('/token', (req, res, next) => {
//code here.....
//goes and checks the email and token code match what is in the database
if (success) {
res.redirect(`/welcome/${encodeURIComponent(email)}`);
}
}
Then when your component loads, check for the query param:
ReactDOM.render(
<Provider store={store}>
<Router history={hashHistory}>
<Route component={App}>
<Route path="/" component={EntryPoint} />
<Route path="/welcome/:email" component={WelcomeContainer} />
</Route>
</Router>
</Provider>,
document.getElementById('root')
);
Alternative:
/token redirects to your webapp.
Your react application now picks up the email & token params and then makes an API request to /verify/token with the email & token parameters.
Your application handles the API request (which returns a success/fail) and then redirects internally to /welcome.
This is the way I've usually done this. The key is to make sure that when the user clicks on the verify link, they are taken directly to the webapp. The webapp does the API verification business.

react + routing + security

I am building a web application with React and react-router and I would like to protect some routes of my React application with an existing external access management infrastructure (OpenAM).
I would like to protect the http://web.example.com:8080/myapp/#/user-profile url which means that only logged users can have access to this route.
I have some other routes which are public and any user can open them, for example http://web.example.com:8080/myapp/#/welcome.
The Access Management protects urls and if a user wants to open a protected url then a login form is shown by Access Management and after a successful login the original requested url will be displayed.
The problem here is that react-router adds the routing info after the '#' character and the above mentioned two different urls are same from the access manager point of view because they refer to the same web resource (/myapp). The different between these two urls appear after the '#' character.
I need to have real urls without '#' chars like this:
http://web.example.com:8080/myapp/user-profile
http://web.example.com:8080/myapp/welcome
Is there any way to use real url mappings with React?
Do you guys have any idea or workaround how to use real url routes with react?
Thanks.
UPDATE:
This is my code. The urls in the web browser look nice but I get a "about did not match any routes" error. Requested url: http://web.example.com:8080/myapp/about
import {Router, Route, IndexRoute, useRouterHistory} from 'react-router';
import {createHistory} from 'history';
const browserHistory = useRouterHistory(createHistory) ({
basename: '/myapp/'
});
ReactDOM.render(
<Router history={browserHistory}>
<Route path="/" component={MainLayout}>
<IndexRoute component={Home} />
<Route path="about" component={About} />
</Route>
</Router>
)
You could use browserHistory, as suggested in the comments, to get rid of the #-sign. If you want the root of your react-application to be /myapp/ instead of / you can try:
import {Router, Route, useRouterHistory} from 'react-router';
import {createHistory} from 'history';
const browserHistory = useRouterHistory(createHistory)({basename: '/myapp/'});
ReactDOM.render(
<Router history={browserHistory}>
...
</Router>
)

Resources