├── .nvmrc ├── README.md ├── index.js ├── package-lock.json └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nuxt-cluster 2 | [![npm](https://img.shields.io/npm/dt/nuxt-cluster.svg?style=flat-square)](https://www.npmjs.com/package/nuxt-cluster) 3 | [![npm (scoped with tag)](https://img.shields.io/npm/v/nuxt-cluster/latest.svg?style=flat-square)](https://www.npmjs.com/package/nuxt-cluster) 4 | 5 | > A production-grade nuxt server. 6 | 7 | ## Installation 8 | 9 | ```sh 10 | npm install nuxt-cluster --save 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | import server from 'nuxt-cluster' 17 | 18 | server.start({ 19 | dev: false, 20 | workers: 2, 21 | workerMaxRequests: 1000, // gracefully restart worker after 1000 requests 22 | workerMaxLifetime: 3600, // gracefully restart worker after 1 hour 23 | rootDir: '/app/', 24 | address: '0.0.0.0', 25 | port: 3000, 26 | }) 27 | ``` 28 | 29 | ## Options 30 | 31 | ### `dev` 32 | - Default: `false` 33 | 34 | Set to true to run nuxt in development mode 35 | 36 | ### `workers` 37 | - Default: `# cpu's` 38 | 39 | How many workers should be started. Defaults to the number of cpus in the system 40 | 41 | ### `workerMaxRequest` 42 | - Default: ` ` 43 | 44 | If set to a value greater then 0, each worker will be gracefully restarted after it has handled this many requests. As the worker is restarted gracefully, it will be handle all open connections before closing. 45 | 46 | ### `workerMaxLifetime` 47 | - Default: ` ` 48 | 49 | If set to a value greater then 0, each worker will be gracefully restarted after it has been alive this many seconds. 50 | 51 | ### `rootDir` 52 | - Default: ` ` 53 | 54 | Set this to the rootDir of your nuxt project. See the Nuxt.js [docs](https://nuxtjs.org/api/configuration-rootdir/#the-rootdir-property) for more info. 55 | 56 | ### `address` 57 | - Default: `0.0.0.0` 58 | 59 | The address the server will listen to 60 | 61 | ### `port` 62 | - Default: `3000` 63 | 64 | The port the server will listen to 65 | 66 | ## License 67 | 68 | MIT 69 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const cluster = require('cluster'); 2 | const path = require('path'); 3 | 4 | const watchdog = {} 5 | 6 | module.exports = { 7 | start ({ dev, workers, workerMaxRequests, workerMaxLifetime, address, port, rootDir }) { 8 | if (cluster.isMaster) { 9 | console.log(`master ${process.pid} is running`); 10 | 11 | // Fork workers 12 | const numWorkers = workers || require('os').cpus().length 13 | for (let i = 0; i < numWorkers; i++) { 14 | cluster.fork(); 15 | } 16 | 17 | if (workerMaxRequests || workerMaxLifetime) { 18 | cluster.on('message', (worker, msg) => { 19 | if (msg.cmd) { 20 | if (msg.cmd === 'notifyRequest') { 21 | let killWorker = false; 22 | 23 | if (workerMaxRequests) { 24 | watchdog[worker.process.pid].req_count++; 25 | 26 | killWorker = watchdog[worker.process.pid].req_count >= workerMaxRequests; 27 | if (killWorker) { 28 | console.log(`worker ${worker.process.pid} processed ${watchdog[worker.process.pid].req_count} requests, sending 'SIGHUP'`) 29 | } 30 | } 31 | 32 | if (!killWorker && workerMaxLifetime) { 33 | const lifetimeSeconds = process.hrtime(watchdog[worker.process.pid].start)[0]; 34 | 35 | killWorker = lifetimeSeconds > workerMaxLifetime; 36 | if (killWorker) { 37 | console.log(`worker ${worker.process.pid} has been up for ${lifetimeSeconds}s, sending 'SIGHUP'`) 38 | } 39 | } 40 | 41 | if (killWorker) { 42 | // fake SIGHUP message to gracefully shutdown the worker 43 | worker.send('SIGHUP'); 44 | } 45 | } 46 | } 47 | }); 48 | } 49 | 50 | cluster.on('fork', (worker) => { 51 | watchdog[worker.process.pid] = { start: process.hrtime(), req_count: 0 }; 52 | }); 53 | 54 | cluster.on('listening', (worker, address) => { 55 | console.log(`worker ${worker.process.pid} started listening on ${address.address}:${address.port}`); 56 | }); 57 | 58 | cluster.on('exit', (worker, code, signal) => { 59 | const workerLifetime = process.hrtime(watchdog[worker.process.pid].start) 60 | const msWorkerLifetime = Math.round((workerLifetime[0] * 1E9 + workerLifetime[1]) / 1E6) 61 | 62 | let message = `worker ${worker.process.pid} died after ${msWorkerLifetime}ms`; 63 | if (signal) { 64 | message += ` by signal: ${signal}`; 65 | } else if (code !== 0) { 66 | message += ` with error code: ${code}`; 67 | } 68 | 69 | if (signal && signal !== 'SIGHUP') { 70 | console.log(message); 71 | } else if (msWorkerLifetime < 1000) { 72 | console.log(message + ` but spawning too quickly (<1s), not restarting`); 73 | } else { 74 | console.log(message + `, restarting`); 75 | cluster.fork(); 76 | } 77 | 78 | // cleanup watchdog 79 | delete watchdog[worker.process.pid] 80 | }); 81 | } else { 82 | const ROOT_DIR = rootDir || process.env.ROOT_DIR || process.cwd(); 83 | const config = require(path.join(ROOT_DIR, 'nuxt.config.js')); 84 | config.port = port || process.env.PORT || 3000; 85 | config.dev = dev || false; 86 | config.rootDir = ROOT_DIR; 87 | 88 | const { Nuxt } = require('nuxt'); 89 | const morgan = require('morgan'); 90 | const app = require('express')(); 91 | const nuxt = new Nuxt(config); 92 | 93 | morgan.token('pid', function (req, res) { return process.pid }); 94 | app.use(morgan('[worker: :pid] :status :method :url - :response-time')); 95 | 96 | if (workerMaxRequests || workerMaxLifetime) { 97 | app.use((req, res, next) => { 98 | if (cluster.worker.isConnected()) { 99 | process.send({ cmd: 'notifyRequest' }); 100 | } 101 | next(); 102 | }); 103 | } 104 | 105 | app.use(nuxt.render); 106 | const server = app.listen(config.port, address || '0.0.0.0'); 107 | 108 | // shutdown worker gracefully 109 | let isClosing = false; 110 | let forceTimeout; 111 | process.on('message', (msg) => { 112 | if (msg === 'SIGHUP') { 113 | if (!isClosing) { 114 | isClosing = true 115 | 116 | server.close(() => { 117 | process.exit(3); 118 | }); 119 | } 120 | 121 | // force shutdown after 3s 122 | clearTimeout(forceTimeout); 123 | forceTimeout = setTimeout(() => { 124 | process.exit(3); 125 | }, 3000); 126 | } 127 | }); 128 | } 129 | } 130 | } 131 | 132 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-cluster", 3 | "version": "1.0.0", 4 | "description": "A production ready nuxt server.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dummy": "./test/run.sh" 8 | }, 9 | "author": "Brian Gonzalez", 10 | "license": "MIT", 11 | "dependencies": { 12 | "express": "^4.14.0", 13 | "morgan": "^1.7.0", 14 | "nuxt": "^1.0.0-rc11" 15 | } 16 | } 17 | --------------------------------------------------------------------------------