Skip to content

Commit 409b281

Browse files
authored
Improve Proxy invalid export error message (#84887)
Improve invalid Proxy/Middleware export error message to: ``` > Build error occurred Error: The file "./proxy.ts" must export a function, either as a default export or as a named "proxy" export. This function is what Next.js runs for every request handled by this proxy (previously called middleware). Why this happens: - You are migrating from `middleware` to `proxy`, but haven't updated the exported function. - The file exists but doesn't export a function. - The export is not a function (e.g., an object or constant). - There's a syntax error preventing the export from being recognized. To fix it: - Ensure this file has either a default or "proxy" function export. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy ``` Turbopack: ``` Error: Turbopack build failed with 1 errors: ./proxy.ts Proxy is missing expected function export name This function is what Next.js runs for every request handled by this proxy (previously called middleware). Why this happens: - You are migrating from `middleware` to `proxy`, but haven't updated the exported function. - The file exists but doesn't export a function. - The export is not a function (e.g., an object or constant). - There's a syntax error preventing the export from being recognized. To fix it: - Ensure this file has either a default or "proxy" function export. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy ``` Closes NEXT-4741
1 parent e226233 commit 409b281

File tree

6 files changed

+118
-28
lines changed

6 files changed

+118
-28
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/next-core/src/middleware.rs

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -139,23 +139,48 @@ impl Issue for MiddlewareMissingExportIssue {
139139
}
140140

141141
#[turbo_tasks::function]
142-
fn title(&self) -> Vc<StyledString> {
143-
let file_name = self.file_path.file_name();
144-
145-
StyledString::Line(vec![
146-
StyledString::Text(rcstr!("The ")),
147-
StyledString::Code(self.file_type.clone()),
148-
StyledString::Text(rcstr!(" file \"")),
149-
StyledString::Code(format!("./{}", file_name).into()),
150-
StyledString::Text(rcstr!("\" must export a function named ")),
151-
StyledString::Code(format!("`{}`", self.function_name).into()),
152-
StyledString::Text(rcstr!(" or a default function.")),
153-
])
154-
.cell()
142+
async fn title(&self) -> Result<Vc<StyledString>> {
143+
let title_text = format!(
144+
"{} is missing expected function export name",
145+
self.file_type
146+
);
147+
148+
Ok(StyledString::Text(title_text.into()).cell())
155149
}
156150

157151
#[turbo_tasks::function]
158-
fn description(&self) -> Vc<OptionStyledString> {
159-
Vc::cell(None)
152+
async fn description(&self) -> Result<Vc<OptionStyledString>> {
153+
let type_description = if self.file_type == "Proxy" {
154+
"proxy (previously called middleware)"
155+
} else {
156+
"middleware"
157+
};
158+
159+
let migration_bullet = if self.file_type == "Proxy" {
160+
"- You are migrating from `middleware` to `proxy`, but haven't updated the exported \
161+
function.\n"
162+
} else {
163+
""
164+
};
165+
166+
// Rest of the message goes in description to avoid formatIssue indentation
167+
let description_text = format!(
168+
"This function is what Next.js runs for every request handled by this {}.\n\n\
169+
Why this happens:\n\
170+
{}\
171+
- The file exists but doesn't export a function.\n\
172+
- The export is not a function (e.g., an object or constant).\n\
173+
- There's a syntax error preventing the export from being recognized.\n\n\
174+
To fix it:\n\
175+
- Ensure this file has either a default or \"{}\" function export.\n\n\
176+
Learn more: https://nextjs.org/docs/messages/middleware-to-proxy",
177+
type_description,
178+
migration_bullet,
179+
self.function_name
180+
);
181+
182+
Ok(Vc::cell(Some(
183+
StyledString::Text(description_text.into()).resolved_cell(),
184+
)))
160185
}
161186
}

packages/next/errors.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,5 +900,6 @@
900900
"899": "Both \"%s\" and \"%s\" files are detected. Please use \"%s\" instead. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy",
901901
"900": "Both %s file \"./%s\" and %s file \"./%s\" are detected. Please use \"./%s\" only. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy",
902902
"901": "Invalid \"cacheHandlers\" provided, expected an object e.g. { default: '/my-handler.js' }, received %s",
903-
"902": "Invalid handler fields configured for \"cacheHandlers\":\\n%s"
903+
"902": "Invalid handler fields configured for \"cacheHandlers\":\\n%s",
904+
"903": "The file \"%s\" must export a function, either as a default export or as a named \"%s\" export.\\nThis function is what Next.js runs for every request handled by this %s.\\n\\nWhy this happens:\\n%s- The file exists but doesn't export a function.\\n- The export is not a function (e.g., an object or constant).\\n- There's a syntax error preventing the export from being recognized.\\n\\nTo fix it:\\n- Ensure this file has either a default or \"%s\" function export.\\n\\nLearn more: https://nextjs.org/docs/messages/middleware-to-proxy"
904905
}

packages/next/src/build/analysis/get-page-static-info.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { NextConfig } from '../../server/config-shared'
22
import type { RouteHas } from '../../lib/load-custom-routes'
33

44
import { promises as fs } from 'fs'
5-
import { basename } from 'path'
5+
import { relative } from 'path'
66
import { LRUCache } from '../../server/lib/lru-cache'
77
import {
88
extractExportedConstValue,
@@ -329,7 +329,7 @@ function validateMiddlewareProxyExports({
329329
return
330330
}
331331

332-
const fileName = isProxy ? 'proxy' : 'middleware'
332+
const fileName = isProxy ? PROXY_FILENAME : MIDDLEWARE_FILENAME
333333

334334
// Parse AST to get export info (since checkExports doesn't return middleware/proxy info)
335335
let hasDefaultExport = false
@@ -396,9 +396,22 @@ function validateMiddlewareProxyExports({
396396
(isMiddleware && hasMiddlewareExport) ||
397397
(isProxy && hasProxyExport)
398398

399+
const relativeFilePath = `./${relative(process.cwd(), pageFilePath)}`
400+
399401
if (!hasValidExport) {
400402
throw new Error(
401-
`The ${fileName === 'proxy' ? 'Proxy' : 'Middleware'} file "./${basename(pageFilePath)}" must export a function named \`${fileName}\` or a default function.`
403+
`The file "${relativeFilePath}" must export a function, either as a default export or as a named "${fileName}" export.\n` +
404+
`This function is what Next.js runs for every request handled by this ${fileName === 'proxy' ? 'proxy (previously called middleware)' : 'middleware'}.\n\n` +
405+
`Why this happens:\n` +
406+
(isProxy
407+
? "- You are migrating from `middleware` to `proxy`, but haven't updated the exported function.\n"
408+
: '') +
409+
`- The file exists but doesn't export a function.\n` +
410+
`- The export is not a function (e.g., an object or constant).\n` +
411+
`- There's a syntax error preventing the export from being recognized.\n\n` +
412+
`To fix it:\n` +
413+
`- Ensure this file has either a default or "${fileName}" function export.\n\n` +
414+
`Learn more: https://nextjs.org/docs/messages/middleware-to-proxy`
402415
)
403416
}
404417
}

packages/next/src/build/templates/middleware.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,25 @@ const isProxy = page === '/proxy' || page === '/src/proxy'
1919
const handler = (isProxy ? mod.proxy : mod.middleware) || mod.default
2020

2121
if (typeof handler !== 'function') {
22+
const fileName = isProxy ? 'proxy' : 'middleware'
23+
// Webpack starts the path with "." as relative, but Turbopack does not.
24+
const resolvedRelativeFilePath = relativeFilePath.startsWith('.')
25+
? relativeFilePath
26+
: `./${relativeFilePath}`
27+
2228
throw new Error(
23-
`The ${isProxy ? 'Proxy' : 'Middleware'} file "${relativeFilePath.startsWith('.') ? relativeFilePath : `./${relativeFilePath}`}" must export a function named \`${isProxy ? 'proxy' : 'middleware'}\` or a default function.`
29+
`The file "${resolvedRelativeFilePath}" must export a function, either as a default export or as a named "${fileName}" export.\n` +
30+
`This function is what Next.js runs for every request handled by this ${fileName === 'proxy' ? 'proxy (previously called middleware)' : 'middleware'}.\n\n` +
31+
`Why this happens:\n` +
32+
(isProxy
33+
? "- You are migrating from `middleware` to `proxy`, but haven't updated the exported function.\n"
34+
: '') +
35+
`- The file exists but doesn't export a function.\n` +
36+
`- The export is not a function (e.g., an object or constant).\n` +
37+
`- There's a syntax error preventing the export from being recognized.\n\n` +
38+
`To fix it:\n` +
39+
`- Ensure this file has either a default or "${fileName}" function export.\n\n` +
40+
`Learn more: https://nextjs.org/docs/messages/middleware-to-proxy`
2441
)
2542
}
2643

test/e2e/app-dir/proxy-missing-export/proxy-missing-export.test.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,18 @@ import { nextTestSetup } from 'e2e-utils'
22
import { join } from 'node:path'
33
import { writeFile } from 'node:fs/promises'
44

5-
const errorMessage =
6-
'The Proxy file "./proxy.ts" must export a function named `proxy` or a default function.'
5+
const errorMessage = `This function is what Next.js runs for every request handled by this proxy (previously called middleware).
6+
7+
Why this happens:
8+
- You are migrating from \`middleware\` to \`proxy\`, but haven't updated the exported function.
9+
- The file exists but doesn't export a function.
10+
- The export is not a function (e.g., an object or constant).
11+
- There's a syntax error preventing the export from being recognized.
12+
13+
To fix it:
14+
- Ensure this file has either a default or "proxy" function export.
15+
16+
Learn more: https://nextjs.org/docs/messages/middleware-to-proxy`
717

818
describe('proxy-missing-export', () => {
919
const { next, isNextDev, skipped } = nextTestSetup({
@@ -22,14 +32,26 @@ describe('proxy-missing-export', () => {
2232
'export function middleware() {}'
2333
)
2434

35+
let cliOutput: string
36+
2537
if (isNextDev) {
2638
await next.start().catch(() => {})
2739
// Use .catch() because Turbopack errors during compile and exits before runtime.
2840
await next.browser('/').catch(() => {})
29-
expect(next.cliOutput).toContain(errorMessage)
41+
cliOutput = next.cliOutput
42+
} else {
43+
cliOutput = (await next.build()).cliOutput
44+
}
45+
46+
// TODO: Investigate why in dev-turbo, the error is shown in the browser console, not CLI output.
47+
if (process.env.IS_TURBOPACK_TEST && !isNextDev) {
48+
expect(cliOutput).toContain(`./proxy.ts
49+
Proxy is missing expected function export name
50+
${errorMessage}`)
3051
} else {
31-
const { cliOutput } = await next.build()
32-
expect(cliOutput).toContain(errorMessage)
52+
expect(cliOutput)
53+
.toContain(`The file "./proxy.ts" must export a function, either as a default export or as a named "proxy" export.
54+
${errorMessage}`)
3355
}
3456

3557
await next.stop()
@@ -94,16 +116,27 @@ describe('proxy-missing-export', () => {
94116
'const proxy = () => {}; export { proxy as handler };'
95117
)
96118

119+
let cliOutput: string
120+
97121
if (isNextDev) {
98122
await next.start().catch(() => {})
99123
// Use .catch() because Turbopack errors during compile and exits before runtime.
100124
await next.browser('/').catch(() => {})
101-
expect(next.cliOutput).toContain(errorMessage)
125+
cliOutput = next.cliOutput
102126
} else {
103-
const { cliOutput } = await next.build()
104-
expect(cliOutput).toContain(errorMessage)
127+
cliOutput = (await next.build()).cliOutput
105128
}
106129

130+
// TODO: Investigate why in dev-turbo, the error is shown in the browser console, not CLI output.
131+
if (process.env.IS_TURBOPACK_TEST && !isNextDev) {
132+
expect(cliOutput).toContain(`./proxy.ts
133+
Proxy is missing expected function export name
134+
${errorMessage}`)
135+
} else {
136+
expect(cliOutput)
137+
.toContain(`The file "./proxy.ts" must export a function, either as a default export or as a named "proxy" export.
138+
${errorMessage}`)
139+
}
107140
await next.stop()
108141
})
109142
})

0 commit comments

Comments
 (0)