Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for type of route handler returned value at build time (via the TS plugin) and at runtime #51394

Merged
merged 14 commits into from
Sep 8, 2023
Merged
13 changes: 13 additions & 0 deletions packages/next/src/build/webpack/plugins/next-types-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ if ('${method}' in entry) {
'${method}'
>
>()
checkFields<
Diff<
{
__tag__: '${method}',
__return_type__: Response | Promise<Response>
joulev marked this conversation as resolved.
Show resolved Hide resolved
},
{
__tag__: '${method}',
__return_type__: ReturnType<MaybeField<TEntry, '${method}'>>
},
'${method}'
>
>()
}
`
).join('')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ type AppRouteHandlerFnContext = {
}

/**
* Handler function for app routes.
* Handler function for app routes. If a non-Response value is returned, an error
* will be thrown.
*/
export type AppRouteHandlerFn = (
/**
Expand All @@ -81,7 +82,7 @@ export type AppRouteHandlerFn = (
* dynamic route).
*/
ctx: AppRouteHandlerFnContext
) => Promise<Response> | Response
) => unknown

/**
* AppRouteHandlers describes the handlers for app routes that is provided by
Expand Down Expand Up @@ -364,6 +365,11 @@ export class AppRouteRouteModule extends RouteModule<
const res = await handler(wrappedRequest, {
params: context.params,
})
if (!(res instanceof Response)) {
joulev marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(
`No response is returned from route handler '${this.resolvedPagePath}'. Ensure you return a \`Response\` or a \`NextResponse\` in all branches of your handler.`
)
}
;(context.staticGenerationContext as any).fetchMetrics =
staticGenerationStore.fetchMetrics

Expand Down
13 changes: 13 additions & 0 deletions test/e2e/app-dir/app-routes/app-custom-routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,5 +629,18 @@ createNextDescribe(
})
})
}

describe('no response returned', () => {
it('should print an error when no response is returned', async () => {
await next.fetch('/no-response', { method: 'POST' })

await check(() => {
expect(next.cliOutput).toMatch(
/No response is returned from route handler '.+\/route\.ts'\. Ensure you return a `Response` or a `NextResponse` in all branches of your handler\./
)
return 'yes'
}, 'yes')
})
})
}
)
1 change: 1 addition & 0 deletions test/e2e/app-dir/app-routes/app/no-response/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function POST() {}