localhost:port/ prints index.js file - node.js

localhost:9400/ always prints my index.js file in the browser. All the urls after / are working as expected. I also want to know if this is server-side rendering or client-side since I am sending bundle.js as script file in index.html.
index.js:
/ import React from "react"
// import { renderToString } from "react-dom/server"
// import { StaticRouter, matchPath } from "react-router-dom"
// import serialize from "serialize-javascript"
// import App from '../shared/App'
// import routes from '../shared/routes'
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
process.env.PORT = process.env.PORT || 9400;
process.env.MONGO_HOST = process.env.MONGO_HOST || '192.168.1.81';
process.env.JWT_EXPIRE = process.env.JWT_EXPIRE || 200;
var _ = require('lodash');
var path = require('path');
var express = require('express');
var config = require('core-npm').config;
var db = require('core-npm').mongoose;
var app = express();
config.root = path.normalize(__dirname + '/..');
// expose the function to start the server instance
app.startServer = startServer;
app.serverShutdown = serverShutdown;
// Setup Express
require('core-npm').express(app);
var coreNPM = require('core-npm');
/////configure routes for frontend UI routing////////
// app.use('/api/users', coreNPM.userRoutes());
// Setup Routes
require('../../server/routes')(app);
// register the shutdown handler to close the database connection on interrupt signals
process
.on('SIGINT', serverShutdown)
.on('SIGTERM', serverShutdown);
/**
* Create an express http server and return it
* #api private
* #return
*/
function startServer() {
var server = require('http').createServer(app);
return server;
}
var coreNPM = require('core-npm');
// start sockets for this instance and start server
app.startServer().listen(app.get('port'), app.get('ip'), function serverStarted() {
console.log('OPRS App started server on ip %s on port %d, in %s mode',
app.get('ip'), app.get('port'), app.get('env'));
});
/**
* Shutdown handler
* Closes the database connection on iterrupt and exits the process
* #api private
*/
////enable script if mongodb connection establishment in environment
function serverShutdown() {
db.connection.close(function connectionClose() {
console.log('Database connection disconnected through app termination');
process.exit(0);
});
}
routes.js in the express server:
use strict';
console.log("routes executed");
var path = require('path');
var coreNPM = require('core-npm');
var middleware = coreNPM.middleware;
module.exports = function (app) {
// extend response with custom methods
app.use(middleware.extendResponse);
// default CUD middleware
app
.put(middleware.removeReservedSchemaKeywords)
.patch(middleware.removeReservedSchemaKeywords)
.delete(middleware.removeReservedSchemaKeywords)
.post(middleware.removeReservedSchemaKeywords);
// Insert routes below
app.use('/api/users', coreNPM.userRoutes());
app.use('/auth', coreNPM.auth);
// All undefined asset or api routes should return a 404
app.route('/:url(api|auth|components|app|bower_components|assets)/*')
.get(function invalidRoute(req, res) { return res.notFound(); });
// All other routes should redirect to the index.html
app.route('/*')
.get(function getIndexFile(req, res) {
console.log("app path from router ============================"+app.get('appPath'));
res.sendFile('/home/pavan/Downloads/rrssr-master/src/server/index.html');
});
// register the default error handler
app.use(middleware.defaultErrorHandler);
};
Another routes.js in my src:
import Home from './components/Home'
import SignIn from './components/SignIn';
const routes = [
{
path: '/home',
exact: true,
component: Home,
},
{
path: '/login',
exact: true,
component: SignIn
},
{
path: '/',
exact: true,
component: SignIn
}
]
export default routes
webpack.config.js:
var path = require('path')
var webpack = require('webpack')
var nodeExternals = require('webpack-node-externals')
var browserConfig = {
entry: './src/browser/index.js',
output: {
path: path.resolve(__dirname, 'public'),
filename: 'bundle.js',
publicPath: '/'
},
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
]
},
plugins: [
new webpack.DefinePlugin({
__isBrowser__: "true"
})
]
}
var serverConfig = {
entry: './src/server/index.js',
target: 'node',
externals: [nodeExternals()],
output: {
path: __dirname,
filename: 'server.js',
},
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
{
test : /\.css$/,
use : [
{
loader : 'style-loader'
}
]
},
{
test : /\.css$/,
use : [
{
loader : 'css-loader'
}
]
}
]
},
plugins: [
new webpack.DefinePlugin({
__isBrowser__: "false"
})
]
}
module.exports = [browserConfig, serverConfig]
And finally my index.html file
index.html:
<html>
<head>
<title>SSR with RR</title>
<script src="/bundle.js" defer></script>
</head>
<body>
<div>
<div id="app"></div>
</div>
</body>
</html>

Related

Nuxt.js + Express + Heroku — Axios requests return 404 in production, but not on localhost in dev mode — any solution?

I've made a cms for blogging using nuxt.js with express for the server. It uses axios to make asyncData requests to the server before loading each page.
It works fine when I run it locally, but when I deploy the site on heroku the axios requests all return 404.
My guess is that the axios requests baseURL is wrong somehow, but when I haven't been able to confirm that. I've confused myself.
I've tried setting the baseURL to the url of the site, I've tried setting it to http://0.0.0.0:8000, which is mentioned in the nuxt.js docs
I've tried including the modeule #nuxtjs/dotenv with require('dotenv').config() at the top of my nuxt.config.js file.
My nuxt.config.js file
module.exports = {
mode: 'universal',
head: {
title:'',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content:'' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.png' }
]
},
loading: { color: '#fff' },
css: [
{ src: '~assets/stylesheets/global.scss', lang: 'scss' }
],
plugins: [
],
/*
** Nuxt.js modules
*/
modules: [
'#nuxtjs/axios',
'#nuxtjs/eslint-module'
],
axios: {
},
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {
// Run ESLint on save
if (ctx.isDev && ctx.isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/,
options : {
fix : true,
extractCSS : true
}
})
}
}
}
}
my server/index.js file
const express = require('express')
const session = require('express-session')
const consola = require('consola')
const passport = require('passport')
const mongoose = require("mongoose")
const bodyParser = require("body-parser")
const cookieParser = require('cookie-parser')
const morgan = require('morgan')
const { Nuxt, Builder } = require('nuxt')
const app = express()
// Import and Set Nuxt.js options
let config = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
//Body Parser / Cookie Parser config
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser())
app.use(morgan('dev'));
// Expresss Session
app.use(session({
secret: 'lkasjdlmsdj18ka124jlsdalkjmlakjc',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 60000 }
}))
app.use(passport.initialize())
app.use(passport.session())
mongoose.connect(process.env.MONGODB_URI);
require('./auth_config')(passport)
require('./auth')(app,passport)
//Posts
require('./posts.js')(app, mongoose)
async function start() {
// Init Nuxt.js
const nuxt = new Nuxt(config)
const { host, port } = nuxt.options.server
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
} else {
await nuxt.ready()
}
// Give nuxt middleware to express
app.use(nuxt.render)
// Listen the server
app.listen(port, host)
consola.ready({
message: `Server listening on http://${host}:${port}`,
badge: true
})
}
start()
An example of one of the axios routes that queries the database.
app.get("/api/posts/latest-post", async (req, res) => {
try {
var result = await PostModel.findOne({published:true}).sort({"date.published.iso": -1}).exec();
res.send(result);
} catch (error) {
res.status(500).send(error);
}
});
The example here should return the latest post, instead I get an nuxt error message "server error." The same is true for every route that uses axios to request data from the server.
It works exacltly as expected on local host.
Any help would be really appreciated.
You can create a new instance of axios with a custom config, like this:
axios.create([config]) ->this is the sample
//code starts
const instance = axios.create({
baseURL: 'https://www.abcdxyz.com/api/',// as you used http://0.0.0.0:8000
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'} // if you like to send details else remove it
});
//code ends
You can also use Axios inline but I prefer an extra utils folder and then a new file named 'axiosconfig' and just import it... helps in the MVC
I hope my answer was helpful to you!

Two Webpack Configs (Client, Server) Express App not hosting HTML files

