Next.js comes with a built-in server that runs via next start. If your application requires custom routing logic or request handling, you can set up a custom server using Node.js. This approach is only recommended for advanced use cases.
server.js// server.ts
import { createServer } from 'http'
import { parse } from 'url'
import next from 'next'
const port = parseInt(process.env.PORT || '3000', 10)
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url!, true)
handle(req, res, parsedUrl)
}).listen(port)
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? 'development' : process.env.NODE_ENV
}`
)
}){
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
}You can also use nodemon for automatic reloads during development.
The next() function accepts a configuration object:
| Option | Type | Description |
|---|---|---|
| conf | Object | Same as next.config.js |
| dev | Boolean | Enable development mode |
| dir | String | Path to Next.js project |
| quiet | Boolean | Suppress error messages |
| hostname | String | Hostname of the server |
| port | Number | Port number |
| httpServer | Server | Existing HTTP server instance |
| turbopack | Boolean | Enable Turbopack |
| webpack | Boolean | Enable Webpack |
Custom servers in Next.js offer advanced control over routing and request handling. However, they should be used cautiously, as they disable key performance features. Avoid combining with standalone mode, and always evaluate whether the built-in router can meet your needs before ejecting.