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

Server-side HTTP requests via getSession usually get extremely slow on Vercel #1535

Closed
1 of 5 tasks
kripod opened this issue Mar 16, 2021 · 30 comments
Closed
1 of 5 tasks
Assignees
Labels
bug Something isn't working help needed The maintainer needs help due to time constraint/missing knowledge

Comments

@kripod
Copy link
Contributor

kripod commented Mar 16, 2021

Describe the bug

Before further ado, here’s a comparison between a /api/user/{id} request which fetches /api/auth/session through the network even on the server side, versus a /api/site/{id} call, which isn’t protected via getSession. Unfortunately, there’s a quite large margin between response times.

image

Steps to reproduce
Call getSession on the server side and observe that it makes an HTTP request instead of just calling functions locally.

Expected behavior
Calls to getSession should be executed locally in a server environment. A separate getServerSession method could be added as a non-isomorphic solution without any HTTP calls.

Additional context
According to the official Next.js docs:

You should not use fetch() to call an API route in getServerSideProps. Instead, directly import the logic used inside your API route. You may need to slightly refactor your code for this approach.

Fetching from an external API is fine!

This seems to hurt performance badly.

Feedback
Documentation refers to searching through online documentation, code comments and issue history. The example project refers to next-auth-example.

  • Found the documentation helpful
  • Found documentation but was incomplete
  • Could not find relevant documentation
  • Found the example project helpful
  • Did not find the example project helpful

Thank you for maintaining this wonderful library – it really helps a lot! 🙏

@kripod kripod added the bug Something isn't working label Mar 16, 2021
@balazsorban44
Copy link
Member

balazsorban44 commented Mar 16, 2021

So this has actually been asked several times before, and @iaincollins's reasoning is the following:

We deliberately use a REST call rather than bringing in all of NextAuth.js and the database Adapter into a Lambda (and I'm very keen to keep it that way).

I am not sure what his opinion was on making an alternative method that would allow the user to deliberately do what they want accepting the consequences. @iaincollins? What do you think?

Related: #1199, #947 (comment), #212 (comment), #212 (comment) and probably more.
Potentially related: #1478

@balazsorban44 balazsorban44 added the help needed The maintainer needs help due to time constraint/missing knowledge label Mar 16, 2021
@kripod
Copy link
Contributor Author

kripod commented Mar 16, 2021

@balazsorban44 thanks for your quick response!

Some deployment services (e.g. Vercel) bundle all the API routes into a single Lambda, so there should be no significant size difference between the fetch-based version and importing already bundled functions directly.

The isomorphic getSession method should be viable once React Server Components are released – so that fetch calls targeting local API routes can be called without network overhead. Until then, I think developers should have a choice between the isomorphic and a server-only variant of getSession, getProviders, etc. They should possible be called getServerSession and getServerProviders, respectively.

@iaincollins
Copy link
Member

I think developers should have a choice between the isomorphic and a server-only variant of getSession, getProviders, etc. They should possible be called getServerSession and getServerProviders, respectively.

I think something like this is reasonable in principle but we'd need to address how passing app configuration is handled in a consistent way in different environments (i.e. consistant behaviour that allows for, but doesn't assume, shared context for all functions).

I've got ideas about how I'd like config to work the longer term, but I'm open to suggestions for interim solutions.

@rozenmd
Copy link

rozenmd commented Apr 26, 2021

This issue is particularly noticeable when Vercel's platform experiences additional latency - API calls to a protected endpoint simply start timing out.

Anything we can do in userland to avoid the additional network request?

@kripod
Copy link
Contributor Author

kripod commented Apr 26, 2021

@rozenmd These issues may also related to the problem: #1832, #602

@janpio
Copy link

janpio commented May 11, 2021

To add a data point here: prisma/prisma#7009 (comment) This is a Prisma user who attributed the latency in his app to Prisma in the beginning. We then, with the help of @kripod, figured out that this might indeed be a Vercel deployment + Nextauth issue as described above.

