firebase / firebase/apphosting-adapters

adapter-nextjs: next.config.mts is rejected, and the failure leaves the config renamed away

Open
#680 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
478
Forks
1.5k
Avg merge
3d 22h
Merged PRs (30d)
2

Description

## Summary

`@apphosting/adapter-nextjs` rejects `next.config.mts`, a filename Next.js itself supports. `overrideNextConfig` switches on the config's file extension and throws for anything that is not `.js`, `.mjs`, or `.ts`. Since Next's own `loadConfig` discovers `.mts`, the adapter reaches that throw on any App Hosting build of a project that uses it.

Two aggravating details:

1. **The rename happens before the extension check**, and `overrideNextConfig` is called *outside* the `try…finally` that runs `restoreNextConfig`. The failure therefore leaves the project with its config renamed to `next.config.original.mts` and no `next.config.mts`, so no rollback.
2. Users have good reason to expect `.mts` to work: Next.js lists it in `CONFIG_FILES`, and Firebase CLI [15.9.1](https://firebase.google.com/support/release-notes/cli#version_1591_-_march_09_2026) announced "Added support for `next.config.ts` and `next.config.mts` in Next.js deployments". That support lives in `src/frameworks/next/` (the Hosting web frameworks path), so it does not reach App Hosting, but the release note reads as blanket support.

## Reproduction

Against the published package, no App Hosting deploy required:

```js
// npm i @apphosting/adapter-nextjs@14.0.21
// proj/next.config.mts contains a normal `export default nextConfig`
import { overrideNextConfig } from "@apphosting/adapter-nextjs/dist/overrides.js";
await overrideNextConfig("/proj", "next.config.mts");
```

```
before: next.config.mts

Overriding Next Config to add configs optmized for Firebase App Hosting
Error overriding Next.js config: Error: Unsupported file extension for Next Config: ".mts", please use ".js", ".mjs", or ".ts"

after: next.config.original.mts <- config renamed away, nothing written back
```

Verified on adapter 14.0.21 (current `latest`), Node 22, Next 16.3.1.

## Root cause

`packages/@apphosting/adapter-nextjs/src/overrides.ts` - the rename precedes the `switch`, whose `default` throws:

```ts
await renamePromise(configPath, originalPath); // happens first
switch (fileExtension) {
case ".js": ...
case ".mjs": ...
case ".ts": ...
default: throw new Error(`Unsupported file extension for Next Config: "${fileExtension}", ...`);
}
```

`packages/@apphosting/adapter-nextjs/src/bin/build.ts` - the restore only guards the build, not the override:

```ts
if (await exists(nextConfigPath)) {
await overrideNextConfig(root, nextConfig.configFileName); // outside the try
await validateNextConfigOverride(...);
}
try {
await runBuild();
...
} finally {
await restoreNextConfig(root, nextConfig.configFileName);
}
```

`nextConfig.configFileName` comes from Next's own `loadConfig`, and Next 16.3.1's `CONFIG_FILES` is `['next.config.js', 'next.config.mjs', 'next.config.ts', 'next.config.mts']`, so `.mts` is discovered and passed straight into the unsupported branch.

## Impact

- Any App Hosting Next.js app using `next.config.mts` fails its build.
- In Cloud Build the mutated workspace is ephemeral, so the rename is mostly invisible there. But the adapter is runnable locally (`npm exec apphosting-adapter-nextjs-build`), which is a reasonable way to reproduce adapter-only build failures, and there it mutates the developer's working tree and does not put it back.

## Suggested fix

1. Handle `.mts` alongside `.mjs`. `.mts` is ESM by extension regardless of the package's `type`, so the generated file needs the `export default config;` tail (emitting the `module.exports = config;` tail for it would produce a config with no default export. The import specifier needs a decision: the `.ts` case strips the extension, and whatever holds for `.ts` presumably holds for `.mts`.)
2. Independently of `.mts`: move the rename after the extension check, or bring `overrideNextConfig` inside the `try…finally`, so *any* failure after the rename restores the original config. Today a `writeFile` failure would strand the project the same way.

Contributor guide

Open the contributing guide

Research direction

Start with packages/@apphosting/adapter-nextjs/src/overrides.ts and src/bin/build.ts, then reproduce with the published-package overrideNextConfig command using a next.config.mts file. Check how the existing .mjs and .ts branches handle imports and generated config, and verify that .mts builds successfully and the original config is restored after any override failure.

Written by the indexing model from the issue text.

Assessment

Tech stack
nextjs, typescript
Domain
build-system, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.