I am building an Application that has a Node Backend that I am trying to bundle with Webpack.
At first I had one Webpack configuration that had target: node. I was unable to compile Websockets into the frontend bundle unless I changed it to target: web but that wasn't compiling my backend code changes. I would have to run tsc && webpack.
I am now to two configs to compile them separately. My current config is:
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
var fs = require("fs");
var nodeModules = {};
fs.readdirSync("node_modules")
.filter(function(x) {
return [".bin"].indexOf(x) === -1;
})
.forEach(function(mod) {
nodeModules[mod] = "commonjs " + mod;
});
const common = {
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/
},
{
test: /\.html$/,
use: [{ loader: "html-loader" }]
}
]
},
resolve: {
extensions: [".tsx", ".ts", ".js"]
}
};
const frontend = {
entry: "./src/index.ts",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "build"),
publicPath: "/"
},
target: "web",
plugins: [
new CopyWebpackPlugin([
{
from: path.resolve(
"node_modules/#webcomponents/webcomponentsjs/webcomponents-bundle.js"
),
to: path.resolve(__dirname, "build/vendor")
},
{
from: path.resolve(
"node_modules/#webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"
),
to: path.resolve(__dirname, "build/vendor")
}
]),
new HtmlWebpackPlugin({
title: "Flop The World Poker",
template: "build/index.template.html"
})
]
};
const backend = {
entry: "./src/server.ts",
output: {
filename: "server.js",
path: path.resolve(__dirname, "build"),
publicPath: "/"
},
target: "node",
externals: nodeModules
};
module.exports = [
Object.assign({}, common, frontend),
Object.assign({}, common, backend)
];
Before I switch to two configs I was able to host my index.html that is in my output folder using:
import bodyParser from "body-parser";
import express from "express";
import { createServer } from "http";
import { listen } from "socket.io";
import { DeckController} from "./controllers";
const app: express.Application = express();
const port: number = ((process.env.PORT as any) as number) || 3000;
const server = createServer(app);
const io = listen(server);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static("static"));
app.use("/deck", DeckController);
app.use(express.static(__dirname, { extensions: ["html"] }));
server.listen(port, () => {
console.log(`Listening at http://localhost:${port}/`);
});
io.on("connection", socket => {
console.log("Client connected..");
socket.on("join", data => {
console.log(data);
});
});
I am now receiving cannot get /. I can set up a route like
router.get("/", (req: Request, res: Response) => {
res.send("Hello World");
});
and receive Hello World in the browser.
Can anyone help me to figure out what changed that now I cannot host my html file?
I fixed this with help from Robert in the comments. Here are my new files:
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
var fs = require("fs");
var nodeModules = {};
fs.readdirSync("node_modules")
.filter(function(x) {
return [".bin"].indexOf(x) === -1;
})
.forEach(function(mod) {
nodeModules[mod] = "commonjs " + mod;
});
const common = {
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/
}
]
},
resolve: {
extensions: [".tsx", ".ts", ".js"]
}
};
const frontend = {
entry: "./src/index.ts",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "build"),
publicPath: "/"
},
target: "web",
plugins: [
new CopyWebpackPlugin([
{
from: path.resolve(
"node_modules/#webcomponents/webcomponentsjs/webcomponents-bundle.js"
),
to: path.resolve(__dirname, "build/vendor")
},
{
from: path.resolve(
"node_modules/#webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"
),
to: path.resolve(__dirname, "build/vendor")
}
]),
new HtmlWebpackPlugin({
title: "Flop The World Poker",
template: "build/index.template.html"
})
]
};
const backend = {
entry: "./src/server.ts",
output: {
filename: "server.js",
path: path.resolve(__dirname, "build"),
publicPath: "/"
},
target: "node",
externals: nodeModules
};
module.exports = [
Object.assign({}, common, frontend),
Object.assign({}, common, backend)
];
import bodyParser from "body-parser";
import express from "express";
import { createServer } from "http";
import { listen } from "socket.io";
import { DeckController } from "./controllers";
const app: express.Application = express();
const port: number = ((process.env.PORT as any) as number) || 3000;
const server = createServer(app);
const io = listen(server);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static("./build"));
app.use("/deck", DeckController);
app.use(express.static(__dirname, { extensions: ["html"] }));
server.listen(port, () => {
// tslint:disable-next-line:no-console
console.log(`Listening at http://localhost:${port}/`);
});
io.on("connection", socket => {
console.log("Client connected..");
socket.send("Testing Message");
socket.on("join", data => {
console.log(data);
});
});

Webpack/React images full path

