vercel / next.js

The React Framework
https://nextjs.org
MIT License
124.69k stars 26.61k forks source link

No response is returned from route handler (Response differs due to polyfill!) #58611

Open WesleyKapow opened 9 months ago

WesleyKapow commented 9 months ago

Link to the code that reproduces this issue

https://github.com/luthfib/auth0-nextjs-repro/tree/fetch-issue

To Reproduce

  1. Copy .env.example to .env and set it up for an auth0 account
  2. pnpm install
  3. pnpm run build
  4. pnpm run start
  5. Open Chrome incognito window. After logging in, open dev tools and go to application and clear site data. Then refresh the page and you should get a 500.

Current vs. Expected behavior

I expect auth to work and to be redirected correctly back to the main page.

Instead their's a 500 and server shows:

 ⨯ Error: No response is returned from route handler '/.../auth0-nextjs-repro/app/api/auth/[auth0]/route.ts'. Ensure you return a `Response` or a `NextResponse` in all branches of your handler.

Verify canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.1.0: Mon Oct  9 21:28:12 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T8103
Binaries:
  Node: 18.14.2
  npm: 9.5.0
  Yarn: 1.22.5
  pnpm: 8.9.2
Relevant Packages:
  next: 13.5.6
  eslint-config-next: 13.5.5
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.2.2
Next.js Config:
  output: standalone

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

App Router

Additional context

Next error is from https://github.com/vercel/next.js/blob/6ed4fddf8a55f956ca6a714e081a384880d45e52/packages/next/src/server/future/route-modules/app-route/module.ts#L367

Cross-fetch overriding of Response object: https://github.com/lquixada/cross-fetch/blob/v4.x/src/node-polyfill.js#L8

In testing, I setup some console logs and used a WeakMap/objectId trick to verify that the Response object is indeed getting changed due to the polyfill. This happens after Next has defined NextResponse so the nextResponse object inherits from the original Response and not the polyfill version. This causes res instaceof Response to fail. For more on this see https://nextjs-forum.com/post/1163946577388380171

kelihansen commented 9 months ago

I don't have a lot to add except to say it's not just you. Thank you for sharing code that reproduces the issue!

kelihansen commented 9 months ago

In case this helps anyone else, my problem was that I hadn't fully adapted the @auth0/nextjs-auth0 example to the app router. I needed to return handleLogin, not just await it.

import { handleAuth, handleLogin } from "@auth0/nextjs-auth0"
import { NextApiRequest, NextApiResponse } from "next"

export const GET = handleAuth({
  async login(req: NextApiRequest, res: NextApiResponse) {
    const returnToQuery = req.query?.returnTo
    const returnTo = typeof returnToQuery === "string" ? returnToQuery : "/"

    return await handleLogin(req, res, {
      returnTo
    })
  },
})
GhislainMitahi commented 3 months ago

Hello Guy, I also have the same issue on my configuration that was working better on local and prod 1m ago but now it broken

Here is the error on my side : ⨯ Error: No response is returned from route handler 'D:\develop\summit-landingpage\src\app\api\auth[...nextauth]\route.ts'. Ensure you return a Response or a NextResponse in all branches of your handler.

My code code

import { PrismaClient } from '@prisma/client'; import { type NextApiRequest, type NextApiResponse } from 'next'; import NextAuth, { type NextAuthOptions, type Profile } from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; import GoogleProvider from 'next-auth/providers/google'; interface GoogleProfile extends Profile { given_name?: string; family_name?: string; picture?: string; }

const prisma = new PrismaClient();

if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_CLIENT_SECRET) { console.error( 'Missing required environment variables: GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET' ); process.exit(1); }

const clientId = process.env.GOOGLE_CLIENT_ID; const clientSecret = process.env.GOOGLE_CLIENT_SECRET;

const options: NextAuthOptions = { providers: [ GoogleProvider({ clientId, clientSecret, }), CredentialsProvider({ name: 'Email', credentials: { email: { label: 'Email', type: 'email', placeholder: 'john.doe@example.com', }, },

  async authorize(credentials) {
    if (!credentials?.email) {
      throw new Error('Please provide the email');
    }

    const user = await prisma.user.findUnique({
      where: { email: credentials.email },
    });

    if (user) {
      return {
        id: user.id.toString(),
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
      };
    }
    return null;
  },
}),

], callbacks: { async signIn({ user, account, profile }) { const email = user.email;

  let firstName = '';
  let lastName = '';
  let pictureUrl = '';

  if (account?.provider === 'google') {
    const googleProfile = profile as GoogleProfile;
    firstName = googleProfile.given_name ?? '';
    lastName = googleProfile.family_name ?? '';
    pictureUrl = googleProfile.picture ?? '';
  }

  let userId: number | null = null;

  if (email) {
    try {
      let existingUser = await prisma.user.findUnique({
        where: { email },
      });

      if (existingUser) {
        if (account?.provider === 'google') {
          await prisma.user.update({
            where: { email },
            data: {
              firstName,
              lastName,
              pictureUrl,
            },
          });
        }
        userId = existingUser.id;
      } else {
        if (account?.provider === 'google') {
          existingUser = await prisma.user.create({
            data: {
              email,
              firstName,
              lastName,
              pictureUrl,
            },
          });
          userId = existingUser.id;
        }
      }

      if (userId !== null) {
        user.id = userId.toString();
        return true;
      }
    } catch (error) {
      console.error('Error creating or updating user:', error);
      return false;
    }
  }
  return true;
},

async jwt({ token, user }) {
  if (user?.id) {
    token.uid = user.id;
  }
  return token;
},
async session({ session, token }) {
  if (token.uid) {
    const user = await prisma.user.findUnique({
      where: { id: Number(token.uid) },
    });
    if (user) {
      session.user = {
        id: user.id.toString(),
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
      };
    }
  }
  return session;
},

}, };

const nextAuthHandler = async (req: NextApiRequest, res: NextApiResponse) => { console.log('Checking res properties:', res); console.log('Checking req properties:', req); try { await NextAuth(req, res, options); } catch (error) { console.error('NextAuth error:', error); } };

export const GET = async (req: NextApiRequest, res: NextApiResponse) => { try { await nextAuthHandler(req, res); } catch (error) { console.error('Error handling GET request:', error); res.status(500).json({ error: 'Error in GET request' }); } };

export const POST = async (req: NextApiRequest, res: NextApiResponse) => { try { await nextAuthHandler(req, res); } catch (error) { console.error('Error handling POST request:', error); res.status(500).json({ error: 'Error in POST request' }); } };

If there is someone with my solution kindly share, I need your support

glenmiracle18 commented 2 months ago

@GhislainMitahi try using code snippets, when submitting code. it helps with indentation and it's easy to read