React Router v4 URL issue - node.js

Ours react app works in dev mode (localhost) perfectly, but when we put it onto the Heroku server we cannot access routes directly. When we do history.push({ }) to our path we get a 404 page.
You can see the page here: placemate.herokuapp.com
Steps to reproduce: go to the above URL, see it load. Then add /login to the URL bar and you will see that a 404 page appears. However, clicking a login from the header will direct you properly.
Here is our router code:
import React from 'react'
import { Switch, Route, Redirect } from 'react-router-dom'
import '../../App.css'
import Home from '../../scenes/general/home/Home'
import Login from '../../scenes/general/login/Login'
import Register from '../../scenes/general/register/Register'
import GuestHeader from '../../components/general/header/Header'
const Main = () => (
<main className='main_app'>
<GuestHeader />
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/login' component={Login}/>
<Route path='/register' component={Register}/>
</Switch>
</main>
);
export default Main;
index.js file:
ReactDOM.render(<BrowserRouter><App /></BrowserRouter>, document.getElementById('root'));
registerServiceWorker();

Why 404 Not Found
This is because when you put up any path in your address bar its request is sent to the web server not react. As you know the react-router we are using is responsible to display the correct component based on the route, but since you pass the route to nginx / apache server not react-router; hence your web server tries to find www.example.com/login which actually does not exist. Hence you get 404 not found.
Solution
Try to create a .htaccess file on your web server public HTML root directory. Create some rules so that the request is always forwarded from the root path. This will let the react come into the picture, hence the react-router can grab the path and render the appropriate component.
For example, you can create something like this
For Apache server
RewriteEngine On
# If an existing asset or directory is requested go to it as it is
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
#RewriteCond %{SERVER_PORT} 80
#RewriteRule ^(.*)$ https://www.example.com/$1 [R,L]
RewriteRule ^ - [L]
# If the requested resource doesn't exist the serve index.html
RewriteRule ^ ./index.html
For Node.js
Sample code for node.js express server can be like this (present in file for server implementation, conventionally named as server.js)
const express = require('express');
const path = require('path');
const app = express();
if(process.env.NODE_ENV!== 'production'){
//do something if you are in development environment
//below lines only if you are using webpack based deployment
const webpackMiddleware = require('webpack-dev-middleware');
const webpack = require('webpack');
const webpackConfig = require('./webpack.config.js');
app.use(webpackMiddleware(webpack(webpackConfig)));
}
else{
// in production env do following thing
app.use(express.static('public_html_folder'));//give user access to public assets
//redirect all request to index.html for compatibality with react router
app.get('*',(req,res)=>{
res.sendFile(path.join(__dirname,'public_html_folder/index.html'));
});
}
app.listen(process.env.PORT || 3050, () => console.log('Listening'));
I hope this solves your problem or at least give you an idea of what to do if there is a different backend server.

Related

Stop statically served React app from calling server path

My app has the default homepage at "/", and a contact page at "/contact". When I ran the react server(localhost:3000) and express server(localhost:8000) separately, the navigation between these pages works fine as handled by "react-router-dom" below.
Frontend React, Routing.tsx:
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Contact from "../pages/Contact/Contact";
import Main from "../pages/Main/Main";
import Error from "../pages/Error/Error";
function Routing() {
return (
<BrowserRouter>
<Switch>
<Route path="/error" component={Error} />
<Route path="/contact" component={Contact} />
<Route path="/" component={Main} />
</Switch>
</BrowserRouter>
);
}
export default Routing;
Now I built the react app using npm run build, and placed the "build" folder inside my backend express server to be served, as per deployment doc here. Then I ran "npm run dev" to start the server.
However, whenever I try to navigate to the /contact page, it issues a server call to "localhost:8000/contact" instead of being handled by the frontend routing. Of course the server doesn't have that route, and all my server routes are prefaced with "/api/" anyway.
How can we prevent frontend navigation from calling the server routes?
More code below, thanks.
Backend Express, App.ts:
import express from "express";
import path from "path";
class App {
private _app: express.Application;
private readonly _port: number | string = process.env.PORT || 8000;
constructor(controllers: any[]) {
this._app = express();
this.initializeControllers(controllers);
this.initializeMiddleWares();
this.initHostingReactUI();
}
public start() {
this._app.listen(this._port, () => {
console.log(`App listening on the port ${this._port}`);
});
}
private initializeControllers(controllers: any[]) {
controllers.forEach((controller) => {
this._app.use("/api", controller.router);
});
}
public initializeMiddleWares() {
require("./src/middleware/express.middleware")(this._app);
}
public initHostingReactUI() {
// I am aware that you can do "/*" below to catch all routes, but that doesn't solve my issue as it still calls the backend for every non-api routes that should be handled by frontend routing.
this._app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "build", "index.html"));
});
}
}
export default App;
Backend Folder structure with Build folder:
If needed:
Backend github source.
Frontend github source
There are two ways to combine the routes between the frontend and backend:
If access from http://localhost:3000
You need to boot both frontend and backend.
In this case, all your backend request urls will start with http://localhost:3000 (if you didn't specify a base url). To solve this issue, add a line in the root of your client-side package.json file:
{
"proxy": "http://localhost:8000"
}
After that, frontend will redirect all the unknown routes to backend.
If access from http://localhost:8000
You only need to boot the backend.
Since React application is one-page application, which means there is only one html entry (index.html). Now we need a route to serve the frontend routes in the backend:
// place this in the end after the other routes
app.get('*', (req, res) =>
res.sendFile(path.join(__dirname, "build", "index.html"));
})
The routing system will check if the route exists among the backend routes. If not, then serve index.html and check if the route exists among the frontend routes. Therefore, in the frontend you should guard the urls by returning a 404 page when not found.
Do you mean navigate through the app? if not, navigating to the page by the URL or refreshing the page always will send a request to the server, which should return the app index.js.
All you need to do is to place the last route which is serving react (You can use app.get('*', .....) or placingapp.use((req, res) => {...}) without route).
In this case, when the request came to the server, the server will search for the route top-to-bottom, if the requested route is not api then it's will serve the client-side app.

