DEV Community

Daniel Blackwell
Daniel Blackwell

Posted on • Edited on

Nuxt.js and Vercel Functions without using `vercel dev`

I'd recently being having issues using Vercel's serverless functions with SSG. Although I haven't been able to fix that just yet, I realised I could run vercel functions without using the dev proxy.

NOTE: I wasn't able to consistently get the correct data from the body of the request. Not sure why, but res.read() was skipping the first line of the request body when toString() was called. This means with my current code ONLY GET requests are supported. Shouldn't be hard to get it working with POST if you can spare the troubleshooting time.

Add @vercel/node

You will need @vercel/node either globally or locally. You don't need to use it directly, but the file we are copying will reference back to this package.

Nuxt serverMiddleware to the rescue.

In production, Vercel uses serverless functions to handle the calls to the functions found at /api. These functions are not available using the standard yarn dev.

serverMiddleware allows any call to the dev server be intercepted.

For us, that means allowing requests to captured and redirected to Vercel functions directly. The only issue there is pass over the request and response in a way that the functions expect.

The @vercel/node package has the file @vercel\node\dist\helpers.js which contains the js that normally runs via the vercel dev command.

Putting it together

/middleware-server/vercel.ts

vercel.ts is the serverMiddleware handler that intercepts the calls to any url under /api.
The request and response have properties added to become a VercelResponse and a VercelRequest.
The api function is loaded using a require, but the require cache is cleared of the module first to make sure only the latest code is used.

// To convert to JS, remove the type imports and the type declarations
// e.g. : VercelRequest 
import { ServerMiddleware } from '@nuxt/types'
import { VercelResponse, VercelRequest, VercelApiHandler } from '@vercel/node'

const helpers = require('./helpers'); 

const vercel: ServerMiddleware = async (req, res, next) => {

    // const body = req.read(); - this should work, but it's being very strange. Leaving for now.
    const body = Buffer.from("");

    const apiNameRegex = /\/([\w-]+).*/g

    const matches = apiNameRegex.exec(req.url || "");

    if (!matches || !matches[1]) {
        res.statusCode = 404;
        res.end()
        return;
    }

    const apiName = matches[1];
    const moduleName = '../api/' + apiName;

    // allow dynamic code change reloads - possibly detect changes and remove that way
    const requestedApi = requireReload(moduleName).default as VercelApiHandler;

    const vercelRequest: VercelRequest = helpers.httpToVercelRequest(req, body);
    const vercelResponse: VercelResponse = helpers.httpToVercelResponse(req, res);

    try {
        requestedApi(vercelRequest, vercelResponse)
    } catch (err) {
        console.error(err)
        res.statusCode = 500;
        res.end()
    }
}

/// https://stackoverflow.com/a/58645175/59532
function requireReload(moduleName: string) {

    let mp = require.resolve(moduleName)
    if (require.cache[mp]) {
        delete require.cache[mp]
        console.log(`[clear] module: ${mp}`)
    }
    return require(moduleName);
}


export default vercel;
Enter fullscreen mode Exit fullscreen mode

/middleware-server/helper.js

helper.js is a copy of the file @vercel\node\dist\helpers.js, but some extra code at 795 to allow access to the surrounding methods.

// The file is a copy of @vercel\node\dist\helpers.js
// ADD THIS CODE AT LINE 795

function httpToVercelRequest (req, body) {
    setLazyProp(req, 'cookies', getCookieParser(req));
    setLazyProp(req, 'query', getQueryParser(req));
    setLazyProp(req, 'body', getBodyParser(req, body));
    return req;
  }

function httpToVercelResponse(req, res) {
    res.status = statusCode => status(res, statusCode);
    res.redirect = (statusOrUrl, url) => redirect(res, statusOrUrl, url);
    res.send = body => send(req, res, body);
    res.json = jsonBody => json(req, res, jsonBody);
    return res;
  }

  exports.httpToVercelRequest = httpToVercelRequest;
  exports.httpToVercelResponse = httpToVercelResponse;

Enter fullscreen mode Exit fullscreen mode

Add the serverMiddleware to the nuxt.config.js

nuxt.config.js

  // Added property:
  serverMiddleware: [
    { path: '/api', handler: '~/middleware-server/vercel' },
  ]
Enter fullscreen mode Exit fullscreen mode

Conclusion

When not running vercel dev, any calls to a url starting with /api should now be handled by nuxt, and will be redirected to the appropriate api function.

If running with vercel dev, or on a server, then the functions will be handled by Vercel, as it was before.

If you are able to get the body part working, drop the solution back here. It would be interesting to see what the issue was.

Top comments (7)

Collapse
 
aksouda profile image
Aksouda

Hello ,thank you for sharing this solution I have been looking for this exactly I couldn't find it anywhere else!
I followed the exact steps but when I run the /api it returns 500 error can anyone please help, much appreciate it

Collapse
 
aksouda profile image
Aksouda

I get requestedApi is not a function error

Collapse
 
douglascalora profile image
DouglasCalora

I'm having issues when running "helpers.js". It can't find the functions, I copied the code from `@vercel/node/dist/helpers. Where should I put the code?

Collapse
 
deeja profile image
Daniel Blackwell

Put it in the same folder as the vercel.ts, or change const helpers = require('./helpers'); to put it where you would like.
If it's not finding the functions, check that you have added the httpToVercelResponse and httpToVercelRequest functions just above createServerWithHelpers(handler, bridge)

Collapse
 
chrisjayden profile image
MultipleHats

Just an FYI, I think you meant require('./helper') since you called the file helpers

Collapse
 
sebbean profile image
Sebastian Bean

btw if you're not using typescript in your project you need to convert the .ts code back to .js

Collapse
 
deeja profile image
Daniel Blackwell

I've updated the code with a couple of notes regarding js.