nextjs - ✅(Solved) Fix unstable_getResponseFromNextConfig does not resolve async function configs [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#92500Fetched 2026-04-09 07:49:57
View on GitHub
Comments
0
Participants
1
Timeline
7
Reactions
0
Participants
Timeline (top)
referenced ×3cross-referenced ×2closed ×1labeled ×1

Root Cause

The root cause is in next/dist/experimental/testing/server/config-testing-utils.js line 59:

Fix Action

Fixed

PR fix notes

PR #92501: fix: resolve function configs in unstable_getResponseFromNextConfig

Description (problem / solution / changelog)

Summary

  • unstable_getResponseFromNextConfig now resolves async function configs before passing them to loadCustomRoutes
  • Uses normalizeConfig — the same utility Next.js already uses at build time to handle (phase, ctx) => config wrappers

Problem

Plugins that wrap next.config.mjs in an async function (e.g. withSentry, withWorkflow, withMDX) caused unstable_getResponseFromNextConfig to silently return null for all rewrites, redirects, and headers. The function config was passed directly to loadCustomRoutes, which accessed .rewrites/.redirects on a function object.

Fix

Call normalizeConfig('phase-production-build', nextConfig) before loadCustomRoutes. This is a 4-line change that makes the testing utility consistent with how Next.js resolves configs at build time.

Test

Added a test that passes an async function config with rewrites and redirects, verifying both are matched correctly.

Fixes #92500

Changed files

  • packages/next/src/experimental/testing/server/config-testing-utils.test.ts (modified, +39/-0)
  • packages/next/src/experimental/testing/server/config-testing-utils.ts (modified, +10/-3)

PR #92514: fix(testing): resolve async function configs in unstable_getResponseFromNextConfig

Description (problem / solution / changelog)

What?

Plugin wrappers like @sentry/nextjs, @vercel/workflow wrap the user config in an async function:

// next.config.mjs
function withPlugin(config) {
  return async (phase, { defaultConfig }) => ({ ...config })
}
export default withPlugin({ rewrites() { ... } })

When such a config is passed to unstable_getResponseFromNextConfig, the config is passed directly to loadCustomRoutes, which immediately accesses config.rewrites, config.redirects, etc. Since the value is still a function, all those properties are undefined and every route matcher returns null.

Why?

loadCustomRoutes expects a plain config object. The main Next.js config loader already calls normalizeConfig(phase, rawExport) which properly resolves both function and Promise exports before use.

How?

Call normalizeConfig(PHASE_PRODUCTION_BUILD, nextConfig) before passing to loadCustomRoutes. This is the same helper used by the Next.js config loading pipeline to unwrap function/async function configs.

Also broadened the TypeScript parameter type to accept function-wrapped configs without needing an as any cast in user code.

Test

Added two test cases in config-testing-utils.test.ts:

  1. Synchronous function config (phase, ctx) => config
  2. Async function config (plugin wrapper pattern) (phase, ctx) => Promise<config>

Closes #92500

Changed files

  • packages/next/src/experimental/testing/server/config-testing-utils.test.ts (modified, +32/-0)
  • packages/next/src/experimental/testing/server/config-testing-utils.ts (modified, +13/-2)

Code Example

// next.config.mjs
function withExamplePlugin(nextConfig) {
  return async (phase, { defaultConfig }) => {
    return { ...nextConfig };
  };
}

export default withExamplePlugin({
  async rewrites() {
    return [{ source: '/old', destination: '/new' }];
  },
});

---

import { unstable_getResponseFromNextConfig, getRewrittenUrl } from 'next/experimental/testing/server';

const nextConfig = (await import('./next.config.mjs')).default;
// typeof nextConfig === 'function'

const response = await unstable_getResponseFromNextConfig({ url: '/old', nextConfig });
getRewrittenUrl(response); // null — expected '/new'

---

let resolved = nextConfig;
if (typeof resolved === 'function') {
  resolved = await resolved('phase-production-build', { defaultConfig: {} });
}

---

Operating System:
  Platform: darwin
  Arch: arm64
Next.js: 16.2.1-canary.24

---

const routes = await loadCustomRoutes(nextConfig);
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/vercel/next-config-fn-repro

To Reproduce

  1. Export an async function config from next.config.mjs (the standard (phase, ctx) => config pattern used by plugins like withSentry, withWorkflow, etc.):
// next.config.mjs
function withExamplePlugin(nextConfig) {
  return async (phase, { defaultConfig }) => {
    return { ...nextConfig };
  };
}

export default withExamplePlugin({
  async rewrites() {
    return [{ source: '/old', destination: '/new' }];
  },
});
  1. Use unstable_getResponseFromNextConfig to test rewrites/redirects:
import { unstable_getResponseFromNextConfig, getRewrittenUrl } from 'next/experimental/testing/server';

const nextConfig = (await import('./next.config.mjs')).default;
// typeof nextConfig === 'function'

const response = await unstable_getResponseFromNextConfig({ url: '/old', nextConfig });
getRewrittenUrl(response); // null — expected '/new'

Current vs. Expected behavior

Current: unstable_getResponseFromNextConfig passes the config directly to loadCustomRoutes, which accesses config.rewrites, config.redirects, etc. Since the config is a function (not an object), these properties are undefined and every route match returns null.

Expected: The utility should resolve function configs before passing them to loadCustomRoutes, the same way Next.js itself does at build time:

let resolved = nextConfig;
if (typeof resolved === 'function') {
  resolved = await resolved('phase-production-build', { defaultConfig: {} });
}

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
Next.js: 16.2.1-canary.24

Which area(s) are affected? (Select all that apply)

Testing utilities (next/experimental/testing/server)

Which stage(s) are affected? (Select all that apply)

Other (N/A — testing utility)

Additional context

The root cause is in next/dist/experimental/testing/server/config-testing-utils.js line 59:

const routes = await loadCustomRoutes(nextConfig);

loadCustomRoutes expects a plain config object. It immediately accesses config.assetPrefix, config.basePath, config.rewrites, etc.

This affects any project using Next.js config plugins that return async function wrappers — a common pattern across the ecosystem (@sentry/nextjs, @vercel/workflow, etc.).

extent analysis

TL;DR

Modify unstable_getResponseFromNextConfig to resolve function configs before passing them to loadCustomRoutes.

Guidance

  • Identify if the nextConfig is a function and resolve it to an object using await nextConfig('phase-production-build', { defaultConfig: {} }) if necessary.
  • Update next/dist/experimental/testing/server/config-testing-utils.js line 59 to handle function configs by resolving them before calling loadCustomRoutes.
  • Verify the fix by checking if getRewrittenUrl(response) returns the expected URL /new after applying the changes.
  • Consider creating a pull request or issue in the Next.js repository to address this issue in the core library.

Example

const resolvedConfig = typeof nextConfig === 'function'
  ? await nextConfig('phase-production-build', { defaultConfig: {} })
  : nextConfig;
const response = await unstable_getResponseFromNextConfig({ url: '/old', nextConfig: resolvedConfig });

Notes

This fix assumes that the nextConfig function is correctly implemented and returns a valid config object when resolved. Additional error handling may be necessary to ensure robustness.

Recommendation

Apply workaround by resolving function configs before passing them to loadCustomRoutes, as this is a testing utility issue and not a direct problem with the Next.js core library.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING