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;
/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;
Add the serverMiddleware
to the nuxt.config.js
nuxt.config.js
// Added property:
serverMiddleware: [
{ path: '/api', handler: '~/middleware-server/vercel' },
]
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)
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
I get requestedApi is not a function error
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?
Put it in the same folder as the
vercel.ts
, or changeconst helpers = require('./helpers');
to put it where you would like.If it's not finding the functions, check that you have added the
httpToVercelResponse
andhttpToVercelRequest
functions just abovecreateServerWithHelpers(handler, bridge)
Just an FYI, I think you meant
require('./helper')
since you called the filehelpers
btw if you're not using typescript in your project you need to convert the .ts code back to .js
I've updated the code with a couple of notes regarding js.