I've created a site with create-react-app that I build in ./client/build
and serve with express.
Using express, I also setup 2 basic API routes that work well in dev (localhost).
I build the client with react-scripts
Then I manually set the environment to production to build locally the server with webpack
Finally I push my code to my GitLab repo, and it triggers the deploy automatically.
Used Netlify deploy's settings :
Build command: Not set
Publish directory: client/build
When I try to use one of my API routes, I get an error 404 ...
My project structure is like this :
| package.json
| server.js
| webpack.config.js
+---API
| dbHandler.js
| routesHandler.js
+---client
| | package.json
| +---src
| | | App.js
| | | ...
| +---node_modules
| +---public
| | index.html
| | manifest.json
| | _redirects
| \---build
| | index.html
| | manifest.json
| | _redirects
| | asset-manifest.json
| | favicon_rings.ico
| | service-worker.js
| \---static
| +---media
| | ...
| +---js
| | main.1e7ccdbf.js
| | main.1e7ccdbf.js.map
| \---css
| main.fc8f2d26.css
| main.fc8f2d26.css.map
+---public
| _redirects
\---bundles
bundle.js
Extract from ./package.json
:
"main": "server.js",
"homepage": "https://custom.netlify.com",
"scripts": {
"start": "node server.js",
"build": "webpack --config webpack.config.js",
"postinstall": "npm run build",
"start:dev": "nodemon -e js --exec babel-node -- ./server.js"
}
./client/package.json
:
"homepage": "https://custom.netlify.com",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build && cp build/index.html build/404.html",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"proxy": "http://localhost:5000"
./client/build/_redirects
and ./public/_redirects
:
# Redirects from what the browser requests to what we serve
/* /index.html 200
./server.js
:
import webpackDevMiddleware from "webpack-dev-middleware";
import webpackHotMiddleware from "webpack-hot-middleware";
import routesHandler from "./API/routesHandler";
import config from "./webpack.config";
import bodyParser from "body-parser";
import webpack from "webpack";
import express from "express";
import path from "path";
const port = process.env.PORT || 5000;
const ExpressServer = express();
const CLI_BUILD_DIR = path.join(__dirname, "client/build");
const HTML_BUNDLE = path.join(CLI_BUILD_DIR, "index.html");
const compiler = webpack(config);
const isDevelopment = process.env.NODE_ENV === "development";
ExpressServer.use(bodyParser.urlencoded({ extended: true }));
ExpressServer.use(bodyParser.json());
ExpressServer.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
if (isDevelopment) {
ExpressServer.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
})
);
ExpressServer.use(webpackHotMiddleware(compiler));
} else {
// Serve static files from the React app
ExpressServer.use(express.static(CLI_BUILD_DIR));
}
// Define API routes
var router = express.Router();
router.post("/register", routesHandler.register);
router.post("/login", routesHandler.login);
// Put all API endpoints under '/API'
ExpressServer.use("/API", router);
// All other routes will be directed to the main page of user interface
ExpressServer.get("/", (req, res) => res.sendFile(HTML_BUNDLE));
ExpressServer.get("*", (req, res) => res.redirect(301, "/"));
// Start the server
ExpressServer.listen(port, function() {
console.log("Express server listening on http://localhost:" + port);
});
./webpack.config.js
:
const path = require('path');
const webpack = require('webpack');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
module.exports = {
entry: [
'webpack-hot-middleware/client',
'./API/routesHandler.js',
'./API/dbHandler.js',
'./server.js'
],
externals: {
jquery: 'jQuery',
'react/addons': 'react',
'react/lib/ExecutionEnvironment': 'react',
'react/lib/ReactContext': 'react',
},
output: {
path: path.resolve(__dirname, 'bundles'),
filename: 'bundle.js',
publicPath: '/public',
sourceMapFilename: 'bundle.map',
},
devtool: process.env.NODE_ENV === 'production'
? undefined : 'cheap-module-eval-source-map',
resolve: {
modules: ['node_modules', './client/build', './API', '.'],
extensions: ['.js', '.jsx', '.css'],
},
module: {
rules: [
{
test: /(\.js$|\.jsx$)/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['react', 'es2015', 'stage-0', 'airbnb'],
},
},
],
},
{
test: /(\.css$|\.scss$)/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|jpeg|gif)(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
}
],
},
node: {
console: false,
fs: 'empty',
fsevents: 'empty'
},
plugins: [
new webpack.IgnorePlugin(/fs/)],
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
minimize: true,
compressor: {
warnings: false,
},
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
},
}),
],
};
Netlify is for static file hosting - you can't deploy an Express application to it, only your frontend. You'll need to use a different service (such as DigitalOcean or Heroku) for your API hosting. You could potentially then route traffic to that API server via Netlify using redirects if you want to use their CDN for response caching.
Edit: This is no longer entirely true - Netlify now also has support for deploying AWS Lambda functions, allowing JavaScript and Go to be run on the server-side.