Text content did not match. Warning in React 16 - javascript

I trying to build ReactJs application with server side rendering
My entry points for client and server:
client.jsx
const store = createStore(window.__INITIAL_STATE__);
hydrate(
<Provider store={store}>
<BrowserRouter>{renderRoutes(routes)}</BrowserRouter>
</Provider>,
document.querySelector('#root')
);
server.jsx
const app = express();
if (isDev) {
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('../../webpack.config.js');
const compiler = webpack(config);
app.use(express.static('/public'));
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
stats: 'errors-only',
})
);
}
app.get('*', (req, res) => {
const helmet = Helmet.renderStatic();
const htmlAttrs = helmet.htmlAttributes.toComponent();
const bodyAttrs = helmet.bodyAttributes.toComponent();
const context = {};
const data = {};
res.set('content-type', 'text/html');
res.send(
'<!DOCTYPE html>' +
renderToString(
<html {...htmlAttrs}>
<head>
{helmet.title.toComponent()}
{helmet.meta.toComponent()}
{helmet.link.toComponent()}
</head>
<body {...bodyAttrs}>
<div id="root">
<StaticRouter location={req.url} context={context}>
{renderRoutes(routes)}
</StaticRouter>
</div>
<script
dangerouslySetInnerHTML={{
__html: `window.__INITIAL_STATE__ = ${JSON.stringify(data)}`,
}}
/>
<script src="/public/vendor.js" />
<script src="/public/app.js" />
</body>
</html>
)
);
});
And component:
home.jsx
import React, { Component } from 'react';
class Home extends Component {
render() {
return <div>home</div>;
}
}
export default Home;
When I change in my component Home and refresh browser page I get this error:
Warning: Text content did not match. Server: "home" Client: "home1"
Its ok because server render old version of my code. How to reload the code on the server so that the client and server versions are equal?

For those discovering this error because your client and server purposefully render different content (like the server rendering a dark theme which gets replaced with the user preference on load), use suppressHydrationWarning to suppress the error.
For example:
<div suppressHydrationWarning>Ignore this</div>

The problem here is that your server-side application does not reflect code changes. To do that you have to configure your express app as a webpack entry.
Briefly, you need 2 webpack configurations one for server and another for client code.
The server one will look something like this
module.exports = {
entry: {
server: './server.js',
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].js'
},
target: 'node',
node: {
// Need this when working with express, otherwise the build fails
__dirname: false, // if you don't put this is, __dirname
__filename: false, // and __filename return blank or /
},
externals: [nodeExternals()], // Need this to avoid error when working with Express
module: {
rules: [
{
// Transpiles ES6-8 into ES5
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
// Loads the javacript into html template provided.
// Entry point is set below in HtmlWebPackPlugin in Plugins
test: /\.html$/,
use: [{loader: "html-loader"}]
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./index.html",
filename: "./index.html",
excludeChunks: [ 'server' ]
})
]
}
Here is a nice article explaining how to do that in details

No one said about but Cloudflare replaced double spaces with a space when you enable Auto Minify (HTML) in optimization.
And this error will happen.

Related

Webpack Hot module replacement, react 18 ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before