i want to load images in public path. I use webpackDevMiddleware + express + react
i have webpack config:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const htmlWebpackPlugin = require('html-webpack-plugin');
const autoprefixer = require('autoprefixer');
const ExtraneousFileCleanupPlugin = require('webpack-extraneous-file-cleanup-plugin');
const VENDOR_LIBS = [
'lodash',
'react',
'react-dom',
'react-router-dom',
'babel-polyfill'
];
const config = {
entry: {
app: './public/js/app.js',
vendor: VENDOR_LIBS,
},
devtool: 'eval-source-map',
output: {
path: path.resolve(__dirname, 'public/build'),
filename: '[name].[hash].js',
publicPath: '/'
},
stats: {
colors: true
},
module: {
rules: [
{
use: 'babel-loader',
test: /\.jsx?$/,
exclude: /node_modules/
},
{
use: ['style-loader', 'css-loader'],
test: /\.css$/
},
{
test: /\.styl$/,
loader: ExtractTextPlugin.extract({ fallback: 'style-loader',
use: [
'css-loader?minimize!',
{
loader: 'postcss-loader',
options: {
plugins: function () {
return [autoprefixer()]
},
sourceMap: 'inline'
}
},
'stylus-loader'
]
})
},
{
test: /\.(png|woff|woff2|otf|eot|ttf|svg|jpg|jpeg)$/,
loader: 'url-loader?limit=100000'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest'],
}),
new ExtractTextPlugin("[name].[hash].min.css"),
new htmlWebpackPlugin({
template: 'index.html'
}),
new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
})
/*new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})*/
]
}
module.exports = config;
Also, app.js entry point.
const express = require('express');
const app = express();
const path = require('path');
const fs = require('fs');
const router = express.Router();
const history = require('connect-history-api-fallback');
if(process.env.NODE_ENV != 'production'){
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpackConfig = require('./webpack.config.js');
const compiler = webpack(webpackConfig)
app.use(history());
app.use(webpackDevMiddleware(compiler, {
publicPath: webpackConfig.output.publicPath,
stats: {colors: true}
}))
} else {
app.use("/", express.static(__dirname));
app.get('*', function(req, res) {
res.sendFile(path.resolve(__dirname, 'public/build', 'index.html'));
});
}
app.listen(3030, () => console.log('listen 3030'));
And some component Menu:
import React from 'react'
import { Link } from 'react-router-dom'
import Logo from '../../../img/logo.svg'
class Menu extends React.Component {
constructor(){
super()
}
render(){
return (
<Link to="/">
<img src='public/img/logo.svg'/>
</Link>
)
}
}
export default Menu;
At all component i want to use <img src="/public/img/***.***"> or <img src="/img/***.***">, but this not work. If i use import Logo from '../../../img/logo.svg' it is work. But i have many images loading from server and i need path for examle '/public/img' or some else path.
My Project structure:
public
bulid
js
img
styl
font
node_modules
webpack.config.js
app.js
****(other)***

Wepack 2 resolve alias does not work in the server-side

