Export your Next.js site without DDOSing yourself

June 27, 2019
By Paul Grieselhuber

Here was our situation as we approached launch of a recent Next.js-powered large content website. We were about 3 weeks away from launch and wanted to start the production server setup, testing deployment, exports, etc.

Everything was running smoothly until we got to the Next.js static exports part of the deployment. Thus far in the project, our team had always been running static export tests from their local dev machines which were some physical distance away from the production server. Sufficiently far that the dev machines were unlikely to overload the production server (the location of the API for the project).

If you are still trying to get static exports setup on your project, check out our detailed post on the WPGraphQL blog.

Everyone’s favorite error: 502

Nearly as soon as we fired off the export from the same server as the API, our terminals filled with 502 errors, specifically:

Error while running `getMarkupFromTree` { Error: Network error: Unexpected token < in JSON at position 0
    at new ApolloError (/var/www/next/node_modules/apollo-boost/node_modules/apollo-client/bundle.umd.js:92:26)
    at /var/www/next/node_modules/apollo-boost/node_modules/apollo-client/bundle.umd.js:1581:34
    at /var/www/next/node_modules/apollo-boost/node_modules/apollo-client/bundle.umd.js:2001:15
    at Set.forEach (<anonymous>)
    at /var/www/next/node_modules/apollo-boost/node_modules/apollo-client/bundle.umd.js:1999:26
    at Map.forEach (<anonymous>)
    at QueryManager.broadcastQueries (/var/www/next/node_modules/apollo-boost/node_modules/apollo-client/bundle.umd.js:1997:20)
    at /var/www/next/node_modules/apollo-boost/node_modules/apollo-client/bundle.umd.js:1476:29
    at <anonymous>
    at process._tickDomainCallback (internal/process/next_tick.js:228:7)
  graphQLErrors: [],
  networkError:
   { ServerParseError: Unexpected token < in JSON at position 0
    at JSON.parse (<anonymous>)
    at /var/www/next/node_modules/apollo-link-http-common/lib/index.js:35:25
    at <anonymous>
    at process._tickDomainCallback (internal/process/next_tick.js:228:7)
     name: 'ServerParseError',
     response:
      Response {
        size: 0,
        timeout: 0,
        [Symbol(Body internals)]: [Object],
        [Symbol(Response internals)]: [Object] },
     statusCode: 502,
     bodyText: '<html>\r\n<head><title>502 Bad Gateway</title></head>\r\n<body bgcolor="white">\r\n<center><h1>502 Bad Gateway</h1></center>\r\n<hr><center>nginx/1.10.3 (Ubuntu)</center>\r\n</body>\r\n</html>\r\n' },
  message: 'Network error: Unexpected token < in JSON at position 0',
  extraInfo: undefined }

This was a WPGraphQL (our review) API, running from a WordPress site. If you’ve been building WordPress sites at substantial scale for some time, you can probably guess the error we got from PHP:

[error] 1248#1248: *5807411 connect() to unix:/run/php/php7.0-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream, client: xxx.xxx.xxx.xxx, server: website.com, request: "GET /this/page HTTP/1.1", upstream: "fastcgi://unix:/run/php/php7.0-fpm.sock:", host: "www.website.com"

If you have run in to a 502 error on a WordPress site, you also know that your day was just ruined. There are a near infinite number of causes to which this error could refer.

De-DDOS yourself

It became clear after some discussion with @p-k on Spectrum (ht for the excellent sleuthing on this), that the issue here is that Next.js export process runs on multiple threads, and due to the proximity of the applications and the speed of execution, you essentially DDOS yourself every time you export your site.

Since we plan to export several times a day as the site contents are updated, this would obviously have been a non-starter. This is also the reason why we don’t build the site locally and simply deploy the exported site to production.

So, we need to tell Next.js to run on fewer threads and with lower concurrency in order to successfully build this site on the production server.

Easy enough: update your package.json file, specifically the export script to include the --threads and --concurrency options, for example:

{
  "scripts": {
    ...
    "export": "next export --threads 1 --concurrency 1",
    ...
  },
  ...
}

This should solve the issue, but will mean much slower export times. Ours went from about 10-12 minutes to about 60 minutes. Further testing will help us determine what the API can handle in terms of additional threads and higher concurrency. In the mean time, this still accomplishes our goal of multiple builds per day.

Check out the next post in our series on building static Next.js sites, Next.js + Multiple Apollo Clients & GraphQL Sources

Paul Grieselhuber