Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

Commit

Permalink
Add next.config.js option to override default keepAlive (vercel#2…
Browse files Browse the repository at this point in the history
…7709)

Follow up to vercel#27376 so users can disable keep-alive.

See comment vercel#27376 (comment)
  • Loading branch information
styfle authored Aug 3, 2021
1 parent 98db695 commit f6a2ac7
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 44 deletions.
36 changes: 36 additions & 0 deletions docs/api-reference/next.config.js/disabling-http-keep-alive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
description: Next.js will automatically use HTTP Keep-Alive by default. Learn more about how to disable HTTP Keep-Alive here.
---

# Disabling HTTP Keep-Alive

Next.js automatically polyfills [node-fetch](/docs/basic-features/supported-browsers-features#polyfills) and enables [HTTP Keep-Alive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive) by default. You may want to disable HTTP Keep-Alive for certain `fetch()` calls or globally.

For a single `fetch()` call, you can add the agent option:

```js
import { Agent } from 'https'

const url = 'https://example.com'
const agent = new Agent({ keepAlive: false })
fetch(url, { agent })
```

To override all `fetch()` calls globally, you can use `next.config.js`:

```js
module.exports = {
httpAgentOptions: {
keepAlive: false,
},
}
```

## Related

<div class="card">
<a href="/docs/api-reference/next.config.js/introduction.md">
<b>Introduction to next.config.js:</b>
<small>Learn more about the configuration file used by Next.js.</small>
</a>
</div>
4 changes: 4 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@
"title": "Disabling ETag Generation",
"path": "/docs/api-reference/next.config.js/disabling-etag-generation.md"
},
{
"title": "Disabling HTTP Keep-Alive",
"path": "/docs/api-reference/next.config.js/disabling-http-keep-alive.md"
},
{
"title": "Setting a custom build directory",
"path": "/docs/api-reference/next.config.js/setting-a-custom-build-directory.md"
Expand Down
2 changes: 2 additions & 0 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@ export default async function build(
distDir,
isLikeServerless,
runtimeEnvConfig,
config.httpAgentOptions,
config.i18n?.locales,
config.i18n?.defaultLocale
)
Expand Down Expand Up @@ -812,6 +813,7 @@ export default async function build(
distDir,
isLikeServerless,
runtimeEnvConfig,
config.httpAgentOptions,
config.i18n?.locales,
config.i18n?.defaultLocale,
isPageStaticSpan.id
Expand Down
4 changes: 4 additions & 0 deletions packages/next/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
import * as Log from './output/log'
import { loadComponents } from '../server/load-components'
import { trace } from '../telemetry/trace'
import { setHttpAgentOptions } from '../server/config'
import { NextConfigComplete } from '../server/config-shared'

const fileGzipStats: { [k: string]: Promise<number> | undefined } = {}
const fsStatGzip = (file: string) => {
Expand Down Expand Up @@ -815,6 +817,7 @@ export async function isPageStatic(
distDir: string,
serverless: boolean,
runtimeEnvConfig: any,
httpAgentOptions: NextConfigComplete['httpAgentOptions'],
locales?: string[],
defaultLocale?: string,
parentId?: any
Expand All @@ -833,6 +836,7 @@ export async function isPageStatic(
return isPageStaticSpan.traceAsyncFn(async () => {
try {
require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig)
setHttpAgentOptions(httpAgentOptions)
const components = await loadComponents(distDir, page, serverless)
const mod = components.ComponentMod
const Comp = mod.default || mod
Expand Down
1 change: 1 addition & 0 deletions packages/next/export/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ export default async function exportApp(
disableOptimizedLoading:
nextConfig.experimental.disableOptimizedLoading,
parentSpanId: pageExportSpan.id,
httpAgentOptions: nextConfig.httpAgentOptions,
})

for (const validation of result.ampValidations || []) {
Expand Down
5 changes: 5 additions & 0 deletions packages/next/export/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
import { trace } from '../telemetry/trace'
import { isInAmpMode } from '../shared/lib/amp'
import { resultFromChunks, resultToChunks } from '../server/utils'
import { NextConfigComplete } from '../server/config-shared'
import { setHttpAgentOptions } from '../server/config'

const envConfig = require('../shared/lib/runtime-config')

Expand Down Expand Up @@ -55,6 +57,7 @@ interface ExportPageInput {
optimizeCss: any
disableOptimizedLoading: any
parentSpanId: any
httpAgentOptions: NextConfigComplete['httpAgentOptions']
}

interface ExportPageResults {
Expand Down Expand Up @@ -103,7 +106,9 @@ export default async function exportPage({
optimizeImages,
optimizeCss,
disableOptimizedLoading,
httpAgentOptions,
}: ExportPageInput): Promise<ExportPageResults> {
setHttpAgentOptions(httpAgentOptions)
const exportPageSpan = trace('export-page-worker', parentSpanId)

return exportPageSpan.traceAsyncFn(async () => {
Expand Down
5 changes: 4 additions & 1 deletion packages/next/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export type NextConfig = { [key: string]: any } & {
reactStrictMode?: boolean
publicRuntimeConfig?: { [key: string]: any }
serverRuntimeConfig?: { [key: string]: any }

httpAgentOptions?: { keepAlive?: boolean }
future?: {
/**
* @deprecated this options was moved to the top level
Expand Down Expand Up @@ -153,6 +153,9 @@ export const defaultConfig: NextConfig = {
serverRuntimeConfig: {},
publicRuntimeConfig: {},
reactStrictMode: false,
httpAgentOptions: {
keepAlive: true,
},
experimental: {
cpus: Math.max(
1,
Expand Down
29 changes: 28 additions & 1 deletion packages/next/server/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import chalk from 'chalk'
import findUp from 'next/dist/compiled/find-up'
import { basename, extname } from 'path'
import { Agent as HttpAgent } from 'http'
import { Agent as HttpsAgent } from 'https'
import * as Log from '../build/output/log'
import { CONFIG_FILE, PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants'
import { execOnce } from '../shared/lib/utils'
Expand Down Expand Up @@ -304,6 +306,12 @@ function assignDefaults(userConfig: { [key: string]: any }) {
}
}

// TODO: Change defaultConfig type to NextConfigComplete
// so we don't need "!" here.
setHttpAgentOptions(
result.httpAgentOptions || defaultConfig.httpAgentOptions!
)

if (result.i18n) {
const { i18n } = result
const i18nType = typeof i18n
Expand Down Expand Up @@ -517,11 +525,30 @@ export default async function loadConfig(
}
}

return defaultConfig as NextConfigComplete
const completeConfig = defaultConfig as NextConfigComplete
setHttpAgentOptions(completeConfig.httpAgentOptions)
return completeConfig
}

export function isTargetLikeServerless(target: string) {
const isServerless = target === 'serverless'
const isServerlessTrace = target === 'experimental-serverless-trace'
return isServerless || isServerlessTrace
}

export function setHttpAgentOptions(
options: NextConfigComplete['httpAgentOptions']
) {
if ((global as any).__NEXT_HTTP_AGENT) {
// We only need to assign once because we want
// to resuse the same agent for all requests.
return
}

if (!options) {
throw new Error('Expected config.httpAgentOptions to be an object')
}

;(global as any).__NEXT_HTTP_AGENT = new HttpAgent(options)
;(global as any).__NEXT_HTTPS_AGENT = new HttpsAgent(options)
}
7 changes: 6 additions & 1 deletion packages/next/server/dev/next-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,11 @@ export default class DevServer extends Server {
// from waiting on them for the page to load in dev mode

const __getStaticPaths = async () => {
const { publicRuntimeConfig, serverRuntimeConfig } = this.nextConfig
const {
publicRuntimeConfig,
serverRuntimeConfig,
httpAgentOptions,
} = this.nextConfig
const { locales, defaultLocale } = this.nextConfig.i18n || {}

const paths = await this.staticPathsWorker.loadStaticPaths(
Expand All @@ -577,6 +581,7 @@ export default class DevServer extends Server {
publicRuntimeConfig,
serverRuntimeConfig,
},
httpAgentOptions,
locales,
defaultLocale
)
Expand Down
4 changes: 4 additions & 0 deletions packages/next/server/dev/static-paths-worker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { buildStaticPaths } from '../../build/utils'
import { setHttpAgentOptions } from '../config'
import { NextConfigComplete } from '../config-shared'
import { loadComponents } from '../load-components'
import '../node-polyfill-fetch'

Expand All @@ -14,6 +16,7 @@ export async function loadStaticPaths(
pathname: string,
serverless: boolean,
config: RuntimeConfig,
httpAgentOptions: NextConfigComplete['httpAgentOptions'],
locales?: string[],
defaultLocale?: string
) {
Expand All @@ -25,6 +28,7 @@ export async function loadStaticPaths(

// update work memory runtime-config
require('../../shared/lib/runtime-config').setConfig(config)
setHttpAgentOptions(httpAgentOptions)

const components = await loadComponents(distDir, pathname, serverless)

Expand Down
6 changes: 1 addition & 5 deletions packages/next/server/node-polyfill-fetch.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import fetch, { Headers, Request, Response } from 'node-fetch'
import { Agent as HttpAgent } from 'http'
import { Agent as HttpsAgent } from 'https'

// Polyfill fetch() in the Node.js environment
if (!global.fetch) {
const httpAgent = new HttpAgent({ keepAlive: true })
const httpsAgent = new HttpsAgent({ keepAlive: true })
const agent = ({ protocol }) =>
protocol === 'http:' ? httpAgent : httpsAgent
protocol === 'http:' ? global.__NEXT_HTTP_AGENT : global.__NEXT_HTTPS_AGENT
const fetchWithAgent = (url, opts, ...rest) => {
if (!opts) {
opts = { agent }
Expand Down
23 changes: 23 additions & 0 deletions test/integration/node-fetch-keep-alive/pages/blog/[slug].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export default function Blog(props) {
return <pre id="props">{JSON.stringify(props)}</pre>
}

export async function getStaticProps({ params: { slug } }) {
return { props: { slug } }
}

export async function getStaticPaths() {
const res = await fetch('http://localhost:44001')
const obj = await res.json()
if (obj.connection === 'keep-alive') {
return {
paths: [{ params: { slug: 'first' } }],
fallback: false,
}
}

return {
paths: [],
fallback: false,
}
}
Loading

0 comments on commit f6a2ac7

Please sign in to comment.