How to configure Node.js in Web hosting?

I made a website with create-react-app with a contact form that communicates with the backend (nodejs with nodemailer). In localhost works perfectly.
When I uploaded the website in a web hosting (wnpower.com/web-hosting is the hosting I bought) that supports nodejs apps, I can't use the contact form because I get "net::ERR_CONNECTION_TIMED_OUT" in the path "https://mywebsite.com/api/sendmessage". It seems the frontend can't find the backend router or something that I can't understand.
In the CPanel of the web hosting, in the terminal I installed Nodejs and ran a test app, works perfectly. But when I want to use node app across the frontend, doesn't work.
My configuration in the node app.js file:
require("./config"); // all process.env
const express = require("express");
const path = require('path');
const app = express();
const bodyParser = require("body-parser");
// ALL ROUTES
const contact_routes = require('./routes');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.listen(process.env.PORT, () => {
console.log("Server listening at port "+process.env.PORT);
});
// Configure Header HTTP
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Authorization, X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Allow-Request-Method"
);
res.header("Access-Control-Allow-Methods", "GET, POST");
res.header("Allow", "GET, POST");
next();
});
app.use(express.static(path.join(__dirname, '../public_html')));
// CONNECT ROUTES WITH API
app.use(process.env.API_URL, contact_routes);
module.exports = {
app
};
public_html directory are the static files that I built with the command npm run-script build and the __dirname is the server folder. So:
Directory:
public_html -> static files frontend.
server -> node app, routes, controllers, etc.
And in the config.js file there is the process.env.PORT and the port is 3050.
In the routes.js:
var express = require('express');
var controller = require('./controller');
var router = express.Router();
router.post('/sendmessage', controller.sendMessage);
module.exports = router;
in the .htaccess file:
DirectoryIndex ""
RewriteEngine On
RewriteCond %{REQUEST_URI} ^.*/index.*
RewriteRule ^(.*)$ http://127.0.0.1:3050/ [P,L]
RewriteRule ^$ http://127.0.0.1:3050/ [P,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ http://127.0.0.1:3050/$1 [P,L]
I can't understand well, I have no experience in that.
My idea is
Run the node app with nohup in the terminal of cpanel so the Node app will always running. Checked!
Try the contact form in the frontend website. Fail! I get timeout connection and I get nothing.
In the node app.js I want to see the console.log() in the terminal cpanel when I use the contact form. It's for test and know thats all is ok, but I can't see anything in the terminal. How can I do that?
If any information is missing, tell me I'll share the code. I need to resolve this as soon as possible. Thank you for reading and sorry for my English.
Try using
https://mywebsite.com:3050/
instead of http://127.0.0.1:3050/
at all places in your .htaaccess file
You can get logs of nohup from tail command in its log file.

React build for static server gives 404 error

