Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Overcoming Next.js Vercel Timeout With Firebase Functions

Ali BaderEddinFollowBetter Programming--ListenShareIn my current Next.js web app, I have two API endpoints:While these endpoints work seamlessly in my local environment, they encounter issues in the Vercel production environment due to the ten-second Serverless Function Execution Timeout.I could upgrade from the Hobby plan to the Pro plan for $20/month to increase the timeout to 60 seconds, but the GPT-4 endpoint may take up to two minutes to run. Additionally, I’m reluctant to spend $240/year just for this single feature.Furthermore, I have an existing mobile app that I would like to integrate with those capabilities at some point in the future.Considering the requirements above, I have decided to migrate my API endpoints from Vercel Serverless Functions to Firebase Cloud Functions.In this article, I will guide you through overcoming Vercel’s Serverless Function Execution Timeout limitation by replacing them with cost-effective Firebase Cloud Functions.Install Firebase CLI and initialize Cloud Functions in your project:The firebase init functions command initializes Firebase Cloud Functions in your project. This command configures and files to create and deploy serverless functions using Firebase.Note: I ran it on my existing Next.js code repository, but I recommend starting a new repository for the Firebase functions.When you run firebase init functions, the Firebase CLI guides you through an interactive setup process:After running firebase init functions, you can start developing your Firebase Cloud Functions by adding your code to the index.js (or index.ts for TypeScript) file inside the functions folder. Once you have written your functions, you can deploy them using the firebase deploy — only functions command.Next, create the new functions inside the functions directory (created during initialization). For example, in functions/index.ts:For my own functions, I copied and pasted the code and the imports from my existing Next.js APIs. I got several errors from Firebase ESLint rules, and fixing them got so annoying and counterproductive, so I just ended up disabling the rules by setting strict to false in the tsconfig.json file in the functions folder.We can use the Firebase Emulator Suite to test the Firebase Cloud Functions locally. Here’s how to set it up and run your functions locally:Navigate to your project’s root directory and set up the emulators:When prompted, select “Functions” and follow the prompts to set up the Functions Emulator. Start the Firebase Emulator Suite:This will start the emulators, including the Functions Emulator.Call your function locally using a tool like curl or Postman. Use the URL provided in the emulator output (e.g., http://localhost:5001//us-central1/).On running the firebase emulators:start command, I got an error:functions: Failed to load function definition from source: FirebaseError: There was an error reading functions/package.json:functions/lib/index.js does not exist, can’t deploy Cloud FunctionsThe Firebase Functions emulator cannot find the required index.js file in the functions/lib directory. I missed building the TypeScript functions before starting the emulator. You can run the build command in the functions directory with this code:This will transpile the TypeScript code to JavaScript and output it in the functions/lib directory. Once the build process is complete, start the Firebase emulators again:I saw this warning, so I updated the package.json to point to Node 18 rather than 16.functions: Your requested “node” version “16” doesn’t match your global version “18”. Using node@18 from host.Now, the Functions emulator started without issues.I ran the following curl command to test one of the functions:I got this response.The error “Request failed with status code 401” indicates the request is unauthorized.When I copied the code earlier, this is how I pulled the API key from OpenAI as an environment variable.Firebase functions have a different way of handling environment variables than Next.js. I had to use Firebase’s environment configuration to set environment variables for the Firebase functions.To set the environment variable for the Firebase project, I ran the following command in the terminal:I got this output, but I did not deploy it yet since I am still testing locally.✔ Functions config updated.Please deploy your functions for the change to take effect by running firebase deploy — only functionsIn the Firebase function, I accessed this environment variable using functions.config() like this:I rebuilt the Firebase functions code and started the emulator again, and now I saw this error:Failed to load function definition from source: FirebaseError: Failed to load function definition from source: Failed to generate manifest from function source: TypeError: Cannot read properties of undefined (reading ‘api_key’)I thought maybe I should deploy and check if Firebase “local” emulator would now pick up the environment variable.On deployment, I received the following errors./Users/alib/Code/GitHub/hq-exercise-catalog/functions/lib/index.js4:1 error Parsing error: The keyword ‘const’ is reserved/Users/alib/Code/GitHub/hq-exercise-catalog/functions/src/index.ts1:1 error Parsing error: The keyword ‘import’ is reserved✖ 2 problems (2 errors, 0 warnings)The ESLint configuration was not recognizing the ES6 syntax. I updated the ESLint configuration to support ES6 syntax by following these steps to fix this.First, ensure you have the following packages installed:Create or update the .eslintrc.js file in the root of your project with the following content:This configuration sets up ESLint to use the TypeScript parser and apply the recommended rules for TypeScript. After making these changes, the ESLint configuration should recognize the ES6 syntax and not throw errors related to the const and import keywords.However, I was still getting the following error on the .eslintrc.js file:‘module’ is not defined.It appears the module keyword is causing an issue with the ESLint configuration. To fix this, I replaced the .js file with a .eslintrc.json file with the following content:On deployment, I got an even larger list of errors (summarized below):/Users/alib/Code/GitHub/hq-exercise-catalog/functions/lib/index.js2:23 error ‘exports’ is not defined no-undef5:18 error Require statement not part of import statement @typescript-eslint/no-var-requires5:18 error ‘require’ is not defined no-undef29:44 error ‘setTimeout’ is not defined no-undef131:5 error ‘console’ is not defined no-undef✖ 12 problems (12 errors, 0 warnings)Can you imagine how much time was wasted because of ESLint? Anyways, I updated the config once more:OK, it seems happy with the deployment now. Well, at least the ESLinting part. To sum up the deployment logs, there was this lingering issue in it:Could not load the function, shutting down.Please visit https://cloud.google.com/functions/docs/troubleshooting for in-depth troubleshooting documentation.Function failed on loading user code. This is likely due to a bug in the user code. Error message: Provided module can’t be loaded.Did you list all required modules in the package.json dependencies?Detailed stack trace: Error: Cannot find module ‘openai’Oops, I forgot to update the packages.json file. I am wondering how the npm run build passed! Anyways, I added the dependency, did npm install and npm run build, then deployed again. Finally, it succeeded.Function URL (generateExerciseData(us-central1)): https://us-central1-handstand-quest.cloudfunctions.net/generateExerciseDataFunction URL (generateWorkout(us-central1)): https://us-central1-handstand-quest.cloudfunctions.net/generateWorkout✔ Deploy complete!Project console: https://console.firebase.google.com/project/handstand-quest/overviewNow, I want to get back to the local environment testing to ensure things work as expected. I started the emulator and then re-ran the curl command.I still got the same error in the emulator.Failed to load function definition from source: FirebaseError: Failed to load function definition from source: Failed to generate manifest from function source: TypeError: Cannot read properties of undefined (reading ‘api_key’)This is when I realized that “deployment” is not related to the local environment. Common sense, but sometimes, I don’t have that.It turns out the environment variables need to be fed to the emulator via a config file.Fetch the current environment configuration and save it as a JSON file:This command will create a .runtimeconfig.json file in your functions directory. Add this file to your .gitignore to prevent sensitive data from being committed to your repository.Finally, it’s all working when the emulator starts. I reran my curl command from the terminal:It’s working the way I expect!Now that we’ve set up the Firebase functions and tested them locally for functionality, it’s time to update the Next.js application to call the Firebase Cloud Function instead of the Vercel API endpoint.The Firebase Cloud Function URL will have the following format:In my case, for one of my functions, this looks like this:Now, in my Next.js code, I’ll simply replace the relative path of /api/generateWorkout with the full path https://us-central1-handstand-quest.cloudfunctions.net/generateWorkout. After that, I’ll do the same for the other function — a very, very simple change.Before testing the behavior of the functions within the Next.js web app, it’s probably wise to test this endpoint directly from the terminal first.Unfortunately, I received this error:B-Pro:~ alib$ curl -X POST -H “Content-Type: application/json” -d ‘{“userPrompt”: “I want something basic that can help me get comfortable upside down with some wall assistance.”, “exerciseList”: “do your magic”}’ https://us-central1-handstand-quest.cloudfunctions.net/generateWorkout403 Forbidden

Your client does not have permission to get URL /generateWorkout from this server.

I noticed (with help from Stack Overflow) a “private” setting in the package.json in the functions folder, so I set it to false and redeployed. Didn’t work!When I tested the function in the Firebase Cloud Function console, it worked as expected.https://console.cloud.google.com/functions/details/us-central1/generateWorkout?env=gen1&tab=testing&authuser=0&project=handstand-questI found steps from the same Stack Overflow thread that worked!Wisely, Google shows a message to prevent you from making this decision to make your cloud functions insecure.Although insecure, I proceed with this option until I guard my web app with login/signup capabilities.With this change, the curl request to the public endpoint works as expected. Now that I have the endpoint running, I will test call it from the app.Let’s test the change in the staging environment.Since I initialized the Firebase functions folder in the Next.js project that gets deployed to Vercel; I need to tell Vercel to ignore this new folder. This is the error I currently get on deployment to Vercel.Type error: Cannot find module firebase-functions or its corresponding type declarations.To tell Vercel to ignore a folder, we can add a .vercelignore file to the root of our project and add the folder or files we want to ignore.I added the following line to the .vercelignore file and pushed the change to the git branch, so Vercel could automatically handle the deployment to the staging environment.Side note: If I deployed the Firebase functions to their own codebase, I wouldn’t have had to deal with this. Luckily, it’s a simple change to ignore the functions folder.When testing in the staging environment, I got this error:Access to fetch at ‘https://us-central1-handstand-quest.cloudfunctions.net/generateWorkout' from origin ‘https://hq-exercise-catalog-bl7zti3ol-alibad.vercel.app' has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.I ran the Next.js local environment and went ahead and did some testing. I got the standard CORS error from localhost. It made me appreciate what Next.js/Vercel is doing behind the scenes.Access to fetch at ‘https://us-central1-handstand-quest.cloudfunctions.net/generateWorkout' from origin ‘http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.Side note: This reminded me… It’s really expensive to build secure software, and by design, it slows companies down and has a major impact on reducing the innovation rate. Still, it’s necessary and the responsible thing to do. Building a side project, I don’t need to be that responsible!The error message is a CORS (Cross-Origin Resource Sharing) issue. This issue happens when you try to make a request from a different origin (in this case http://localhost:3000) to a server (https://us-central1-handstand-quest.cloudfunctions.net/generateWorkout) that doesn’t have the necessary headers to allow requests from different origins.I added the CORS headers to the Cloud Functions to resolve this issue.First, install the CORS npm package:Wrap the login in each function with this:This will deploy the Cloud Functions with the updated CORS headers. With the above steps, the Cloud Functions should now allow requests from different origins.Unfortunately, when I tried to call the Firebase Cloud Function from my Web App, I still got an error saying the CORS policy blocked that access. So, where is the CORS policy defined?Let’s look at the requests and responses in the Network tab in the Google Chrome Developer Tools.There is an initial preflight request, as shown below:The OPTIONS request has an HTTP 204 from the server, with the following properties in the response header.The OPTIONS request is then followed by the POST Request.The response for the POST request is an HTTP 408.HTTP 408 status code, also known as “Request Timeout,” indicates the server did not receive a complete request from the client within the server’s allotted time limit.This means Firebase is timing out too, and the resulting browser error is misleading!The response also includes a Referrer Policy set to strict-origin-when-cross-origin.Regarding the Referrer Policy set to strict-origin-when-cross-origin, this security feature controls how much referrer information should be included when a user navigates from one origin to another. It’s not directly related to the timeout issue or the CORS configuration, so I will ignore it.So Firebase also happens to have that timeout. However, the difference is that Firebase lets you configure it without subscribing to a $20-per-month plan. By default, Firebase Cloud Functions have a timeout limit of 60 seconds. You can increase this limit to 540 seconds (nine minutes).For the generateExerciseData function, the 60-second timeout is good enough since gpt-3.5-turbo is much faster than gpt-4, which is used in the generateWorkout function.Increasing the timeout limit may result in higher resource usage and, thus, costs. However, I’m willing to try and see how that plays out. I highly doubt it’ll hit $20 every month. We’ll see how that goes with time when traffic patterns change. For now, I’m expecting it to cost less than $1 a month.To change the amount of timeout in the generateWorkout Firebase function, let’s update the function definition in the index.ts file. I think I want it to wait up to three minutes.I deployed the change to Firebase and finally got unblocked!To speed up the whole experience and make it easier to enjoy, streaming the GPT data and gradually reflecting that in the UI would be a good idea. This will reduce the time it takes to start seeing data, and it will avoid timeouts in the first place.Upon reflection, it became clear that the current design approach was not optimal, which led to the need for a workaround to address the timeout issue. A more efficient approach would have been to deliver the data in smaller, more manageable chunks from the outset.Time is a critical factor, and while transitioning from Next.js APIs to Firebase Functions, authentication and CORS issues caused a delay.In my upcoming article, I will explore whether streaming is a more effective design strategy. It’ll all depend on how long it will take and whether unreasonable blockers come up.It will be tricky, though, since my internal implementation gets GPT to return the exact JSON format, which would be hard to stream.I hope this article gave you the idea and the details on how to update your Next.js application to use Firebase Cloud Functions instead of Vercel API endpoints.By making this simple switch, you can save a significant amount in yearly costs without sacrificing the functionality of your application.Although Vercel is an excellent platform, the ten-second timeout can be a hindrance. Nonetheless, I remain a big fan of Vercel and appreciate its numerous benefits.----Better ProgrammingSoftware Eng Leader | Process ObsessedAli BaderEddin--Sergei SavvovinBetter Programming--9Dmitry KruglovinBetter Programming--31Ali BaderEddin--SebastianinCodingTheSmartWay--4SebastianinCodingTheSmartWay--1Ivan CamposinSopmac AI--1SebastianinCodingTheSmartWay--Teemu Maatta--1Adhithi Ravichandran--10HelpStatusWritersBlogCareersPrivacyTermsAboutText to speechTeams



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

Overcoming Next.js Vercel Timeout With Firebase Functions

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×