2

I'm running Apollo/React with Express and I'm trying to get server side rendering to work. The Apollo docs suggest the following initialisation code for connecting to the API server:

app.use((req, res) => {
  match({ routes, location: req.originalUrl }, (error, redirectLocation, renderProps) => {
    const client = new ApolloClient({
      ssrMode: true,
      networkInterface: createNetworkInterface({
        uri: 'http://localhost:3000', // Instead of 3010
        opts: {
          credentials: 'same-origin',
          headers: {
            cookie: req.header('Cookie'),
          },
        },
      }),
    });

    const app = (
      <ApolloProvider client={client}>
        <RouterContext {...renderProps} />
      </ApolloProvider>
    );

    getDataFromTree(app).then(() => {
      const content = ReactDOM.renderToString(app);
      const initialState = {[client.reduxRootKey]: client.getInitialState()  };
      const html = <Html content={content} state={initialState} />;
      res.status(200);
      res.send(`<!doctype html>\n${ReactDOM.renderToStaticMarkup(html)}`);
      res.end();
    });

  });
});

which uses the match() function from React Router v3 (as evidenced by package.json in the "GitHunt" example linked from the docs). I'm using React Router v4 from which match() is absent, so I refactored the code as follows, using renderRoutes() from the react-router-config package.

app.use((req, res) => {
  const client = new ApolloClient(/* Same as above */)

  const context = {}

  const app = (
    <ApolloProvider client={client}>
      <StaticRouter context={context} location={req.originalUrl}>
        { renderRoutes(routes) }
      </StaticRouter>
    </ApolloProvider>
  )

  getDataFromTree(app).then(/* Same as above */)
})

My understanding is that <StaticRouter> obviates the use of match(). However react-router-config provides a matchRoutes() function which seems to provide a similar functionality (albeit without the callback) if needed.

When I visit http://localhost:3000, the page loads as expected and I can follow links to subdirectories (e.g. http://localhost:3000/folder). When I try to directly load a subdirectory by typing in the name in the address bar, my browser keeps waiting for the server to respond. After about six seconds, Terminal shows one of the following errors (not sure what causes the error to change on subsequent tries):

(node:1938) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Network error: request to http://localhost:3000 failed, reason: getaddrinfo ENOTFOUND localhost localhost:3000

or

(node:8691) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Network error: request to http://localhost:3000 failed, reason: socket hang up

I've been struggling with this problem for a few hours now, but can't seem to figure it out. The solution to a similar problem seems unrelated to this case. Any help will be much appreciated!

Further information

If I don't kill the nodemon server, after some time I get thousands of the following errors:

POST / - - ms - -

(node:1938) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4443): Error: Network error: request to http://localhost:3000 failed, reason: socket hang up

If I do kill the server, however, I immediately get this error instead:

/Users/.../node_modules/duplexer/index.js:31

writer.on("drain", function() {

      ^

TypeError: Cannot read property 'on' of undefined

at duplex (/Users/.../node_modules/duplexer/index.js:31:11)

at Object.module.exports (/Users/.../node_modules/stream-combiner/index.js:8:17)

at childrenOfPid (/Users/.../node_modules/ps-tree/index.js:50:6)

at kill (/Users/.../node_modules/nodemon/lib/monitor/run.js:271:7)

at Bus.onQuit (/Users/.../node_modules/nodemon/lib/monitor/run.js:327:5)

at emitNone (events.js:91:20)

at Bus.emit (events.js:188:7)

at process. (/Users/.../node_modules/nodemon/lib/monitor/run.js:349:9)

at Object.onceWrapper (events.js:293:19)

at emitNone (events.js:86:13)

Also, port 3000 is correct. If I change the number, I get a different error instead:

(node:2056) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Network error: request to http://localhost:3010 failed, reason: connect ECONNREFUSED 127.0.0.1:3010

Bart
  • 1,600
  • 1
  • 13
  • 29
  • 1
    Have you tried `http://127.0.0.1:3000` instead of using `localhost`? Also, you should rule out antivirus software and/or firewalls. And make sure there's an entry `127.0.0.1 localhost` in your hosts file. – robertklep Jul 01 '17 at 07:54
  • I tried both `127.0.0.1` and `localhost`, with and without `http://`, but that wasn't it. I also double-checked the entry in my hosts file. However I noticed that the problem only manifests itself when directly visiting subfolders and updated the question accordingly. Thanks – Bart Jul 01 '17 at 11:33
  • Add ssrMode: true to ApolloClient, I had a similar issue and root cause was repeated fetching. Hope it helps. – sBanda Jul 04 '17 at 20:04
  • Do you mean at `const client = new ApolloClient(/* ... */)`? Because I'm already including `ssrMode: true` there, I just left out the arguments in the second code snippet because they were identical to those in the first one. Thanks anyway! – Bart Jul 05 '17 at 01:00
  • What is the node version you are using, and OS? – sBanda Jul 05 '17 at 15:03
  • Node v7.10.0, macOS Sierra 10.12.5 – Bart Jul 05 '17 at 15:11

2 Answers2

3

Are you running your server / project inside a container / containers. I had the same issue as this and ended up doing the following to fix it.

const networkInterface = createNetworkInterface({ uri: process.browser ? 'http://0.0.0.0:1111/graphql' : 'http://my-docker-container-name:8080/graphql' })

I have an internal docker network created for my containers in my docker-compose.yml, which allows the containers to communicate with each other, however the browser communicates to the GQL server on a different URL, causing the issue you described on SSR the getaddrinfo ENOTFOUND, so although it was working client side, on the SSR it would fail.

I am using nextJS framework which gives the ability to detect the browser or SSR, i'm sure you could do the same outside of nextJS.

Simon
  • 227
  • 4
  • 17
0

I finally found out that the error was due to renderToString and renderToStaticMarkup not being made available by importing ReactDOM.

The import statement import ReactDOM from 'react-dom' had to be replaced by import ReactDOMServer from 'react-dom/server'.

Also, uri had to point to http://localhost:3000/graphql instead of http://localhost:3000.

Bart
  • 1,600
  • 1
  • 13
  • 29