Fixing this in some way or protecting users in serverless environments would be very nice. (And yep, we know the pain of serverless deployments at Prisma as well. We are trying everything to make it nice for people, but some problems are just still unsolved. [which is totally not Vercel's fault by the way, they are doing an amazing job of making serverless usable in the first place - but now people use it 😆])

@balazsorban44
Copy link
Member

You can see the original comment what I used to base my answers on protecting our current implementation: #947 (comment) but I'm getting more and more convinced that this has indeed been a poor choice, especially when the reason behind it was to better support serverless environments. It seems to me that it kind of has the opposite effect of what the original implementation wanted to achieve.

@rozenmd
Copy link

rozenmd commented May 15, 2021

FYI I managed to roughly half the cold-boot impact by bypassing getSession when the session is valid: #1673 (comment)

@balazsorban44
Copy link
Member

I created a temporary version at 3.22.0-canary.2

import {getServerSession} from "next-auth"
import {options} from "pages/api/auth/[...nextauth]"
export default Page(){
  return null
}
export async function getServerSideProps(context) {
  return {
    props: {
      session: await getServerSession(context, options),
    },
  }
}

And in your [...nextauth].js you have to extract the options into its own const, so you can share it in the page file. This configuration could ultimately be extracted into something like next-auth.config.js or similar I guess, but this is a Work In Progress for now.

Very interested if someone could check out if it helps. The getServerSession does not do fetch

@rozenmd
Copy link

rozenmd commented May 21, 2021

Keen to check it out @balazsorban44 - is there a PR/branch I can look over?

@balazsorban44
Copy link
Member

the code is only available locally currently it is just a quick and dirty implementation, so nothing to look at there. the more interesting part is if it makes any difference in performance. check out the above mentioned version

@kripod
Copy link
Contributor Author

kripod commented Jun 9, 2021

We’ve been using the proposed getServerSession API at Copyfolio for the past few weeks, and since then, our response times have improved greatly.

I would love to see a similar API implemented in the upcoming versions of NextAuth, as it improves performance by a leap in serverless environments.

@balazsorban44
Copy link
Member

balazsorban44 commented Jun 11, 2021

this is up for a take to anyone who would likte to open a PR about this against next. I am busy with other parts currently, so I might not get around this for a while, but I think it would be nice to have it in our next major release.

@walid-mokrani
Copy link

walid-mokrani commented Jun 21, 2021

I have created a branch https://github.com/walid-mokrani/next-auth/tree/feat/get-server-side if someone can look at it.

@balazsorban44
Copy link
Member

@walid-mokrani looks promising, would you care to open a PR against our next branch? I'm pretty sure we could tune it a bit and make it part of the v4 release!

@balazsorban44
Copy link
Member

getServerSession has landed in an experimental release which is going to be included in v4! You can find it here right now: #2857 (comment)

Documentation comes later, but have a look at these files to get the idea:

https://github.com/nextauthjs/next-auth/blob/9aba57396c6776642ed743b60a266a29385652ac/app/pages/api/auth/%5B...nextauth%5D.ts
image

https://github.com/nextauthjs/next-auth/blob/9aba57396c6776642ed743b60a266a29385652ac/app/pages/protected-ssr.js
image

The API is not necessarily final yet, we might change the name or recommend the config to be moved to a next-auth.config.ts file or similar, but it should not really matter.

@balazsorban44
Copy link
Member

This will also be the solution for #2850. That discussion made me aware that we have never actually updated the session cookie when getSession was called only server-side!

@trentprynn
Copy link

trentprynn commented Nov 26, 2021

TLDR

  • This has resulted in a 100% (2X) speedup for basically every action in my application
  • Here's source code for my habit tracking app which is now exclusively using getServerSession and runs on vercel serverless api functions

Here's my write up of changing to use getServerSession solely in my application

Just as an update to other who are interested in the getServerSession method in regards to implementation and possible performance gains when running serverless on Vercel

I use NextAuth heavily in my open source daily habit tracking application which uses Next (Full SSR Frontend + API) + NextAuth + Prisma + Postgres and is hosted on Vercel. This means all of my API functions are deployed as server less functions and I do all of my auth checking in server side rendering methods for my pages.

Previously NextAuth's use of http fetch in the getSession method severely hurt performance when run on vercel's serverless functions -- imagine the following flow and the amount of HTTP requests that were happening on Vercel's platform where HTTP request startup time is the largest hindrance for performance

  1. User that's already logged in navigates to habitapper.com (1)
    • http request (1)
  2. SSR for page does fetch request for session (2) -> finds a valid session -> redirects user to habitapper.com/habits page
    • http request (2)
  3. User loads habitapper.com/habits (3) -> SSR does fetch request for session (4) -> finds session -> uses internal method (non-http) to get habits for that user and passes them in props to component
    • http requests (3, 4)

With the new getServerSession this removes http requests (2) and (4) because the SSR method is directly accessing the logic to retrieve the session without having to make an http request.

Some notes about current implementation if you're looking to do this in your application

  1. I had to use next-auth@0.0.0-pr.3222.41318883, when I tried using the getServerSession method with next-auth@4.0.0-beta.7 every call failed to fetch the session and I got errors in console -- these went away when I upgraded to this experimental version and I assume will be fixed in auth@4.0.0-beta.8 when it's released
  2. You'll need to modify your pages/api/[...nextauth].ts file so you can access the NextAuthOptions object which you'll need to export and use anywhere you want to use the getServerSession method
  3. Using getServerSession in a server side rendering function differs from using it in api route

@trentprynn
Copy link

Also, @balazsorban44 THANK YOU! 😃

@balazsorban44
Copy link
Member

There won't be beta.8. Next stop is at stable v4. 😊

getServerSession won't launch officially yet, and considered experimental until documented, but hearing that it helped is awesome!

@davudk
Copy link

davudk commented Nov 30, 2021

getServerSession works for me locally, but it returns an error string when run on Vercel.

Here is my API code:

import { authOptions } from 'common/config/next-auth';
import type { NextApiRequest, NextApiResponse } from 'next';
import { getServerSession } from 'next-auth';

export default async (req: NextApiRequest, res: NextApiResponse) => {
    // const session = await getSession({ req });
    const session = await getServerSession({ req, res }, authOptions);

    console.log('session: ', session);

    res.status(200).json({ session });
}

Here are my auth options:

import { NextAuthOptions } from 'next-auth';
import CognitoProvider from 'next-auth/providers/cognito';

export const authOptions: NextAuthOptions = {
    debug: true,
    providers: [
        CognitoProvider({
            clientId: process.env.COGNITO_CLIENT_ID!,
            clientSecret: process.env.COGNITO_CLIENT_SECRET!,
            issuer: `https://cognito-idp.us-east-1.amazonaws.com/${process.env.COGNITO_USER_POOL_ID}`,
        }),
    ],
    secret: '...'
};

When running on Vercel getServerSession returns an HTML string containing the following (formatted for readability):

<!DOCTYPE html>
<html lang=\"en\">
    <head>
        <meta charset=\"UTF-8\">
        <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
        <style>:root{--border-width:1px;--border-radius:0.3rem;--color-error:#c94b4b;--color-info:#157efb;--color-info-text:#fff}.__next-auth-theme-auto,.__next-auth-theme-light{--color-background:#fff;--color-text:#000;--color-primary:#444;--color-control-border:#bbb;--color-button-active-background:#f9f9f9;--color-button-active-border:#aaa;--color-seperator:#ccc}.__next-auth-theme-dark{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}@media (prefers-color-scheme:dark){.__next-auth-theme-auto{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}}body{background-color:var(--color-background);font-family:-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,Helvetica,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;margin:0;padding:0}h1{font-weight:400;margin-bottom:1.5rem;padding:0 1rem}h1,p{color:var(--color-text)}form{margin:0;padding:0}label{color:#666;display:block;font-weight:500;margin-bottom:.25rem;text-align:left}input[type]{background:var(--color-background);border:var(--border-width) solid var(--color-control-border);border-radius:var(--border-radius);box-shadow:inset 0 .1rem .2rem rgba(0,0,0,.2);box-sizing:border-box;color:var(--color-text);display:block;font-size:1rem;padding:.5rem 1rem;width:100%}input[type]:focus{box-shadow:none}p{font-size:1.1rem;line-height:2rem;margin:0 0 1.5rem;padding:0 1rem}a.button{line-height:1rem;text-decoration:none}a.button,a.button:link,a.button:visited,button{background-color:var(--color-background);color:var(--color-primary)}a.button,button{border:var(--border-width) solid var(--color-control-border);border-radius:var(--border-radius);box-shadow:0 .15rem .3rem rgba(0,0,0,.15),inset 0 .1rem .2rem var(--color-background),inset 0 -.1rem .1rem rgba(0,0,0,.05);font-size:1rem;font-weight:500;margin:0 0 .75rem;padding:.75rem 1rem;position:relative;transition:all .1s ease-in-out}a.button:hover,button:hover{cursor:pointer}a.button:active,button:active{background-color:var(--color-button-active-background);border-color:var(--color-button-active-border);box-shadow:0 .15rem .3rem rgba(0,0,0,.15),inset 0 .1rem .2rem var(--color-background),inset 0 -.1rem .1rem rgba(0,0,0,.1);cursor:pointer}a.site{color:var(--color-primary);font-size:1rem;line-height:2rem;text-decoration:none}a.site:hover{text-decoration:underline}.page{display:grid;height:100%;margin:0;padding:0;place-items:center;position:absolute;width:100%}.page>div{padding:.5rem;text-align:center}.error a.button{display:inline-block;margin-top:.5rem;padding-left:2rem;padding-right:2rem}.error .message{margin-bottom:1.5rem}.signin a.button,.signin button,.signin input[type=text]{display:block;margin-left:auto;margin-right:auto}.signin hr{border:0;border-top:1px solid var(--color-seperator);display:block;margin:1.5em auto 0;overflow:visible}.signin hr:before{background:var(--color-background);color:#888;content:\"or\";padding:0 .4rem;position:relative;top:-.6rem}.signin .error{background:#f5f5f5;background:var(--color-info);border-radius:.3rem;font-weight:500}.signin .error p{color:var(--color-info-text);font-size:.9rem;line-height:1.2rem;padding:.5rem 1rem;text-align:left}.signin>div,.signin form{display:block}.signin>div input[type],.signin form input[type]{margin-bottom:.5rem}.signin>div button,.signin form button{width:100%}.signin>div,.signin form{max-width:300px}.signout .message{margin-bottom:1.5rem}.logo{display:inline-block;margin-top:100px;max-height:150px;max-width:300px}.card{border:1px solid var(--color-control-border);border-radius:5px;margin:50px auto;max-width:-webkit-max-content;max-width:-moz-max-content;max-width:max-content;padding:20px 50px}.card .header{color:var(--color-primary)}.section-header{color:var(--brand-color)}</style>
        <title>Error</title>
    </head>
    <body class=\"__next-auth-theme-auto\">
        <div class=\"page\">
            <div class=\"error\">
                <style>\n        :root {\n          --brand-color: undefined;\n        }\n      </style>
                <div class=\"card\">
                    <h1>Server error</h1>
                    <div class=\"message\">
                        <div>
                            <p>There is a problem with the server configuration.</p>
                            <p>Check the server logs for more information.</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

The effective result of the test API I have above is:

{
    session: "<!DOCTYPE ...."
}

No actual exception is thrown and nothing else is logged (debug mode is enabled in authOptions).

Has anyone experienced this?

@trentprynn
Copy link

@davudk What version of next-auth are you using? I had a very similar behavior when I was using next-auth@4.0.0-beta.7, when I upgraded to next-auth@0.0.0-pr.3222.41318883 these went away.

@davudk
Copy link

davudk commented Nov 30, 2021

@trentprynn Ah this was exactly it! Now it's working on my Vercel deployment and the difference in the session load speed is immense. Much better! Thanks

@SimplyComplexable
Copy link

Just to add another success story to this thread. I just migrated a project from v3 to v4 (to address these performance issues), and updated my server route to use getServerSession over getSession. Prior to the update the just the session check was taking between 1s and 5s. After the update, the session check is taking between 1ms and 20ms, usually less than 2ms! This equates to a nearly 1000x increase in performance! Absolutely game-changing!

One note, not necessarily related to getServerSession is that I'm now consistently getting this error on every auth check, but it's still working correctly so 🤷.

ERROR	[next-auth][error][MISSING_NEXTAUTH_API_ROUTE_ERROR] 
https://next-auth.js.org/errors#missing_nextauth_api_route_error Cannot find [...nextauth].{js,ts} in `/pages/api/auth`. Make sure the filename is written correctly. MissingAPIRoute [MissingAPIRouteError]: Cannot find [...nextauth].{js,ts} in `/pages/api/auth`. Make sure the filename is written correctly.
    at assertConfig (/var/task/node_modules/next-auth/core/lib/assert.js:19:12)
    at NextAuthHandler (/var/task/node_modules/next-auth/core/index.js:34:52)
    at getServerSession (/var/task/node_modules/next-auth/next/index.js:64:51)
    at contentfulAPI (/var/task/.next/server/pages/api/contentful.js:4578:91)
    at apiResolver (/var/task/node_modules/next/dist/next-server/server/api-utils.js:8:7)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async Server.handleApiRequest (/var/task/node_modules/next/dist/next-server/server/next-server.js:67:462)
    at async Object.fn (/var/task/node_modules/next/dist/next-server/server/next-server.js:59:492)
    at async Router.execute (/var/task/node_modules/next/dist/next-server/server/router.js:25:67)
    at async Server.run (/var/task/node_modules/next/dist/next-server/server/next-server.js:69:1042) {
  code: 'MISSING_NEXTAUTH_API_ROUTE_ERROR'
}

@balazsorban44
Copy link
Member

@SimplyComplexable fixed in #3222, part of the upcoming release. #3307

@trentprynn
Copy link

update: upgraded to next-auth@4.0.1 today and all is well

@balazsorban44
Copy link
Member

Hi everyone! getServerSession (see above in this thread) has been introduced, which shows very good results, so I consider this issue solved. I opened a new issue that would cover the documentation of it: #3973

As a note, since undocumented, I still consider the feature experimental, and we might change the API when we finalize it.

Cheers 🍻

@jgabriele
Copy link

Hello, and thanks a lot for taking care of this issue!

I was wondering if that would be possible to make it also available for _app, as using it there today crash because of imports.

./node_modules/openid-client/lib/helpers/deep_clone.js:1:33
Module not found: Can't resolve 'v8'

Import trace for requested module:
./node_modules/openid-client/lib/issuer.js
./node_modules/openid-client/lib/index.js
./node_modules/next-auth/core/lib/oauth/callback.js
./node_modules/next-auth/core/routes/callback.js
./node_modules/next-auth/core/routes/index.js
./node_modules/next-auth/core/index.js
./node_modules/next-auth/next/index.js
./pages/_app.tsx

My use case is like the one in this thread where I want to load user data from SSR pages and have it served by a top-level context to all my components instead of having it called in every of my page.

Do you think it's something feasible?

@balazsorban44
Copy link
Member

Bug report

In any case, getServerSession as the name suggests is for the Server. While I assume you mean getInitialProps by _app. Since it runs in the browser also, it won't work.

To require auth on your entire page now only needs a single line though, using Middleware:

pages/_middleware.js

export { default } from "next-auth/middleware

https://next-auth.js.org/configuration/nextjs

getServerSession should be reserved for cases when you need to do something special with the session in the backend.

@nextauthjs nextauthjs locked as resolved and limited conversation to collaborators Mar 1, 2022
@balazsorban44
Copy link
Member

balazsorban44 commented Jun 23, 2022

Heads up anyone using getServerSession, we renamed it to unstable_getServerSession in #4116 and will be included in the next release as such!

So we can include it in the documentation. The API might change, but the idea will be the same, to avoid the extra fetch call.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working help needed The maintainer needs help due to time constraint/missing knowledge
Projects
None yet
Development

No branches or pull requests

10 participants