Cannot GET "/About" with react-router v4 (Production Help) - reactjs

I've been reading over all the docs for react-router-dom (v4), and tons of Stack Overflow questions with my same error, but A) They leave a lot of unanswered holes and B) They seem to all be suggesting a development only fix, so I'm hoping to see what people are actually doing in PRODUCTION for this simple scenario and C) I'm probably doing something stupid and the answers aren't working for me, with the error "Cannot GET /about" rendering with no errors in the console.
I'm using Express, React, Node and using Webpack for compiling. I can successfully reach my homepage, and clicking any links takes me to the appropriate components, but manually typing in the URL breaks this, as discussed here and the reasons for this error discussed here.
Most answers suggest adding devServer.historyApiFallback = true and output.publicPath = '/' in the webpack.config.js file, which implies I also need to run npm install --save-dev webpack-dev-server and run it using node_modules/.bin/webpack-dev-server as suggested in the docs. Doing all of this, nothing happens. In fact, it's worse now because I also can't access my home route of '/'.
So before dropping my current config here, 1) What can I do to fix this? 2) Does it even matter? The webpack-dev-server is obviously for development only so what about production?
My webpack.config.js file:
var webpack = require('webpack');
var path = require('path');
var envFile = require('node-env-file');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
try {
envFile(path.join(__dirname, 'config/' + process.env.NODE_ENV + '.env'))
} catch (e) {
}
module.exports = {
devServer: {
historyApiFallback: true,
},
entry: [
'script-loader!foundation-sites/dist/js/foundation.min.js',
'./app/app.jsx'
],
plugins: [
new webpack.DefinePlugin({
'process.env': {
//you don't get to see this
}
})
],
output: {
path: __dirname,
filename: './public/bundle.js',
publicPath: '/'
},
resolve: {
modules: [
__dirname,
'node_modules',
'./app/components',
'./app/api'
],
alias: {
app: 'app',
applicationStyles: 'app/styles/app.scss',
actions: 'app/actions/actions.jsx',
configureStore: 'app/store/configureStore.jsx',
reducers: 'app/reducers/reducers.jsx'
),
},
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
loader: 'babel-loader',
query: {
presets: ['react', 'es2015', 'stage-0']
},
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/
},
{
loader: 'url-loader?limit=100000',
test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/
},
{
loader: 'sass-loader',
test: /\.scss$/,
options: {
includePaths: [
path.resolve(__dirname, './node_modules/foundation-sites/scss')
]
}
}
]
},
devtool: process.env.NODE_ENV === 'production' ? false : 'source-map'
};
My app.jsx:
var React = require('react');
var ReactDOM = require('react-dom');
import {Provider} from 'react-redux';
import {BrowserRouter as Router, Route, Switch, Link, HashRouter} from 'react-router-dom';
import Home from 'Home';
import Watch from 'Watch';
import About from 'About';
import AddShow from 'AddShow';
var store = require('configureStore').configure();
import firebase from 'app/firebase/';
// Load Foundation
$(document).foundation();
// App css
require('style-loader!css-loader!sass-loader!applicationStyles');
ReactDOM.render(
<Provider store={store}>
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route path="/watch" component={Watch}/>
<Route path="/about" component={About}/>
<Route path="/addshow" component={AddShow}/>
</div>
</Router>
</Provider>,
document.getElementById('app')
);

You have to set up your web server (the one that serves index.html with the react app) to redirect all requests to the url of your index.html so that react-router can do its job. That's what the suggested change to webpack.config.js is doing for webpack-dev-server
In your webpack.config.js you need to enable the html plugin so webpack knows where your index.html is:
plugins: [
new webpack.DefinePlugin({
'process.env': {
//you don't get to see this
}
}),
new HtmlWebpackPlugin({
template: 'public/index.html' //or wherever your index.html is
})
],

Related

React router dom is not working in the main app