Wepack 2 resolve alias does not work server side
I run wepback middleware with my express server. If the server is started when the import is relative, it'll work fine:
`import from '../../foobar/stuff'`
While the server is already running and the initial server side render is ready in memory; If we change the import in the component, to use the resolve property previously defined through webpack, it'll work:
`import from 'foobar/stuff'`
Meanwhile, if the server is stopped, and re-run with this last change, where the import uses the webpack resolve property defined in the configuration, this will fail (triggers the error not found)
File structure:
[root]
node_modules
webpack.config.js
|____ src
|____ src/js
|____ src/js/modules/
|____ src/js/modules/foobar/containers|components/**/*.js
|____ src/js/modules/lorem/containers|components/**/*.js
It seems that the express server does not know how to resolve the path that it's defined in the webpack 2 resolve property (even though I pass the webpackDevConfig file to the middleware).
Here's the server.dev.js:
import express from 'express'
import path from 'path'
import superagent from 'superagent'
import chalk from 'chalk'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router'
import configureStore from './src/js/root/store'
import { Provider } from 'react-redux'
import MyApp from './src/js/modules/app/containers/app'
import Routes from './src/js/root/routes'
const myAppChildRoutes = Routes[0].routes
const app = express()
const port = process.env.PORT ? process.env.PORT : 3000
var serverInstance = null
var dist = path.join(__dirname, 'dist/' + process.env.NODE_ENV)
var config = null
const webpack = require('webpack')
const webpackHotMiddleware = require('webpack-hot-middleware')
const webpackDevConfig = require('./webpack.dev.config')
const compiler = webpack(require('./webpack.dev.config'))
var webpackDevMiddleware = require('webpack-dev-middleware')
const webpackAssets = require('./webpack-assets.json')
config = require('./config')
/**
* Process error handling
*/
process.on('uncaughtException', (err) => {
throw err
})
process.on('SIGINT', () => {
serverInstance.close()
process.exit(0)
})
app.set('views', path.join(__dirname, 'src'))
app.set('view engine', 'ejs')
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: webpackDevConfig.output.publicPath,
stats: {
colors: true,
hash: false,
version: true,
timings: false,
assets: false,
chunks: false,
modules: false,
reasons: false,
children: false,
source: false,
errors: true,
errorDetails: true,
warnings: true,
publicPath: false
}
}))
app.use(webpackHotMiddleware(compiler, {
log: console.log
}))
/**
* The Cross origin resource sharing rules
*/
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type')
res.setHeader('Access-Control-Allow-Credentials', true)
next()
})
/**
* Health check
*/
app.use('/healthcheck', (req, res) => {
res.json({
'env': {
'NODE_ENV': process.env.NODE_ENV
}
})
res.end()
})
app.use('/api/test', (req, res) => {
superagent
.get('https://jsonip.com/')
.end((err, response) => {
if (err) {
console.log('api test err', err)
}
res.send(response.body)
})
})
app.use('/assets', express.static(dist))
app.get('*', (req, res) => {
// (wip) migration to react-router v4 temporary solution
// let matches
// if (typeof routes.props.children !== 'undefined' && Array.isArray(routes.props.children)) {
// matches = routes.props.children.find((v) => {
// return v.props.path === req.url
// })
// } else {
// matches = routes.props.children.props.path === req.url
// }
let matches = true
if (!matches) {
res.status(404).send('Not found')
} else {
const preloadedState = {'foobar': 1}
// Create a new Redux store instance
const store = configureStore(preloadedState)
// Render the component to a string
const myAppHtml = renderToString(<StaticRouter context={{}} location={req.url}>
<Provider store={store}>
<MyApp routes={myAppChildRoutes} />
</Provider>
</StaticRouter>)
// Grab the initial state from our Redux store
const finalState = store.getState()
res.render('index', {
app: myAppHtml,
state: JSON.stringify(finalState).replace(/</g, '\\x3c'),
bundle: webpackAssets.main.js,
build: config.build_name,
css: '/assets/css/main.min.css'
})
}
})
serverInstance = app.listen(port, (error) => {
if (error) {
console.log(error) // eslint-disable-line no-console
}
console.log(chalk.green('[' + config.build_name + '] listening on port ' + port + '!'))
})
Finally, the webpack dev config file is:
var path = require('path')
var webpack = require('webpack')
var AssetsPlugin = require('assets-webpack-plugin')
var assetsPluginInstance = new AssetsPlugin()
var ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
context: path.resolve(__dirname, 'src'),
entry: [
'react-hot-loader/patch',
'webpack/hot/dev-server',
'webpack-hot-middleware/client',
'babel-polyfill',
'./js/index.js'
],
output: {
path: path.join(__dirname, '/dist/development'),
publicPath: '/assets/',
filename: 'js/bundle.js?[hash]'
},
devtool: 'inline-source-map',
devServer: {
hot: true,
// match the output path
contentBase: path.join(__dirname, '/dist/development'),
// match the output `publicPath`
publicPath: '/assets/'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules)/,
use: [{
loader: 'babel-loader'
}]
},
{
test: /\.scss$/,
use: ['css-hot-loader'].concat(ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
}))
},
{
test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
use: [
'file-loader?name=[path][name].[ext]'
]
},
{
test: /\.(jpg|png|gif|svg)$/i,
use: [
'file-loader?name=[path][name].[ext]&emitFile=false'
]
}
]
},
plugins: [
new ExtractTextPlugin('css/[name].min.css?[hash]'),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development')
}
}),
assetsPluginInstance
],
resolve: {
alias: {
modules: path.resolve(__dirname, 'src/js/modules')
}
}
}
Also, tried to include the path to resolve automaticaly, without success:
module.paths.unshift(path.resolve(__dirname, 'src/js'))
The process.env.NODE_PATH is:
process.env.NODE_PATH Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/johnColtrane/www/coolApp/server.js',
loaded: false,
children: [],
paths:
[ '/Users/johnColtrane/www/coolApp/src/js',
'/Users/johnColtrane/www/coolApp/node_modules',
'/Users/johnColtrane/www/node_modules',
'/Users/johnColtrane/node_modules',
'/Users/node_modules',
'/node_modules' ] }
NOTE: someone suggested babel-plugin-module-alias instead, because of some issues with the approach I want to take. I'll check it and if better then this approach, I'll post here.

