next-connect
next-connect copied to clipboard
Next.js 14 Ensure you return a `Response` or a `NextResponse` in all branches of your handler.
Here is the route handler implementation:
const router = createEdgeRouter<NextRequest, AuthCallbackReqContext>()
router.use(handleAuthServerError, handleAuthMissingCodeError).get((req) => {
const { origin } = new URL(req.url)
return NextResponse.redirect(origin)
})
export async function GET(request: NextRequest, ctx: AuthCallbackReqContext) {
return router.run(request, ctx)
}
As you can see it's pretty simple GET endpoint. For simplicity, I removed the GET related logic, as it seems it doesn't have anything to do with the problem.
Currently, when I am redirected from my auth form (Supabase Auth) to the http://localhost:3000/api/v1/auth/callback?code=some-random-values
I get the following error:
⨯ Error: No response is returned from route handler '.../api/v1/auth/callback/route.ts'. Ensure you return a `Response` or a `NextResponse` in all branches of your handler.
at .../node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:6:63416
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Which is odd, as I consider return NextResponse.redirect(origin)
a valid response 🤔
Here is the request object to provide more context:
############ ROUTER GET NextRequest [Request] {
[Symbol(realm)]: {
settingsObject: { baseUrl: undefined, origin: [Getter], policyContainer: [Object] }
},
[Symbol(state)]: {
method: 'GET',
localURLsOnly: false,
unsafeRequest: false,
body: null,
client: { baseUrl: undefined, origin: [Getter], policyContainer: [Object] },
reservedClient: null,
replacesClientId: '',
window: 'client',
keepalive: false,
serviceWorkers: 'all',
initiator: '',
destination: '',
priority: null,
origin: 'client',
policyContainer: 'client',
referrer: 'client',
referrerPolicy: '',
mode: 'cors',
useCORSPreflightFlag: false,
credentials: 'same-origin',
useCredentials: false,
cache: 'default',
redirect: 'follow',
integrity: '',
cryptoGraphicsNonceMetadata: '',
parserMetadata: '',
reloadNavigation: false,
historyNavigation: false,
userActivation: false,
taintedOrigin: false,
redirectCount: 0,
responseTainting: 'basic',
preventNoCacheCacheControlHeaderModification: false,
done: false,
timingAllowFailed: false,
headersList: _HeadersList {
cookies: null,
[Symbol(headers map)]: [Map],
[Symbol(headers map sorted)]: [Array]
},
urlList: [ URL {} ],
url: URL {
href: 'http://localhost:3000/api/v1/auth/callback?code=some-code',
origin: 'http://localhost:3000',
protocol: 'http:',
username: '',
password: '',
host: 'localhost:3000',
hostname: 'localhost',
port: '3000',
pathname: '/api/v1/auth/callback',
search: '?code=some-code',
searchParams: URLSearchParams { 'code' => 'some-code' },
hash: ''
}
},
[Symbol(signal)]: AbortSignal { aborted: false },
[Symbol(abortController)]: AbortController { signal: AbortSignal { aborted: false } },
[Symbol(headers)]: _HeadersList {
cookies: null,
[Symbol(headers map)]: Map(20) {
'accept' => [Object],
'accept-encoding' => [Object],
'accept-language' => [Object],
'connection' => [Object],
'cookie' => [Object],
'host' => [Object],
'referer' => [Object],
'sec-ch-ua' => [Object],
'sec-ch-ua-mobile' => [Object],
'sec-ch-ua-platform' => [Object],
'sec-fetch-dest' => [Object],
'sec-fetch-mode' => [Object],
'sec-fetch-site' => [Object],
'sec-fetch-user' => [Object],
'upgrade-insecure-requests' => [Object],
'user-agent' => [Object],
'x-forwarded-for' => [Object],
'x-forwarded-host' => [Object],
'x-forwarded-port' => [Object],
'x-forwarded-proto' => [Object]
},
[Symbol(headers map sorted)]: [
[Array], [Array], [Array],
[Array], [Array], [Array],
[Array], [Array], [Array],
[Array], [Array], [Array],
[Array], [Array], [Array],
[Array], [Array], [Array],
[Array], [Array]
]
},
[Symbol(internal request)]: {
cookies: RequestCookies { _parsed: [Map], _headers: [_HeadersList] },
geo: {},
ip: undefined,
nextUrl: NextURL { [Symbol(NextURLInternal)]: [Object] },
url: 'http://localhost:3000/api/v1/auth/callback?code=some-code'
}
} { params: undefined }
Middlewares are pretty straightforward as well:
export const handleAuthServerError = async (
req: NextRequest,
_: AuthCallbackReqContext,
next: NextHandler,
) => {
const redirectUrl = hasAuthServerError(new URL(req.url))
if (redirectUrl) {
return NextResponse.redirect(redirectUrl)
}
await next()
}
export const handleAuthMissingCodeError = async (
req: NextRequest,
_: AuthCallbackReqContext,
next: NextHandler,
) => {
const redirectUrl = await hasAuthVerifyCode(new URL(req.url))
if (redirectUrl) {
return NextResponse.redirect(redirectUrl)
}
await next()
}
It's my attempt to convert my route to next-connect structure, it seems to me it's some sort of an issue with next-connect. However I am not next-connect heavy user, so messing smth up is also an option. It is however my 2nd issue with next-connect while working with the newest next.js 14. While working with older versions I didn't have issues like that.
Here is my regular route, that works without any issues:
export async function GET(req: NextRequest) {
const url = new URL(req.url)
try {
const redirectAuthErrorUrl = hasAuthServerError(url) ?? (await hasAuthVerifyCode(url))
if (redirectAuthErrorUrl) {
throw new AuthErrorWithRedirect('Authentication middleware failure.', redirectAuthErrorUrl)
}
const { searchParams, origin } = url
const code = searchParams.get('code')! // hasAuthVerifyCode ensures the code is present
const { dbServerClient } = composeDbServerClient({
cookieMethods: composeCookieMethods(),
})
const response = await dbServerClient.auth.exchangeCodeForSession(code)
if (response.error) {
throw response.error
}
return NextResponse.redirect(origin)
} catch (error) {
logger.error(`Callback authentication error occurred. ${JSON.stringify(error)}`)
...error handling logic
}
We have a suspect, that next-intl can potentially alter responses in a way the produces this error. For now it's just an unconfirmed assumption, but next-intl is what we added to the stack that was working smoothly earlier on.
We also run into another issue with next-intl, maybe someone with more context will be able to connect those dots faster: https://github.com/amannn/next-intl/issues/728
I'm having this issue too. Some findings: This works:
api.use((req, event, next) => {
return next(); // call next in chain
});
This does Not work:
api.use(async (req, event, next) => {
await next(); // call next in chain
});
Yielding error:
⨯ Error: No response is returned from route handler '......../src/app/api/waitlist/route.ts'. Ensure you return a `Response` or a `NextResponse` in all branches of your handler.
▲ Next.js 14.0.4
This is how I used it before, and I also got this error.
const router = createEdgeRouter<NextRequest, RequestContext>()
router
// A middleware example
.use(async (request, event, next) => {
const start = Date.now()
await next() // call next in chain
const end = Date.now()
console.log(`Request took ${end - start}ms`)
})
.get(async (request) => {
try {
return NextResponse.json([])
} catch (error: any) {
console.log('🚀 ~ GET ~ error:', error)
return NextResponse.json([])
}
})
export async function GET(request: NextRequest, ctx: RequestContext) {
return router.run(request, ctx)
}
Later I changed it to
const router = createEdgeRouter<NextRequest, RequestContext>()
router
// changed here
.use((request, event, next) => {
const start = Date.now()
const end = Date.now()
console.log(`Request took ${end - start}ms`)
return next()
})
.get(async (request) => {
try {
return NextResponse.json([])
} catch (error: any) {
console.log('🚀 ~ GET ~ error:', error)
return NextResponse.json([])
}
})
export async function GET(request: NextRequest, ctx: RequestContext) {
return router.run(request, ctx)
}
Then it's ok
router
.use(async (request, event, next) => {
const start = Date.now()
const end = Date.now()
console.log(`Request took ${end - start}ms`)
return (await next())
})
.get(async (request) => {
return Response.json([])
})
}
Also works for me in Nextjs 14.1 while keeping the the first function async.
Since we need to return next()
, I moved the timer to .finally()
since I haven't found a way to have it in the middleware.
router
.use(async (request, event, next) => {
return (await next())
})
.get(async (request) => {
return Response.json([])
})
}
export const GET = (request: NextRequest, ctx: RequestContext) => {
const start = Date.now();
return router.run(request, ctx).finally(() => {
const end = Date.now();
console.log(`Request took ${end - start}ms`);
});
};