I have a React app which runs fine locally but when I try to build it on the static Node server serve (npm serve) using
serve -s build
the server returns a 404 The requested path could not be found page. My understanding is the issue has to do with the fact I am using BrowserRouter in the application. I put a .htaccess file in the root directory with the following code but it did not solve the issue.
<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>
One suggestion I saw for a Node environment follows but I don't understand where to put the code.
app.get('*', function (req, res) {
res.send('index_path')
})
My index.js file is as follows.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { Route, Switch, BrowserRouter as Router } from 'react-router-dom';
import App from './App';
import Service from './javascript/Service';
import { PageNotFound } from './PageNotFound';
import * as serviceWorker from './serviceWorker';
const routing = (
<Router>
<Switch>
<Route exact path="/" component={App} />
<Route path="/Service/:serviceName" component={Service} />
<Route component={PageNotFound} />
</Switch>
</Router>
);
ReactDOM.render(routing, document.getElementById('root'));
serviceWorker.unregister();
you should first build your project with
npm run build (or yarn)
next install serve
npm install -g serve
and then run this code
serve -s build

How to serve both react application and api from express server?

So my node code looks like this (simplified):
const express = require('express');
const app = express();
const routers = require('./routers');
app.use(express.static(`${__dirname}./../dist/`)); // serving index.html
app.use('/api', routers.api);
app.listen(80, () =>
console.log(`listening on port 80`)
);
and let's say my react router has a /fun/:param route and a link to the /fun/foo page.
If I click the link, I end up on mypage.com/fun/foo, but if I refresh I get Cannot GET /fun/foo.
Now I understand that what's actually going on is I shoot a request to my express server and it tries to find the /fun/foo route, fails and returns Cannot GET but how do I make it go to the SPA's /fun/:param-ruote?
I found this link and tried to add the following to the bottom:
app.get('/*', (req, res) => {
res.sendFile(path.join(`${__dirname}./../dist/index.html`));
});
If I log incoming requests to the server I get the following when refreshing /fun/foo:
a connection is made for /fun/foo
a connection is made for /dist/main.js
a connection is made for /fun/main.js
and the client shows nothing, the console logs two:
Uncaught SyntaxError: Unexpected token < main.js:1
EDIT:
Turns out the main.js that is delivered when refreshing the site IS the index.html??? (the main.js file on the server is an actual js-file), how does this even happen!?!?!
New info: Folder structure and relative paths
server
index.js
src
App.js
index.js
public -- only used for devServer(?)
index.html
favicon.ico
dist -- what's used in production(?)
index.html
main
main.js
main.js.map
The project is built with webpack
index.html references main.js with:
<script src="../dist/main.js"></script>
Oh, and if I change the reference to main.js in index.html to:
<script src="./main.js"></script>
I get:
Invariant Violation: Minified React error #321; visit
https://reactjs.org/docs/error-decoder.html?invariant=321 for the full
message or use the non-minified dev environment for full errors and
additional helpful warnings.

Page not found "/" route

I'm trying to setup a VueJS app using Nuxt for server side rendering. However after deployment to my server the index.vue is return a 404 Page not found error.
This doesn't happen when run on my development machine, and still happens even if run in Development mode on my server.
All other routes work, and getting to the index route from within the app itself works fine. It just doesn't load when refreshed. Eg:
http://myapp.com/ Doesn't work
http://myapp.com/login Works fine
My pages folder currently looks like this:
- pages
-- index.vue
-- login.vue
I have nothing fancy set up within my nuxt.config file and have pretty much the same setup as is described in the Auth0 example
On my server I'm running nuxt with the command pm2 start npm --name "my-app" -- start Which runs fine without any errors, and I have the following .htaccess config:
RewriteCond %{HTTP_HOST} ^my-app\.com$ [OR]
RewriteRule ^(.*) "http\:\/\/127\.0\.0\.1\:3000\/$1" [P,L]
This is the error screen I get:
The page itself is very basic at the moment, it did have far more on it however I've cut it down to the following trying to debug this issue:
<template>
<v-container grid-list-md>
<v-layout row wrap>
<h1>Test index, trying to fix the 404</h1>
<h2>Hello, {{ loggedUser ? loggedUser : 'friend' }}!</h2>
</v-layout>
</v-container>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: mapGetters([
'isAuthenticated',
'loggedUser'
])
}
</script>
I seem to have managed to fix this. However, I'm not completely sure on the cause the fix involved modifying my .htaccess. I started playing with the settings and ended up with the following which works well (for now at least).
RewriteCond %{HTTP_HOST} ^my-app\.com$
RewriteRule "(.*\.(jpg|gif|png|svg|js|css))$" "http://127.0.0.1:3000/$1" [P,NC]
RewriteRule ^(.*) "http\:\/\/127\.0\.0\.1\:3000\/$2" [P,L]
The rule for jpg|gif etc is required as they don't load when using the second rule.

Resources