Good morning,
I am having an issue when working with React router dom using my own webpack configuracion. I know using create-react-app will solve (almost) any issues and is friendly to use, but we would like to have more flexibility.
This is my main application:
import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import Login from '../pages/Login'
import { AppContextProvider } from '../context/AppContext'
const MainApp = () => {
return (
<AppContextProvider>
<Router basename={'nynweb'}>
<Switch>
<Route exact path='/' component={Login} />
<Route exact path='/login' component={Login} />
</Switch>
</Router>
</AppContextProvider>
)
}
export default MainApp
As you can see, it´s very simple. If I use localhost:3000/nynweb in the browser, it works perfectly and the router redirects to the login page. Then, If I type localhost:3000/nynweb/login, it should do exactly the same (at list to my understanding), but it doesn´t. I have the Cannot GET /nynweb/login response instead.
Internally the routing is working, though. I mean, when in the components I use the useHistory push method, it redirects properly (history.push('/login')).
What am I missing? Below I am posting my webpack configuration. Maybe the problem lies there:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const BabelRules = {
test: /\.(js)$/,
use: ['babel-loader'],
exclude: /node_modules/,
}
const CSSRules = {
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
}
const ImageRules = {
test: /\.(woff(2)?|ttf|otf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'static/fonts/',
},
},
],
}
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, '/dist'),
filename: 'bundle.js',
publicPath: '/nynweb/',
clean: true,
},
optimization: {
minimize: true,
},
devServer: {
port: 3000,
historyApiFallback: true,
},
module: {
rules: [BabelRules, CSSRules, ImageRules],
},
resolve: {
extensions: ['.js'],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
}
Below I have included a screenshot with the applicacion tree, so that you can see where the static folder is located (all the assets lie there)
As the documentation of Webpack states, the publicPath option allows to serve assets, like images, SVGs etc. What's probably going on is Webpack searching for an asset with the name 'login', but can't find it.
I'd advise you to change this string to your public folder where you store assets (typically, /public).
Also, the example in React-Router documentation adds a forward slash before the basename string.
Clarifications
You set your publicPath variable to /nynweb/, and what Webpack understands is that everything under the '/nynweb/*' path is to be served as an asset.
Change it to, let's say, /nynweb/public/, and now, everything under the '/nynweb/public/*' path is to be served as an asset.
Routes that are not '/nynweb/public/some_route' will now work as expected.

react-router-dom v4 router is not working

router is not working.
localhost:3000/, localhost:3000/#/, localhost:3000/#/aa ==> all moves in home page.
index.js
import ReactDOM from 'react-dom'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
ReactDOM.render(
<BrowserRouter>
<div>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/aa" component={Main}/>
</Switch>
</div>
</BrowserRouter>,
document.getElementById('app')
);
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const lunaRocketModulesPath = path.resolve(__dirname, 'luna-rocket');
module.exports = {
entry: [
'#babel/polyfill',
path.join(__dirname,'src/app','app.js')
],
output: {
path: path.join(__dirname,'build'),
filename: 'index.bundle.js'
},
mode: process.env.NODE_ENV || 'development',
resolve: {
alias: {
},
extensions: [
'.js',
],
},
devServer: {
contentBase: path.join(__dirname,'src')
},
module: {
rules: [
{
// this is so that we can compile any React,
// ES6 and above into normal ES5 syntax
test: /\.(js)$/,
// we do not want anything from node_modules to be compiled
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
},
{
test: /\.(css|scss)$/,
use: [
"style-loader", // creates style nodes from JS strings
"css-loader", // translates CSS into CommonJS
"sass-loader" // compiles Sass to CSS, using Node Sass by default
]
},
{
test: /\.(jpg|jpeg|png|gif|mp3|svg)$/,
loaders: ['file-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname,'src','index.html')
}),
]
};
how to move aa??? please help me.
i don't why router is not working.
and url in # is what is meaning in react??
if localhost:3000/aa--> devServer is working
if localhost:3000/#/aa ==> Home page
(React 16, babel7, wepack4)
I can't really identify was wrong here, but try removing the <Switch></Switch> around the routes and also <Route exact path="/aa" component={Main}/>
without exact this also matches the first route
Actually with /#/aa you are just routing towards / because # specifies an "Anchor", or a position on the page, and allows you to "jump" or "scroll" to that position on the page.
your Main will be available at localhost:3000/aa

React Router not working when URL contains multiple paths

I'm using the webpack-dev-server for development and equally react router. This is my wepack configuration.
const webpack = require('webpack');
const path = require('path');
const common = require('./common.config.js')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = merge(common, {
devtool: 'source-map',
mode:'development',
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
// Adds CSS to the DOM by injecting a `<style>` tag
loader: 'style-loader'
},
{
// Interprets `#import` and `url()` like `import/require()` and will resolve them
loader: 'css-loader'
},
{
// Loader for webpack to process CSS with PostCSS
loader: 'postcss-loader',
options: {
plugins: function () {
return [
require('autoprefixer')
];
}
}
},
{
// Loads a SASS/SCSS file and compiles it to CSS
loader: 'sass-loader'
}
]
}
]
},
devServer:{
contentBase: path.join(__dirname, '../dist'),
port: 3000,
proxy:{
'/api': 'http://127.0.0.1:5000',
},
historyApiFallback:true,
overlay: true,
stats: 'normal'
},
watchOptions: {
ignored: /node_modules/
},
plugins: [
new CleanWebpackPlugin(['../dist'], { exclude:['sw.js']}),
new HtmlWebpackPlugin({
title: 'Script App - Dev',
filename: '../dist/index.html',
template:'index_template.html'
}),
new MiniCssExtractPlugin({
filename: 'bundled_styles.css'
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
});
here is the entry point to my app (where I've defined the routes)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import {BrowserRouter, Route, Switch} from 'react-router-dom'
import configureStore from './store/configureStore';
import FirstComponent from './FirstComponent';
import SecondComponent from './SecondComponent';
import App from './App';
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App>
<Switch>
<Route path="/first/:a_code" component={FirstComponent} />
<Route path="/second" component={SecondComponent} />
</Switch>
</App>
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
I'm using react-router v4 and wepack v4.29.
My problem: When I push routes from my code using the history prop, everything works well but when I refresh my browser with the route, Everything goes blank. This behavior is observed only with the route that has multiple paths(/first/:param).
What I've tried: I read from some SO posts that I had to set the historyApiFallback property in my webpack config to true, and I did but it still did not help.
One of the comments in this github issue says that historyApiFallback will not work well if certain proxy configurations are available in the webpack config.
I don't want to remove these proxy configurations because I'm making api calls to another server running on a different port.
Can anyone help me on this? Please!
I actually missed a webpack configuration. I had to add an output configuration and set the publicPath to /. i.e
output:{
publicPath: '/'
}
I also added the publicPath in the devServer config. i.e
devServer:{
...
publicPath:'/',
historyApiFallback: true
}
Update:
Equally make sure that your bundle.js script is inserted in your html file like this:
<script src="/bundle.js"></script>
AND NOT
<script src="./bundle.js"></script> OR <script src="bundle.js"></script>
I had a similar situation, and I solved using the render: func, that avoids the component to be remounted when the location matches.
So my route look´s like this
<Route
path="/first/:a_code"
render={props =>
<FirstComponent {...props}/>
}
/>
and then in my component I get the params with match
const first_component = (props) => {
return (
<div>
{props.match.params.a_code}
</div>
)
}
at the url "/first/something" I can refresh the browser and still get the desired params, and without any error or blank page. And I don´t believe is a config issue because I didn´t eject the webpack configs in this project so far.

React-router-v4 default route not working with Express backend

I'm using react-router-v4, React 16, and Express 4 for a web application. I use a BrowserRouter component for my main application to render all my routes. All of the component routes and the default route (404) render fine when requesting a single level subdirectory (example.com/asdf), but when one or more subdirectories are being requested (example.com/asdf/zzz), the default route fails to match the request and render, and I'm simply left with a blank page. Here is an example of my app.js with my routes:
import React, { Component } from 'react';
import {
BrowserRouter, Switch, Route
} from 'react-router-dom'
class App extends Component {
constructor() {
super();
}
render() {
return (
<BrowserRouter>
<Switch>
<ScrollToTopRoute exact path="/" component={Cover}/>
<ScrollToTopRoute path="/faq" component={FAQ}/>
<ScrollToTopRoute path="/signup" component={Signup}/>
<ScrollToTopRoute path="/login" component={Login}/>
<Route component={FourOhFour} />
</Switch>
</BrowserRouter>
);
}
}
export default App;
Like I said, I'm using Express for hosting this web application. These are the relevant routes for that.
// Serve up index as React app under build dir
app.use('/', express.static(path.join(__dirname, 'build')));
// Handles all other react routes so no 404 errors
app.get('/*', function (request, response){
response.sendFile(path.join(__dirname, 'build/index.html'));
});
If I console.log inside of the app.get route, on nested subdirectories like example.com/asdf/zzz, it seems to loop 4-5 times before finally ending on a blank screen. I've tried removing all other routes besides the default route in app.js, and the results are the same, it works fine until nested subdirectories.
EDIT: Someone asked for my webpack config, so here it is:
// webpack.config.js
const HtmlPlugin = require('html-webpack-plugin');
const path = require('path');
const webpack = require('webpack');
module.exports = {
// Tell webpack to start bundling our app at src/index.js
entry: [
'./src/index.js'
],
// Output our app to the dist/ directory
output: {
filename: 'app.js',
path: path.resolve(__dirname, "build")
},
// Emit source maps so we can debug our code in the browser
devtool: 'source-map',
// Tell webpack to run our source code through Babel
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.css$/,
loader: [ 'style-loader', 'css-loader' ]
},
{
test: /\.(png|jpg|gif|svg|ico)$/,
loader: 'file-loader?name=./public/img/[hash].[ext]'
},
{
test: /\.(json)$/,
loader: 'file-loader?name=./public/assets/[hash].[ext]'
},
{
test: /\.(eot|ttf|woff|woff2)$/,
loader: 'file-loader?name=./public/fonts/[hash].[ext]'
},
{
test: /\.(html)$/,
options: {
attrs: ['link:href']
},
loader: 'html-loader'
}]
},
// Since Webpack only understands JavaScript, we need to
// add a plugin to tell it how to handle html files.
plugins: [
// Configure HtmlPlugin to use our own index.html file
// as a template.
// Check out https://github.com/jantimon/html-webpack-plugin
// for the full list of options.
new HtmlPlugin({
template: './public/index.html'
}),
new webpack.EnvironmentPlugin(['NODE_ENV'])
]
}
EDIT: Some more information. It seems like the Express routes are working, and rendering all non-existant routes using response.sendFile, but the default route appears to stop working when using nested subdirectories.

React router + webpack, sub routes not working

I am trying to set up my routers for my app, and have the basic / entry point working (seemingly). It seems when I try to start adding sub routes, it is breaking. I have a pretty straight forward set up right now. I am using react + redux and my render looks like :
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory} >
<Route path="/" component={comp1.Component}>
<Route path="test" component={comp2.Component} />
</Route>
</Router>
</Provider>,
// eslint-disable-next-line no-undef
document.getElementById('app')
);
I am running webpack dev server on localhost:8080, and it serves the first route with no problem, however when I go to localhost:8080/test, I am getting a Cannot GET /test .
Here is my webpack config:
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool: 'eval',
entry: [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./client/app.jsx'
],
output: {
path: path.join(__dirname, ''),
filename: 'bundle.js',
publicPath: '/'
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
],
module: {
loaders: [{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: "babel-loader",
include: __dirname,
query: {
presets: [ 'es2015', 'react', 'react-hmre' ]
}
}]
}
}
Unsure what I am doing wrong here, would be grateful for any help. Thanks!
React Router uses the HTML5 history API. This means that 404 responses need to serve /index.html.
The docs mention how this works. You need to add this to your module.exports object:
devServer: {
historyApiFallback: true
}
Note that this only works for the CLI, when using the Node.js API you need to add this as a second parameter:
var server = new WebpackDevServer(compiler, { historyApiFallback: true });

Resources