diff --git a/crates/next-build-test/nextConfig.json b/crates/next-build-test/nextConfig.json
index 13142e8a57d24..6341127ae3a30 100644
--- a/crates/next-build-test/nextConfig.json
+++ b/crates/next-build-test/nextConfig.json
@@ -53,6 +53,8 @@
"productionBrowserSourceMaps": false,
"optimizeFonts": true,
"excludeDefaultMomentLocales": true,
+ "serverRuntimeConfig": {},
+ "publicRuntimeConfig": {},
"reactProductionProfiling": false,
"reactStrictMode": true,
"httpAgentOptions": {
diff --git a/docs/01-app/02-guides/environment-variables.mdx b/docs/01-app/02-guides/environment-variables.mdx
index 83917914232fd..809d4871cd992 100644
--- a/docs/01-app/02-guides/environment-variables.mdx
+++ b/docs/01-app/02-guides/environment-variables.mdx
@@ -228,6 +228,7 @@ This allows you to use a singular Docker image that can be promoted through mult
**Good to know:**
- You can run code on server startup using the [`register` function](/docs/app/guides/instrumentation).
+- We do not recommend using the [`runtimeConfig`](/docs/pages/api-reference/config/next-config-js/runtime-configuration) option, as this does not work with the standalone output mode. Instead, we recommend [incrementally adopting](/docs/app/guides/migrating/app-router-migration) the App Router if you need this feature.
## Test Environment Variables
diff --git a/docs/01-app/02-guides/self-hosting.mdx b/docs/01-app/02-guides/self-hosting.mdx
index 9e161028cdede..c09f87d050104 100644
--- a/docs/01-app/02-guides/self-hosting.mdx
+++ b/docs/01-app/02-guides/self-hosting.mdx
@@ -79,6 +79,7 @@ This allows you to use a singular Docker image that can be promoted through mult
> **Good to know:**
>
> - You can run code on server startup using the [`register` function](/docs/app/guides/instrumentation).
+> - We do not recommend using the [runtimeConfig](/docs/pages/api-reference/config/next-config-js/runtime-configuration) option, as this does not work with the standalone output mode. Instead, we recommend [incrementally adopting](/docs/app/guides/migrating/app-router-migration) the App Router.
## Caching and ISR
diff --git a/docs/01-app/03-api-reference/05-config/01-next-config-js/output.mdx b/docs/01-app/03-api-reference/05-config/01-next-config-js/output.mdx
index ee010ca94702f..4d068fa1bba8d 100644
--- a/docs/01-app/03-api-reference/05-config/01-next-config-js/output.mdx
+++ b/docs/01-app/03-api-reference/05-config/01-next-config-js/output.mdx
@@ -59,7 +59,7 @@ node .next/standalone/server.js
> **Good to know**:
>
-> - `next.config.js` is read during `next build` and serialized into the `server.js` output file.
+> - `next.config.js` is read during `next build` and serialized into the `server.js` output file. If the legacy [`serverRuntimeConfig` or `publicRuntimeConfig` options](/docs/pages/api-reference/config/next-config-js/runtime-configuration) are being used, the values will be specific to values at build time.
> - If your project needs to listen to a specific port or hostname, you can define `PORT` or `HOSTNAME` environment variables before running `server.js`. For example, run `PORT=8080 HOSTNAME=0.0.0.0 node server.js` to start the server on `http://0.0.0.0:8080`.
diff --git a/docs/02-pages/04-api-reference/04-config/01-next-config-js/runtime-configuration.mdx b/docs/02-pages/04-api-reference/04-config/01-next-config-js/runtime-configuration.mdx
new file mode 100644
index 0000000000000..879394ea38dd4
--- /dev/null
+++ b/docs/02-pages/04-api-reference/04-config/01-next-config-js/runtime-configuration.mdx
@@ -0,0 +1,60 @@
+---
+title: Runtime Config
+description: Add client and server runtime configuration to your Next.js app.
+---
+
+> **Warning:**
+>
+> - **This feature is deprecated.** We recommend using [environment variables](/docs/pages/guides/environment-variables) instead, which also can support reading runtime values.
+> - You can run code on server startup using the [`register` function](/docs/app/guides/instrumentation).
+> - This feature does not work with [Automatic Static Optimization](/docs/pages/building-your-application/rendering/automatic-static-optimization), [Output File Tracing](/docs/pages/api-reference/config/next-config-js/output#automatically-copying-traced-files), or [React Server Components](/docs/app/getting-started/server-and-client-components).
+
+To add runtime configuration to your app, open `next.config.js` and add the `publicRuntimeConfig` and `serverRuntimeConfig` configs:
+
+```js filename="next.config.js"
+module.exports = {
+ serverRuntimeConfig: {
+ // Will only be available on the server side
+ mySecret: 'secret',
+ secondSecret: process.env.SECOND_SECRET, // Pass through env variables
+ },
+ publicRuntimeConfig: {
+ // Will be available on both server and client
+ staticFolder: '/static',
+ },
+}
+```
+
+Place any server-only runtime config under `serverRuntimeConfig`.
+
+Anything accessible to both client and server-side code should be under `publicRuntimeConfig`.
+
+> A page that relies on `publicRuntimeConfig` **must** use `getInitialProps` or `getServerSideProps` or your application must have a [Custom App](/docs/pages/building-your-application/routing/custom-app) with `getInitialProps` to opt-out of [Automatic Static Optimization](/docs/pages/building-your-application/rendering/automatic-static-optimization). Runtime configuration won't be available to any page (or component in a page) without being server-side rendered.
+
+To get access to the runtime configs in your app use `next/config`, like so:
+
+```jsx
+import getConfig from 'next/config'
+import Image from 'next/image'
+
+// Only holds serverRuntimeConfig and publicRuntimeConfig
+const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
+// Will only be available on the server-side
+console.log(serverRuntimeConfig.mySecret)
+// Will be available on both server-side and client-side
+console.log(publicRuntimeConfig.staticFolder)
+
+function MyImage() {
+ return (
+
+
+
+ )
+}
+
+export default MyImage
+```
diff --git a/packages/next/config.d.ts b/packages/next/config.d.ts
new file mode 100644
index 0000000000000..20c292fb467ef
--- /dev/null
+++ b/packages/next/config.d.ts
@@ -0,0 +1,3 @@
+import getConfig from './dist/shared/lib/runtime-config.external'
+export * from './dist/shared/lib/runtime-config.external'
+export default getConfig
diff --git a/packages/next/config.js b/packages/next/config.js
new file mode 100644
index 0000000000000..668ee7c54f0e0
--- /dev/null
+++ b/packages/next/config.js
@@ -0,0 +1 @@
+module.exports = require('./dist/shared/lib/runtime-config.external')
diff --git a/packages/next/index.d.ts b/packages/next/index.d.ts
index ab2ddbddb0c51..8e7cf5d71b9be 100644
--- a/packages/next/index.d.ts
+++ b/packages/next/index.d.ts
@@ -4,6 +4,7 @@
///
///
///
+///
///
///
///
diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts
index 81e24ed175487..e75691ceec0f5 100644
--- a/packages/next/src/build/index.ts
+++ b/packages/next/src/build/index.ts
@@ -1909,7 +1909,9 @@ export default async function build(
}
}
- const { configFileName } = config
+ const { configFileName, publicRuntimeConfig, serverRuntimeConfig } =
+ config
+ const runtimeEnvConfig = { publicRuntimeConfig, serverRuntimeConfig }
const sriEnabled = Boolean(config.experimental.sri?.algorithm)
const nonStaticErrorPageSpan = staticCheckSpan.traceChild(
@@ -1922,6 +1924,7 @@ export default async function build(
(await worker.hasCustomGetInitialProps({
page: '/_error',
distDir,
+ runtimeEnvConfig,
checkingApp: false,
sriEnabled,
}))
@@ -1935,6 +1938,7 @@ export default async function build(
page: '/_error',
distDir,
configFileName,
+ runtimeEnvConfig,
cacheComponents: isAppCacheComponentsEnabled,
authInterrupts: isAuthInterruptsEnabled,
httpAgentOptions: config.httpAgentOptions,
@@ -1954,6 +1958,7 @@ export default async function build(
? worker.hasCustomGetInitialProps({
page: appPageToCheck,
distDir,
+ runtimeEnvConfig,
checkingApp: true,
sriEnabled,
})
@@ -1963,6 +1968,7 @@ export default async function build(
? worker.getDefinedNamedExports({
page: appPageToCheck,
distDir,
+ runtimeEnvConfig,
sriEnabled,
})
: Promise.resolve([])
@@ -2149,6 +2155,7 @@ export default async function build(
originalAppPath,
distDir,
configFileName,
+ runtimeEnvConfig,
httpAgentOptions: config.httpAgentOptions,
locales: config.i18n?.locales,
defaultLocale: config.i18n?.defaultLocale,
diff --git a/packages/next/src/build/templates/edge-ssr.ts b/packages/next/src/build/templates/edge-ssr.ts
index 3aad16017f99e..f1002b14b8463 100644
--- a/packages/next/src/build/templates/edge-ssr.ts
+++ b/packages/next/src/build/templates/edge-ssr.ts
@@ -155,6 +155,12 @@ async function requestHandler(
distDir: '',
crossOrigin: nextConfig.crossOrigin ? nextConfig.crossOrigin : undefined,
largePageDataBytes: nextConfig.experimental.largePageDataBytes,
+ // Only the `publicRuntimeConfig` key is exposed to the client side
+ // It'll be rendered as part of __NEXT_DATA__ on the client side
+ runtimeConfig:
+ Object.keys(nextConfig.publicRuntimeConfig).length > 0
+ ? nextConfig.publicRuntimeConfig
+ : undefined,
isExperimentalCompile: nextConfig.experimental.isExperimentalCompile,
// `htmlLimitedBots` is passed to server as serialized config in string format
diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts
index 4c1e6420e7bc1..680a25971213c 100644
--- a/packages/next/src/build/utils.ts
+++ b/packages/next/src/build/utils.ts
@@ -567,6 +567,7 @@ export async function isPageStatic({
page,
distDir,
configFileName,
+ runtimeEnvConfig,
httpAgentOptions,
locales,
defaultLocale,
@@ -593,6 +594,7 @@ export async function isPageStatic({
cacheComponents: boolean
authInterrupts: boolean
configFileName: string
+ runtimeEnvConfig: any
httpAgentOptions: NextConfigComplete['httpAgentOptions']
locales?: readonly string[]
defaultLocale?: string
@@ -642,6 +644,9 @@ export async function isPageStatic({
const isPageStaticSpan = trace('is-page-static-utils', parentId)
return isPageStaticSpan
.traceAsyncFn(async (): Promise => {
+ ;(
+ require('../shared/lib/runtime-config.external') as typeof import('../shared/lib/runtime-config.external')
+ ).setConfig(runtimeEnvConfig)
setHttpClientAndAgentOptions({
httpAgentOptions,
})
@@ -950,14 +955,20 @@ export function reduceAppConfig(
export async function hasCustomGetInitialProps({
page,
distDir,
+ runtimeEnvConfig,
checkingApp,
sriEnabled,
}: {
page: string
distDir: string
+ runtimeEnvConfig: any
checkingApp: boolean
sriEnabled: boolean
}): Promise {
+ ;(
+ require('../shared/lib/runtime-config.external') as typeof import('../shared/lib/runtime-config.external')
+ ).setConfig(runtimeEnvConfig)
+
const { ComponentMod } = await loadComponents({
distDir,
page: page,
@@ -980,12 +991,17 @@ export async function hasCustomGetInitialProps({
export async function getDefinedNamedExports({
page,
distDir,
+ runtimeEnvConfig,
sriEnabled,
}: {
page: string
distDir: string
+ runtimeEnvConfig: any
sriEnabled: boolean
}): Promise> {
+ ;(
+ require('../shared/lib/runtime-config.external') as typeof import('../shared/lib/runtime-config.external')
+ ).setConfig(runtimeEnvConfig)
const { ComponentMod } = await loadComponents({
distDir,
page: page,
diff --git a/packages/next/src/client/index.tsx b/packages/next/src/client/index.tsx
index 81fdc33f88e95..d63875b6b0590 100644
--- a/packages/next/src/client/index.tsx
+++ b/packages/next/src/client/index.tsx
@@ -20,6 +20,7 @@ import {
urlQueryToSearchParams,
assign,
} from '../shared/lib/router/utils/querystring'
+import { setConfig } from '../shared/lib/runtime-config.external'
import { getURL, loadGetInitialProps, ST } from '../shared/lib/utils'
import type { NextWebVitalsMetric, NEXT_DATA } from '../shared/lib/utils'
import { Portal } from './portal'
@@ -211,6 +212,12 @@ export async function initialize(opts: { devClient?: any } = {}): Promise<{
// So, this is how we do it in the client side at runtime
;(self as any).__next_set_public_path__(`${prefix}/_next/`) //eslint-disable-line
+ // Initialize next/config with the environment configuration
+ setConfig({
+ serverRuntimeConfig: {},
+ publicRuntimeConfig: initialData.runtimeConfig || {},
+ })
+
asPath = getURL()
// make sure not to attempt stripping basePath for 404s
diff --git a/packages/next/src/export/index.ts b/packages/next/src/export/index.ts
index 0823be6f13028..eaa88b623f651 100644
--- a/packages/next/src/export/index.ts
+++ b/packages/next/src/export/index.ts
@@ -422,6 +422,12 @@ async function exportAppImpl(
nextConfig.experimental.enablePrerenderSourceMaps === true,
}
+ const { publicRuntimeConfig } = nextConfig
+
+ if (Object.keys(publicRuntimeConfig).length > 0) {
+ renderOpts.runtimeConfig = publicRuntimeConfig
+ }
+
// We need this for server rendering the Link component.
;(globalThis as any).__NEXT_DATA__ = {
nextExport: true,
diff --git a/packages/next/src/export/types.ts b/packages/next/src/export/types.ts
index 8a27f76b269ec..795202731dd48 100644
--- a/packages/next/src/export/types.ts
+++ b/packages/next/src/export/types.ts
@@ -54,6 +54,7 @@ export interface ExportPageInput {
ampValidatorPath?: string
trailingSlash?: boolean
buildExport?: boolean
+ serverRuntimeConfig: { [key: string]: any }
subFolders?: boolean
optimizeCss: any
disableOptimizedLoading: any
diff --git a/packages/next/src/export/worker.ts b/packages/next/src/export/worker.ts
index c29ad8f1eae0c..2a7f0d265aa0d 100644
--- a/packages/next/src/export/worker.ts
+++ b/packages/next/src/export/worker.ts
@@ -50,6 +50,10 @@ import type { PagesRenderContext, PagesSharedContext } from '../server/render'
import type { AppSharedContext } from '../server/app-render/app-render'
import { MultiFileWriter } from '../lib/multi-file-writer'
import { createRenderResumeDataCache } from '../server/resume-data-cache/resume-data-cache'
+
+const envConfig =
+ require('../shared/lib/runtime-config.external') as typeof import('../shared/lib/runtime-config.external')
+
;(globalThis as any).__NEXT_DATA__ = {
nextExport: true,
}
@@ -71,6 +75,7 @@ async function exportPageImpl(
distDir,
pagesDataDir,
buildExport = false,
+ serverRuntimeConfig,
subFolders = false,
optimizeCss,
disableOptimizedLoading,
@@ -191,6 +196,11 @@ async function exportPageImpl(
addRequestMeta(req, 'isLocaleDomain', true)
}
+ envConfig.setConfig({
+ serverRuntimeConfig,
+ publicRuntimeConfig: commonRenderOpts.runtimeConfig,
+ })
+
const getHtmlFilename = (p: string) =>
subFolders ? `${p}${sep}index.html` : `${p}.html`
@@ -406,6 +416,7 @@ export async function exportPages(
ampValidatorPath:
nextConfig.experimental.amp?.validator || undefined,
trailingSlash: nextConfig.trailingSlash,
+ serverRuntimeConfig: nextConfig.serverRuntimeConfig,
subFolders: nextConfig.trailingSlash && !options.buildExport,
buildExport: options.buildExport,
optimizeCss: nextConfig.experimental.optimizeCss,
diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts
index 35f7ee4a76b03..2c5bd0438d002 100644
--- a/packages/next/src/server/base-server.ts
+++ b/packages/next/src/server/base-server.ts
@@ -57,6 +57,7 @@ import {
UNDERSCORE_NOT_FOUND_ROUTE_ENTRY,
} from '../shared/lib/constants'
import { isDynamicRoute } from '../shared/lib/router/utils'
+import { setConfig } from '../shared/lib/runtime-config.external'
import { execOnce } from '../shared/lib/utils'
import { isBlockedPage } from './utils'
import { getBotType, isBot } from '../shared/lib/router/utils/is-bot'
@@ -474,7 +475,14 @@ export default abstract class Server<
? new LocaleRouteNormalizer(this.i18nProvider)
: undefined
- const { assetPrefix, generateEtags } = this.nextConfig
+ // Only serverRuntimeConfig needs the default
+ // publicRuntimeConfig gets it's default in client/index.js
+ const {
+ serverRuntimeConfig = {},
+ publicRuntimeConfig,
+ assetPrefix,
+ generateEtags,
+ } = this.nextConfig
this.buildId = this.getBuildId()
// this is a hack to avoid Webpack knowing this is equal to this.minimalMode
@@ -543,6 +551,12 @@ export default abstract class Server<
? this.nextConfig.crossOrigin
: undefined,
largePageDataBytes: this.nextConfig.experimental.largePageDataBytes,
+ // Only the `publicRuntimeConfig` key is exposed to the client side
+ // It'll be rendered as part of __NEXT_DATA__ on the client side
+ runtimeConfig:
+ Object.keys(publicRuntimeConfig).length > 0
+ ? publicRuntimeConfig
+ : undefined,
isExperimentalCompile: this.nextConfig.experimental.isExperimentalCompile,
// `htmlLimitedBots` is passed to server as serialized config in string format
@@ -569,6 +583,12 @@ export default abstract class Server<
reactMaxHeadersLength: this.nextConfig.reactMaxHeadersLength,
}
+ // Initialize next/config with the environment configuration
+ setConfig({
+ serverRuntimeConfig,
+ publicRuntimeConfig,
+ })
+
this.pagesManifest = this.getPagesManifest()
this.appPathsManifest = this.getAppPathsManifest()
this.appPathRoutes = this.getAppPathRoutes()
diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts
index dc06321a79301..adfedff641121 100644
--- a/packages/next/src/server/config-schema.ts
+++ b/packages/next/src/server/config-schema.ts
@@ -653,6 +653,7 @@ export const configSchema: zod.ZodType = z.lazy(() =>
pageExtensions: z.array(z.string()).min(1).optional(),
poweredByHeader: z.boolean().optional(),
productionBrowserSourceMaps: z.boolean().optional(),
+ publicRuntimeConfig: z.record(z.string(), z.any()).optional(),
reactProductionProfiling: z.boolean().optional(),
reactStrictMode: z.boolean().nullable().optional(),
reactMaxHeadersLength: z.number().nonnegative().int().optional(),
@@ -685,6 +686,7 @@ export const configSchema: zod.ZodType = z.lazy(() =>
.catchall(z.any())
.optional(),
serverExternalPackages: z.array(z.string()).optional(),
+ serverRuntimeConfig: z.record(z.string(), z.any()).optional(),
skipMiddlewareUrlNormalize: z.boolean().optional(),
skipTrailingSlashRedirect: z.boolean().optional(),
staticPageGenerationTimeout: z.number().optional(),
diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts
index 02f2867f10449..23e6391b7b53f 100644
--- a/packages/next/src/server/config-shared.ts
+++ b/packages/next/src/server/config-shared.ts
@@ -1115,6 +1115,20 @@ export interface NextConfig {
*/
reactMaxHeadersLength?: number
+ /**
+ * Add public (in browser) runtime configuration to your app
+ *
+ * @see [Runtime configuration](https://nextjs.org/docs/pages/api-reference/config/next-config-js/runtime-configuration
+ */
+ publicRuntimeConfig?: { [key: string]: any }
+
+ /**
+ * Add server runtime configuration to your app
+ *
+ * @see [Runtime configuration](https://nextjs.org/docs/pages/api-reference/config/next-config-js/runtime-configuration
+ */
+ serverRuntimeConfig?: { [key: string]: any }
+
/**
* Next.js enables HTTP Keep-Alive by default.
* You may want to disable HTTP Keep-Alive for certain `fetch()` calls or globally.
@@ -1353,6 +1367,8 @@ export const defaultConfig = Object.freeze({
i18n: null,
productionBrowserSourceMaps: false,
excludeDefaultMomentLocales: true,
+ serverRuntimeConfig: {},
+ publicRuntimeConfig: {},
reactProductionProfiling: false,
reactStrictMode: null,
reactMaxHeadersLength: 6000,
diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts
index e3febaca236cf..df7f323082e5c 100644
--- a/packages/next/src/server/dev/next-dev-server.ts
+++ b/packages/next/src/server/dev/next-dev-server.ts
@@ -804,7 +804,12 @@ export default class DevServer extends Server {
// from waiting on them for the page to load in dev mode
const __getStaticPaths = async () => {
- const { configFileName, httpAgentOptions } = this.nextConfig
+ const {
+ configFileName,
+ publicRuntimeConfig,
+ serverRuntimeConfig,
+ httpAgentOptions,
+ } = this.nextConfig
const { locales, defaultLocale } = this.nextConfig.i18n || {}
const staticPathsWorker = this.getStaticPathsWorker()
@@ -816,6 +821,8 @@ export default class DevServer extends Server {
config: {
pprConfig: this.nextConfig.experimental.ppr,
configFileName,
+ publicRuntimeConfig,
+ serverRuntimeConfig,
cacheComponents: Boolean(
this.nextConfig.experimental.cacheComponents
),
diff --git a/packages/next/src/server/dev/static-paths-worker.ts b/packages/next/src/server/dev/static-paths-worker.ts
index 72023e2e42a70..227933c08fe22 100644
--- a/packages/next/src/server/dev/static-paths-worker.ts
+++ b/packages/next/src/server/dev/static-paths-worker.ts
@@ -25,6 +25,8 @@ import type { AppRouteRouteModule } from '../route-modules/app-route/module'
type RuntimeConfig = {
pprConfig: ExperimentalPPRConfig | undefined
configFileName: string
+ publicRuntimeConfig: { [key: string]: any }
+ serverRuntimeConfig: { [key: string]: any }
cacheComponents: boolean
}
@@ -90,6 +92,9 @@ export async function loadStaticPaths({
})
// update work memory runtime-config
+ ;(
+ require('../../shared/lib/runtime-config.external') as typeof import('../../shared/lib/runtime-config.external')
+ ).setConfig(config)
setHttpClientAndAgentOptions({
httpAgentOptions,
})
diff --git a/packages/next/src/server/lib/router-utils/router-server-context.ts b/packages/next/src/server/lib/router-utils/router-server-context.ts
index eaeafcc6eb56a..568be2acae75f 100644
--- a/packages/next/src/server/lib/router-utils/router-server-context.ts
+++ b/packages/next/src/server/lib/router-utils/router-server-context.ts
@@ -28,6 +28,8 @@ export type RouterServerContext = Record<
parsedUrl?: UrlWithParsedQuery,
setHeaders?: boolean
) => Promise
+ // current loaded public runtime config
+ publicRuntimeConfig?: NextConfigComplete['publicRuntimeConfig']
// exposing nextConfig for dev mode specifically
nextConfig?: NextConfigComplete
// whether running in custom server mode
diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx
index 5a93b9bb0c015..6d9df62d3bf97 100644
--- a/packages/next/src/server/render.tsx
+++ b/packages/next/src/server/render.tsx
@@ -243,6 +243,7 @@ function renderPageTree(
export type RenderOptsPartial = {
canonicalBase: string
+ runtimeConfig?: { [key: string]: any }
assetPrefix?: string
err?: Error | null
nextExport?: boolean
@@ -1490,6 +1491,7 @@ export async function renderToHTMLImpl(
domainLocales,
locale,
locales,
+ runtimeConfig,
} = renderOpts
const htmlProps: HtmlProps = {
__NEXT_DATA__: {
@@ -1498,6 +1500,7 @@ export async function renderToHTMLImpl(
query, // querystring parsed / passed by the user
buildId: sharedContext.buildId,
assetPrefix: assetPrefix === '' ? undefined : assetPrefix, // send assetPrefix to the client side when configured, otherwise don't sent in the resulting HTML
+ runtimeConfig, // runtimeConfig if provided, otherwise don't sent in the resulting HTML
nextExport: nextExport === true ? true : undefined, // If this is a page exported by `next export`
autoExport: isAutoExport === true ? true : undefined, // If this is an auto exported page
isFallback,
diff --git a/packages/next/src/server/route-modules/pages/pages-handler.ts b/packages/next/src/server/route-modules/pages/pages-handler.ts
index a113676e97c46..686fcc80b3dea 100644
--- a/packages/next/src/server/route-modules/pages/pages-handler.ts
+++ b/packages/next/src/server/route-modules/pages/pages-handler.ts
@@ -204,6 +204,10 @@ export const getHandler = ({
query: hasStaticProps ? {} : originalQuery,
})
+ const publicRuntimeConfig: Record =
+ routerServerContext?.publicRuntimeConfig ||
+ nextConfig.publicRuntimeConfig
+
const handleResponse = async (span?: Span) => {
const responseGenerator: ResponseGenerator = async ({
previousCacheEntry,
@@ -278,6 +282,12 @@ export const getHandler = ({
nextConfig.experimental.disableOptimizedLoading,
largePageDataBytes:
nextConfig.experimental.largePageDataBytes,
+ // Only the `publicRuntimeConfig` key is exposed to the client side
+ // It'll be rendered as part of __NEXT_DATA__ on the client side
+ runtimeConfig:
+ Object.keys(publicRuntimeConfig).length > 0
+ ? publicRuntimeConfig
+ : undefined,
isExperimentalCompile,
diff --git a/packages/next/src/shared/lib/runtime-config.external.ts b/packages/next/src/shared/lib/runtime-config.external.ts
new file mode 100644
index 0000000000000..b4a48a362d638
--- /dev/null
+++ b/packages/next/src/shared/lib/runtime-config.external.ts
@@ -0,0 +1,9 @@
+let runtimeConfig: any
+
+export default () => {
+ return runtimeConfig
+}
+
+export function setConfig(configValue: any): void {
+ runtimeConfig = configValue
+}
diff --git a/packages/next/src/shared/lib/utils.ts b/packages/next/src/shared/lib/utils.ts
index 7a5ff10744ae9..abc5d6da627f3 100644
--- a/packages/next/src/shared/lib/utils.ts
+++ b/packages/next/src/shared/lib/utils.ts
@@ -91,6 +91,7 @@ export type NEXT_DATA = {
query: ParsedUrlQuery
buildId: string
assetPrefix?: string
+ runtimeConfig?: { [key: string]: any }
nextExport?: boolean
autoExport?: boolean
isFallback?: boolean
diff --git a/test/integration/config-mjs/next.config.mjs b/test/integration/config-mjs/next.config.mjs
index a26eb8fbda653..84b5831fc866e 100644
--- a/test/integration/config-mjs/next.config.mjs
+++ b/test/integration/config-mjs/next.config.mjs
@@ -4,6 +4,12 @@ export default {
maxInactiveAge: 1000 * 60 * 60,
},
poweredByHeader: false,
+ serverRuntimeConfig: {
+ mySecret: 'secret',
+ },
+ publicRuntimeConfig: {
+ staticFolder: '/static',
+ },
env: {
customVar: 'hello',
},
diff --git a/test/integration/config-mjs/pages/next-config.js b/test/integration/config-mjs/pages/next-config.js
index da615fabbe425..945aeba52da33 100644
--- a/test/integration/config-mjs/pages/next-config.js
+++ b/test/integration/config-mjs/pages/next-config.js
@@ -1,5 +1,10 @@
+import getConfig from 'next/config'
+const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
+
export default () => (