I've got an express server running to pre-render my react application. I've got a routes file that matches the HomeContainer to the base route /
and all other routes match to page not found.
import HomeContainer from 'containers/home-container/home-container';
import PageNotFound from 'ponents/page-not-found/page-not-found';
const routes = [
{
path: '/',
exact: true,
ponent: HomeContainer
},
{
path: '*',
ponent: PageNotFound
}
];
export default routes;
The problem I'm having is when I run the application on the server the page not found route gets rendered before quickly changing to the HomeContainer route.
I've identified that this is occurring because my express server is making a request to /json/version
before it makes a request to /
, this route doesn't match one in my routes file and therefore the page not found ponent is rendered.
Now what I don't get is why it's making this request and how to stop the page not found
ponent being rendered before the home container. I've tried debugging the node server and the earliest place I can find this path being referenced is in an emit call inside a file called _http_server.js
Below is a screenshot of the debugger and where I found the URL being referenced.
Also for reference, I've included my express server below.
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import { StaticRouter, matchPath } from 'react-router-dom';
import serialize from 'serialize-javascript';
import expressStaticGzip from 'express-static-gzip';
import sourceMapSupport from 'source-map-support';
import routes from 'routes';
import configureStore from 'store';
import AppContainer from 'containers/app-container/app-container';
if (process.env.NODE_ENV === 'development') {
sourceMapSupport.install();
}
const app = express();
app.use(expressStaticGzip('./static/assets'));
app.get('*', (req, res, next) => {
const store = configureStore();
/**
* Load initial data into state
* match requested URL path to the ponent in routes
* check for 'fireInitialActions' method (found in the container ponents)
* and dispatch if it exists
*/
const routeComponentPromises = routes.reduce((accumulator, route) => {
if (matchPath(req.url, route) && routeponent && routeponent.fireInitialActions) {
accumulator.push(Promise.resolve(store.dispatch(routeponent.fireInitialActions())));
}
return accumulator;
}, []);
Promise.all(routeComponentPromises)
.then(() => {
const context = {};
const markup = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<AppContainer />
</StaticRouter>
</Provider>
);
const initialData = store.getState();
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script src='vendor.js' defer></script>
<script src='app.js' defer></script>
<script>window.__initialData__ = ${serialize(initialData)}</script>
</head>
<body>
<div id="root">${markup}</div>
</body>
</html>
`);
})
.catch(next);
});
app.listen(process.env.PORT || 3000, () => {
console.log('Server is listening');
});
Does anyone know why is this happening and how I can solve my problem?
EDIT: Here's a video showing the issue - .24%20pm.mov
I've got an express server running to pre-render my react application. I've got a routes file that matches the HomeContainer to the base route /
and all other routes match to page not found.
import HomeContainer from 'containers/home-container/home-container';
import PageNotFound from 'ponents/page-not-found/page-not-found';
const routes = [
{
path: '/',
exact: true,
ponent: HomeContainer
},
{
path: '*',
ponent: PageNotFound
}
];
export default routes;
The problem I'm having is when I run the application on the server the page not found route gets rendered before quickly changing to the HomeContainer route.
I've identified that this is occurring because my express server is making a request to /json/version
before it makes a request to /
, this route doesn't match one in my routes file and therefore the page not found ponent is rendered.
Now what I don't get is why it's making this request and how to stop the page not found
ponent being rendered before the home container. I've tried debugging the node server and the earliest place I can find this path being referenced is in an emit call inside a file called _http_server.js
Below is a screenshot of the debugger and where I found the URL being referenced.
Also for reference, I've included my express server below.
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import { StaticRouter, matchPath } from 'react-router-dom';
import serialize from 'serialize-javascript';
import expressStaticGzip from 'express-static-gzip';
import sourceMapSupport from 'source-map-support';
import routes from 'routes';
import configureStore from 'store';
import AppContainer from 'containers/app-container/app-container';
if (process.env.NODE_ENV === 'development') {
sourceMapSupport.install();
}
const app = express();
app.use(expressStaticGzip('./static/assets'));
app.get('*', (req, res, next) => {
const store = configureStore();
/**
* Load initial data into state
* match requested URL path to the ponent in routes
* check for 'fireInitialActions' method (found in the container ponents)
* and dispatch if it exists
*/
const routeComponentPromises = routes.reduce((accumulator, route) => {
if (matchPath(req.url, route) && route.ponent && route.ponent.fireInitialActions) {
accumulator.push(Promise.resolve(store.dispatch(route.ponent.fireInitialActions())));
}
return accumulator;
}, []);
Promise.all(routeComponentPromises)
.then(() => {
const context = {};
const markup = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<AppContainer />
</StaticRouter>
</Provider>
);
const initialData = store.getState();
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script src='vendor.js' defer></script>
<script src='app.js' defer></script>
<script>window.__initialData__ = ${serialize(initialData)}</script>
</head>
<body>
<div id="root">${markup}</div>
</body>
</html>
`);
})
.catch(next);
});
app.listen(process.env.PORT || 3000, () => {
console.log('Server is listening');
});
Does anyone know why is this happening and how I can solve my problem?
EDIT: Here's a video showing the issue - https://d26dzxoao6i3hh.cloudfront/items/2z3y3f1x3N1D2e422W42/Screen%20Recording%202017-10-23%20at%2012.24%20pm.mov
Share Improve this question edited Oct 23, 2017 at 11:26 woolm110 asked Oct 23, 2017 at 11:10 woolm110woolm110 1,20419 silver badges29 bronze badges 6- Out of interest, presuming you are using react-router, what version is it? – jthawme Commented Oct 23, 2017 at 11:29
- @jthawme I am and its version 4 – woolm110 Commented Oct 23, 2017 at 11:30
- what happens if you run it without JavaScript? I think what happens is that your server returns 'Page not found', but as soon as JS kicks in it loads proper data and rebuilds DOM. I would assume you have an issue in routeComponentPromises. – Max Gram Commented Oct 23, 2017 at 17:17
-
@MaxGram same thing, i've stripped the whole thing back and just serve up some basic HTML with the express server (no routing or react) and it still tries to make calls to
/json
and/json/version
– woolm110 Commented Oct 23, 2017 at 17:28 -
I would try to disable
'/json/version
on the server withapp.get('/json/version', (req, res) => { res.send(204);})
then would turn off JS in browser and rerun the entire thing watching console in terminal. if 'not fond' page would still be the case I'd start debugging server. the first step would be to check everything that es in req.url in<StaticRouter location={req.url} context={context}>
– Max Gram Commented Oct 23, 2017 at 17:42
2 Answers
Reset to default 8I faced the same problem right after I had run my node application with --inspect
key. These strange GET requests to /json
and /json/version
were sent by chrome inspector.
So, the solution (in my case) is:
- Go to
chrome://inspect
. - Click the link "Open dedicated DevTools for Node";
- Open
Connection
tab. - Remove your endpoint from the list.
Not 100% sure of what was causing the issues but i've now fixed it.
The two major changes I made were
- Using EJS as a templating engine
- Setting
indexFromEmptyFile
to false in the gzip plugin
Number 2 seems to be really important, without it express was trying to serve index.html
rather than index.ejs
and this was causing the same issues as above as index.html
didn't match a route in my routes file.
Below is updated code
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import { StaticRouter, matchPath } from 'react-router-dom';
import serialize from 'serialize-javascript';
import sourceMapSupport from 'source-map-support';
import expressStaticGzip from 'express-static-gzip';
import routes from 'routes';
import configureStore from 'store';
import AppContainer from 'containers/app-container/app-container';
if (process.env.NODE_ENV === 'development') {
sourceMapSupport.install();
}
const app = express();
app.set('views', './static/');
app.set('view engine', 'ejs');
app.use(expressStaticGzip('static/assets', { indexFromEmptyFile: false }));
app.get('*', (req, res, next) => {
const store = configureStore();
/**
* Load initial data into state
* match requested URL path to the ponent in routes
* check for 'fireInitialActions' method (found in the container ponents)
* and dispatch if it exists
* return as promises so we can chain
*/
const routeComponentPromises = routes.reduce((accumulator, route) => {
if (matchPath(req.url, route) && route.ponent && route.ponent.fireInitialActions) {
accumulator.push(Promise.resolve(store.dispatch(route.ponent.fireInitialActions())));
}
return accumulator;
}, []);
Promise.all(routeComponentPromises)
.then(() => {
const context = {};
const markup = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<AppContainer />
</StaticRouter>
</Provider>
);
const initialData = serialize(store.getState());
res.render('index.ejs', {initialData, markup});
})
.catch(next);
});
app.listen(process.env.PORT || 3000, () => {
console.log('Server is listening');
});`