Use React-Router browserHistory, Webpack 2 historyApiFallback and Node

I'm trying to run my React app locally using React, Node, Webpack 2. Whenever I hit a route that isn't / I get a 404. My goal is to be able to run my node server, have webpack-dev-server run, use browserHistory and back my webpack historyApiFallback work.
What currently does work:
If I just run webpack-dev-server and no node server then browserHistory works fine, no 404s.
If I run node with hashHistory it works fine, no 404s.
So that rules out that my routes aren't working. Here is some code:
server.js
const express = require('express');
const expressGraphQL = require('express-graphql');
const schema = require('./schema');
const app = express();
app.use('/graphql', expressGraphQL({
schema,
graphiql: true
}));
const webpackMiddleware = require('webpack-dev-middleware');
const webpack = require('webpack');
const webpackConfig = require('../webpack.config.js');
app.use(webpackMiddleware(webpack(webpackConfig)));
app.listen(process.env.PORT || 5000, () => console.log('Listening'));
webpack.config.js
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VENDOR_LIBS = [
'axios', 'react', 'react-dom', 'react-router', 'react-apollo', 'prop-types'
];
module.exports = {
entry: {
bundle: './client/src/index.js',
vendor: VENDOR_LIBS
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].[chunkhash].js'
},
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/,
exclude: /node_modules/
},
{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "sass-loader"
}]
},
{
test: /\.(jpe?g|png|gif|svg|)$/,
use: [
{
loader: 'url-loader',
options: {limit: 40000}
},
'image-webpack-loader'
]
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}),
new HtmlWebpackPlugin({
template: './client/src/index.html'
})
],
devServer: {
historyApiFallback: true
}
};
routes.js
import React from 'react';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import App from './components/App';
import Portal from './components/portal/Portal';
const componentRoutes = {
component: App,
path: '/',
indexRoute: { component: Portal },
childRoutes: [
{
path: 'home',
getComponent(location, cb) {
System.import('./components/homepage/Home')
.then(module => cb(null, module.default));
}
}
]
};
const Routes = () => {
return <Router history={ browserHistory } routes={ componentRoutes } />
};
export default Routes;
Again, the goal is to be able to locally start up my node server, use browserHistory and not get 404s. I don't want to use hashHistory and I need to use my node server so I can use graphql. I also don't want to revert back to webpack v1. Though here is a link to where people got it working in v1:
historyApiFallback doesn't work in Webpack dev server
The historyApiFallback option is specifically for webpack-dev-server. If you're running your own server, even with webpack-dev-middleware, you need to configure it to send the index.html when a 404 occurs. Because you're using html-webpack-plugin the index.html you want to send does not exist on your file system but only in memory. To make it work you can access the output of the webpack compiler as shown in the comment of html-webpack-plugin #145.
server.js
const path = require('path');
const express = require('express');
const expressGraphQL = require('express-graphql');
const schema = require('./schema');
const app = express();
app.use('/graphql', expressGraphQL({
schema,
graphiql: true
}));
const webpackMiddleware = require('webpack-dev-middleware');
const webpack = require('webpack');
const webpackConfig = require('../webpack.config.js');
const compiler = webpack(webpackConfig);
app.use(webpackMiddleware(compiler));
// Fallback when no previous route was matched
app.use('*', (req, res, next) => {
const filename = path.resolve(compiler.outputPath, 'index.html');
compiler.outputFileSystem.readFile(filename, (err, result) => {
if (err) {
return next(err);
}
res.set('content-type','text/html');
res.send(result);
res.end();
});
});
app.listen(process.env.PORT || 5000, () => console.log('Listening'));

Resources