I updated React to v18 and my Webpack dev server gives me a console error whenever the hot module replacement fires and injects the new javascript code:
Warning: You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root. render() on the existing root instead if you want to update it.
index.js file
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App />);
if (module.hot) module.hot.accept(function (err) {
console.log('An error occurred while accepting new version');
});
webpack.config.js
const path = require('path');
const HtmlWEbpackPlugin = require('html-webpack-plugin');
module.exports = (env) => {
let cfg = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash:6].js',
publicPath: '/',
clean: true
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
]
},
plugins: [new HtmlWEbpackPlugin({ template: './src/index.html' })
],
devServer: {
static: {
directory: path.join(__dirname, 'public'),
},
compress: true,
port: 3000,
hot: true,
open: true,
},
performance: {
hints: false
}
}
return cfg;
};
React 18 has native support for hot reloading, this is called Fast Refresh. An excerpt from the react-refresh readme:
Fast Refresh is a feature that lets you edit React components in a running application without losing their state. It is similar to an old feature known as "hot reloading", but Fast Refresh is more reliable and officially supported by React.
To use this with Webpack 5, you will need a plugin called react-refresh-webpack-plugin. To get it to work I would recommend taking a look at the examples included in the git repository, especially the webpack-dev-server example.
NOTE: As of writing this answer, the react-refresh-webpack-plugin is in an experimental state but create-react-app is already using it, so it is probably stable enough to use.
The following is taken straight from react-refresh-webpack-plugin's webpack-dev-server example:
src/index.js
import { createRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('app');
const root = createRoot(container);
root.render(<App />);
webpack.config.js
const path = require('path');
const ReactRefreshPlugin = require('#pmmmwh/react-refresh-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
devServer: {
client: { overlay: false },
},
entry: {
main: './src/index.js',
},
module: {
rules: [
{
test: /\.jsx?$/,
include: path.join(__dirname, 'src'),
use: 'babel-loader',
},
],
},
plugins: [
isDevelopment && new ReactRefreshPlugin(),
new HtmlWebpackPlugin({
filename: './index.html',
template: './public/index.html',
}),
].filter(Boolean),
resolve: {
extensions: ['.js', '.jsx'],
},
};
babel.config.js
module.exports = (api) => {
// This caches the Babel config
api.cache.using(() => process.env.NODE_ENV);
return {
presets: [
'#babel/preset-env',
// Enable development transform of React with new automatic runtime
['#babel/preset-react', { development: !api.env('production'), runtime: 'automatic' }],
],
// Applies the react-refresh Babel plugin on non-production modes only
...(!api.env('production') && { plugins: ['react-refresh/babel'] }),
};
};
You can remove the following from your Webpack entry point:
src/index.js
// ...
if (module.hot) {
module.hot.accept()
}
This has the small drawback that whenever you modify your entry point (src/index.js) a full reload is necessary. Webpack is very in your face about needing to do a full reload, showing you the following log messages.
This really annoyed me. When looking at how create-react-app solved this, I found that they disabled client side logging for the webpack-dev-server, or at least set the log level to warn or error. You can set the log level by setting the client.logging property in the devServer configuration:
webpack.config.js
// ...
devServer: {
client: {
overlay: false,
logging: 'warn' // Want to set this to 'warn' or 'error'
}
}
// ...
What the odd thing is about the "warning", is that it is not a warning at all, it is just an info message dressed up as a warning.
Hope this helps.

How to include styles when rendering page on server?

I have a problem implementing server side rendering. I'm using react + typescript frontend and backend written in node.js with typescript as well. I cannot find a way to include styles in my page when it's send from the server for the first time (before js loads). What I've tried is bundling all my styles into single main.css file and then using link tag in html file send from server, but in this scenario it takes as much time to load css, as it takes to load js (what is the purpose of SSR here ?). Also i'm trying to avoid styling my component inline. Any help would be apprecieted. Here I'm sharing some of my current code in case it might be helpful in some way.
App Component <- optimally i would like to see styled app as soon as browser reads .html file (i guess that's
the purpose of SSR)
export default class App extends React.Component<State, Props> {
state: State = {
}
render = () => {
return (
<div className="app">
APP COMPONENT !!!
</div>
)
}
}
Client code entry (ClientEntry.tsx)
import React from "react";
import ReactDOM from "react-dom";
import App from "../shared/containers/App";
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("app")
);
Webpack config
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const autoprefixer = require("autoprefixer");
const WebpackShellPlugin = require('webpack-shell-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const clientConfig = {
stats:'errors-only',
entry: "./src/client/ClientEntry.tsx",
output: {
path: __dirname,
filename: "./build/bundle.js"
},
devtool: "cheap-module-source-map",
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
module: {
rules: [
{
test: [/\.svg$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: "file-loader",
options: {
name: "build/media/[name].[ext]",
publicPath: url => url.replace(/build/, "")
}
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: [
{
loader: "css-loader",
options: { importLoaders: 1 }
},
{
loader: "postcss-loader",
options: { plugins: [autoprefixer()] }
}
]
})
},
{
test: [/tsx?$/],
exclude: /(node_modules)/,
loader: "ts-loader",
}
]
},
plugins: [
new ExtractTextPlugin({
filename: "build/css/[name].css"
}),
new WebpackShellPlugin({
onBuildEnd: ['nodemon build/server.js']
})
]
};
const serverConfig = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin({ terserOptions: { mangle: false } })],
},
stats:'errors-only',
entry: "./src/server/index.tsx",
target: "node",
output: {
path: __dirname,
filename: "./build/server.js",
libraryTarget: "commonjs2"
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
devtool: "cheap-module-source-map",
module: {
rules: [
{
test: [/\.svg$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: "file-loader",
options: {
name: "build/media/[name].[ext]",
publicPath: url => url.replace(/build/, ""),
emit: false
}
},
{
test: /\.css$/,
use: [
{
loader: "css-loader/locals"
}
]
},
{
test: [/tsx?$/],
exclude: /(node_modules)/,
loader: "ts-loader",
}
]
}
};
module.exports = [clientConfig, serverConfig];
Part of index.tsx (express main file)
server.get('/*', (req, res) => {
const body = renderToString(
<StaticRouter location={req.url}>
<App />
</StaticRouter>
);
res.send(
html({
body
})
);
})
And function that converts to html
const html = ({ body }: { body: string }) => `
<!DOCTYPE html>
<title> my app</title>
<html>
<head>
<link rel="stylesheet" href="/css/main.css">
</head>
<body style="margin:0">
<div id="app">${body}</div>
</body>
<script src="/bundle.js" defer></script>
</html>
`;
export default html;
I'm assuming you're using express, which has a built in method to serve static files (such as images, css, etc...) you just need to show it the path as follow
server.use('static', express.static('css'))
which basically serves all files inside the css folder under the link
http://${host}/static/
if you want to serve static images you can do the following.
server.use('images', express.static('images'))
and then you can refer to any image file inside your code using the following.
<img src="/images/<image_name>" />
You can read more about it here.
express static files

How to load web components in webpack project?

I have started experimenting with webpack and webcomponents. So far I managed only to run this basic web component example without webpack. If I simply load the web component in the index.html using a <script> tag than I get it's content rendered. However, when I try to run the same example using a basic webpack setup I don't see the rendered template.
The server is launched successfully and I can see the console init message. What is the missing step? I would expect to be able to concatenate them this way.
I have the following files:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/">
<meta charset="UTF-8">
<title>Web Components App</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<vs-app></vs-app>
</body>
</html>
main.ts
module.hot && module.hot.accept();
// Components
import { App } from './app';
// Styles
require('./shared/scss/main.scss');
console.log('Initialise Main');
app.ts
/**
* App
*/
export class App extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = this.template;
}
get template() {
return `
<div>This is a div</div>
`;
}
}
window.customElements.define('vs-app', App);
webpack-common.js
const webpack = require("webpack"),
path = require("path"),
CopyWebpackPlugin = require('copy-webpack-plugin'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
// Constants
const BUILD_DIR = path.join(__dirname, "../../build"),
PUBLIC_DIR = path.join(__dirname, "../../public"),
NODE_MODULES_DIR = path.join(__dirname, "../../node_modules");
// Shared between prod and dev
module.exports = {
entry: "./public/main.ts",
output: {
// Since webpack-dev-middleware handles the files in memory, path property is used only in production.
path: BUILD_DIR,
publicPath: "/",
filename: "bundle.js"
},
resolve: {
extensions: ["*", ".ts", ".js"]
},
module: {
loaders: [{
test: /\.ts?$/,
loader: "awesome-typescript-loader",
include: PUBLIC_DIR,
exclude: /node_modules/
},
{
test: /\.css$/,
exclude: /node_modules/,
loader: "style-loader!css-loader!autoprefixer-loader"
},
{
test: /\.scss$/,
loader: 'style-loader!css-loader!sass-loader'
},
]
},
// Base plugins
plugins: [
new CopyWebpackPlugin([
{
from: `public/shared/images`,
to: 'images'
},
]),
new HtmlWebpackPlugin({
template: 'public/index.html'
}),
// Load bundle.js after libs are loaded
new ScriptExtHtmlWebpackPlugin({
defaultAttribute: 'defer'
})
],
stats: {
colors: true,
modules: true,
reasons: true,
errorDetails: true
}
};
Edit
Eventually I got the component rendered moving the following line window.customElements.define('vs-app', App); from app.ts to main.ts. In the mean time I discovered that even that is not necessary.
// This is enough to trigger rendering
import { App } from './app';
App;
However, I still have an issue. Because of webpack hot reload, the component ends up registered twice, giving an error. Still looking for a fix.
Edit 2
After some research online I managed to find the problem: I did not have the inject: false property. This triggered webpack to load twice the same stuff. Looks like I can finally move forward with developing the app. However I would love to find out alternative ways to this setup, just to get confirmation that I'm on the right road.
new HtmlWebpackPlugin({
template: 'public/index.html',
inject: false
}),

File size when using Webpack, Node, Express, React, React-Router

Webpack is bundling a file to the size of 1.34mb for a simple server. To me, it seems unnecessarily large considering how limited the server is. I know that using express, react, react-router, etc. will balloon the file, but to me, it seems huge!
Firstly, I was wondering if I was using webpack correctly? Secondly, where should I start to look to make it smaller?
server.js
var express = require('express')
var path = require('path')
import React from 'react'
import { match, RouterContext } from 'react-router'
import { renderToString } from 'react-dom/server'
import routes from './src/routes/routes'
var app = express()
app.use(express.static(path.join(__dirname, 'public')))
app.use(function(rq, rs, nx){
console.log("rq.url: ", rq.url)
nx()
})
app.get('*', function(req, res){
match({
routes: routes,
location: req.url
}, (err, redirect, props) => {
if(err){
res.status(500).send(err.message)
} else if(redirect) {
res.redirect(redirect.pathname + redirect.search)
} else if (props) {
console.log("PROPS: ", props)
let appHtml = renderToString(<RouterContext {...props} />)
res.status(200).send(renderPage(appHtml))
} else {
res.status(404).send('Not Found')
}
})
})
var PORT = process.env.PORT || 8089
app.listen(PORT, function(){
console.log('listening on port ' + PORT)
})
function renderPage(appHtml){
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>React With Server</title>
</head>
<body>
<div id="main">${appHtml}</div>
<script src="/javascript/bundle.js"></script>
</body>
</html>
`
}
webapack.config.js:
module.exports = {
entry: path.resolve(__dirname, 'server.js'),
output: {
filename: 'server.bundle.js',
path: __dirname
},
target: 'node',
node: {
__filename: true,
__dirname: true
},
module: {
rules: [
{
loader: 'babel-loader',
test: /\.js$/,
exclude: /node_modules/,
options: {
presets: ['latest', 'react']
}
}
]
}
}
What you're looking for is a Production build.
webpack -p
The -p flag automatically enables production mode, which optimises the bundle. Or you can configure those steps manually. With this the bundle's size is only 1/3 of the original.
From here you can start looking what actually makes your bundle the size it is, for example by using Webpack Bundle Analyzer.
As you can see db.json from mime-db is by far the biggest part of the bundle, taking up about a fourth of the total size. And react-dom in total also takes up about the same space.
You might be wondering why you have mime-db in the bundle. If you are using Yarn, you can run the why command:
yarn why mime-db
Which tells you:
This module exists because "express#accepts#mime-types" depends on it.
So you can't really get rid of that. While using Express and React you probably won't get much lower in size of the bundle.

Images not displaying in React App

Good afternoon,
I am working on a React/Redux app and the images that are being loaded into my store are not displaying. My galleryObject.js includes information about each image I want to display like so:
pastry1: {
name: "Pastry!",
image: "./client/images/cake1.jpg",
desc: "Tasty Pastry"
},
pastry2: {
name: "Pastry!",
image: "./client/images/cake2.jpg",
desc: "Tasty Pastry"
} ... (all the way to pastry17)
What baffles me is that the absolute path does not lead to the image being displayed, the state is being loaded correctly because I can see it in my React dev tools. I even threw in a hyperlink to an image online and it works to test it out.
My file structure is like so:
// Project
// client
//images (where the actual pictures are stored)
//data (where galleryObject.js resides)
//main.js (where everything eventually becomes bundled
In my experience this is normally a problem with how my devServer.js accesses static files in my project. The real admission here is that I copy pasta-d this devServer.js from Wes Bos' Learn Redux tutorial and this is what it looks like:
devServer.js
var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev');
var PORT = process.env.PORT || 7770
var app = express();
var compiler = webpack(config);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
app.use(require('webpack-hot-middleware')(compiler));
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(PORT, "localhost", function(err) {
if (err) {
console.log(err);
return;
}
console.log(__dirname);
console.log('Listening at http://localhost:7770');
});
Seeing a lot of webpack stuff I figured that is where I am going wrong so I checked out tcoopman's image-webpack-loader. I npm installed the module and my webpack.config.dev/prod.js both look the same as the example from tcoopman:
webpack.config.dev.js:
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool: 'source-map',
entry: [
'webpack-hot-middleware/client',
'./client/main'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/static/'
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
module: {
loaders: [
// js
{
test: /\.js$/,
loaders: ['babel'],
include: path.join(__dirname, 'client')
},
// CSS
{
test: /\.css$/,
include: path.join(__dirname, 'client'),
loader: 'style-loader!css-loader!stylus-loader'
},
// images
{
test: /\.(jpe?g|png|gif|svg)$/i,
include: path.join(__dirname, 'client'),
loaders: [
'file?hash=sha512&digest=hex&name=[hash].[ext]',
'image-webpack?bypassOnDebug&optimizationLevel=7&interlaced=false'
]
}
]
}
};
webpack.config.prod.js:
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool: 'source-map',
entry: [
'./client/main'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/static/'
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': "'production'"
}
}),
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false
}
})
],
module: {
loaders: [
// js
{
test: /\.js$/,
loaders: ['babel'],
include: path.join(__dirname, 'client')
},
// CSS
{
test: /\.scss$/,
include: path.join(__dirname, 'client'),
loaders: ["style", "css", "sass"]
},
// IMAGES
{
test: /\.(jpe?g|png|gif|svg)$/i,
include: path.join(__dirname, 'client'),
loaders: [
'file?hash=sha512&digest=hex&name=[hash].[ext]',
'image-webpack?bypassOnDebug&optimizationLevel=7&interlaced=false'
]
}
]
}
};
I'm certain that my combination of copy pasta and lack of knowledge when it comes to web pack are at fault. Bad dev. But I'd really appreciate some insight as to what else I am doing wrong not getting my images to display.
Cheers
edit showing how the images make it into the store:
project/client/store.js
import { createStore, compose } from "redux";
import { syncHistoryWithStore } from "react-router-redux";
import { browserHistory } from "react-router";
// all roots
// root reducer
import rootReducer from "./reducers/mainReducer";
// data Objects
import cakeGallery from './data/galleryObject'; // the object is passed into the default state
// default state object
const defaultState = {
cakeGallery,
open: false
};
const store = createStore(rootReducer, defaultState);
export const history = syncHistoryWithStore(browserHistory, store);
export default store;
project/client/reducers/cakeGalleryReducer.js
function cakeGallery(state={}, action){
console.log("cake gallery reducer")
console.log(state, action);
return state;
}
export default cakeGallery; // this gets combined with another reducer
// in project/client/reducers/mainReducer.js
I think here is where I run into trouble. When the page is loaded the cakeGalleryReducer.js function is firing off, so am I passing an empty object continually? This is a picture of my javascript console when the page loads initially, it still seems like I have an object that should be full.
project/client/components/App.js
// this file is basically creating a component that will
// "sprinkle on" props and dispatchers to our Main component
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import * as actionCreators from "../actions/userActions.js";
import StoreShell from "./StoreShell.js";
// cakeGallery is now known simply as images when referred to in components
function mapStateToProps(state){
return {
images: state.cakeGallery,
open: state.open
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators, dispatch);
}
const App = connect(mapStateToProps, mapDispatchToProps)(StoreShell);
// immediately call what component you want to connect to (Main)
export default App;
project/client/components/StoreShell.js
import Main from "./Main"
const StoreShell = React.createClass({
render(){
return(
<div>
<Main {...this.props} />
</div>
)
}
})
export default StoreShell;
From here the information in the intial galleryObject.js accessible as {this.props.images}.
Webpack is not that smart to import images for you.
I suggest you to import all images required in your galleryObject.js, and keep a reference of the image in each pastry object. Then when it gets to Main component, you can just use them directly.
Check it out
devServer.js
var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev');
var PORT = process.env.PORT || 7770
var app = express();
var compiler = webpack(config);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
// SOLVED IT
app.use(express.static(path.join(__dirname + "/client")));
app.use(require('webpack-hot-middleware')(compiler));
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(PORT, "localhost", function(err) {
if (err) {
console.log(err);
return;
}
console.log(__dirname);
console.log('Listening at http://localhost:7770');
});
So this line: app.use(express.static(path.join(__dirname + "/client"))); was not originally in my copy pasta'd devServer.js. What I did was make my static files (css, javascript, and most importantly images) accessible using the express.static middleware that allows you to serve up static files.
This is how I normally solve this issue but I assumed that images were already covered in the code above and below my soluton.
Thanks for everyone's input.

Categories