How to set up a local development environment for serverless Next.js apps on Now v2 with Node.js lambda functions

Jake Burden
Feb 11 · 5 min read

The latest version of now enables users to deploy serverless applications and lambda functions with ease. If you’ve had the opportunity to try this out, you’ll probably find that Now v2 makes your deployments feel impressive.

Splitting your app into individual lambda functions makes cold start time nearly unnoticeable. It forces you to make good decisions with dependencies and data storage. Also, you can write each lambda function for your app in the best language for that particular endpoint. You can pick from a range of languages such as JavaScript, PHP, Go, Python, Bash, and even Rust. But there’s always one question that new users ask.

What is the local development story?

The Zeit team is working hard on a new local development tool called now dev which by the look of the commits coming in, will be released soon (a few days to a couple of weeks is my guess). We are anticipating that now dev will let you run your entire serverless project locally, so you can build and debug without needing to do any deployments.

So what should you do in the meantime? I don’t have a complete answer for the full range of what now deployments offer, but I will show you how I’ve been building my serverless Next.js apps with Node.js lambda functions locally.

Routing, Builds, and now.json

Ensure that you’ve configured a now.json. Here is an example of one that I’m currently using. It uses the @now/next builder for the app found in my package.json, and the@now/node builder for any JavaScript file in my /api directory. For my routes, any call to an endpoint starting with/api/ gets forwarded to the respective file in that directory. Any call to a URL such as /apply/<job url> gets forward to Next.js as /appy?job=<job url> in the /www directory (which is where the app lives). Any other URL is handled normally via Next.js.

Running Node.js Lambda Functions Locally

You’ll want to globally install now-lambda-runner , which is a tool that reads your now.json, then it creates a Node.js server and makes your lambda functions over localhost.

npm i now-lambda-runner -g

For example, if you’ve created a jobs.js lambda function in a /api directory and your API routes are structured the same way mine are above, this will be the localhost URL for your API:

http://localhost:8004/api/jobs.js

If you want your lambda functions to be available locally, open your terminal to the root of your project (or wherever your now.json is) and run:

now-lambda

If your now.json looks like mine, you’ll see something like this:

~/example-app ∴ now-lambda
-----------------------------------
Routes:
ALL - http://localhost:8004/api/(.*)
ALL - http://localhost:8004/apply/(?<job>[^/]+)$
ALL - http://localhost:8004/(.*)
-----------------------------------

Running Next.js locally

This part is easy. In another terminal tab, go to the root of your Next.js app and enter the command npm run dev. If you follow the now.json structure above then it will look like this:

~ ∴ cd example-app/www/
~/example-app/www ∴ npm run dev

Page.getInitialProps and absolute URLs

Your Next.js app is now running on localhost:3000and your lambda functions are running on localhost:8004.

You can server-side render your app with information from your APIs by calling them within Page.getInitialProps and returning the data to be used as props from your Page component.

Let’s look at the following Page.getInitalProps as an example of how you might try to call your API and the problems that you’ll run into which we’ll fix afterward:

// ❌ localhost:8004 won't work on live deploymentsPage.getInitialProps = async () => {
const jobs = await (
await fetch(`http://localhost:8004/api/allJobs.js`)
).json()
return {
jobs
}
}

This will run locally, but if you deploy this with now it will not work since your deployed app will not be running on localhost:8004 . So what about using relative URLs instead? (Hint: this will not work either)

// ❌ `now` deployments require absolute urlsPage.getInitialProps = async () => {
const jobs = await (
await fetch('/api/allJobs.js')
).json()
return {
jobs
}
}

If you try to load this page on a now deployment, you’ll be greeted with an error:

Error: only absolute urls are supported

So you need some way to determine the full absolute URL, both locally and on a live deployment. Also since now uses unique, immutable URLs for deployments before you alias to a domain, you can not deterministically hardcode a deployment URL ahead of time.

Here is one working way to get around that:

// ✅ URL generated from the host environment and works with local development setup.Page.getInitialProps = async ({ req }) => {
let protocol = 'https:'
let host = req ? req.headers.host : window.location.hostname
if (host.indexOf('localhost') > -1) {
host = 'localhost:8004'
protocol = 'http:'
}
const jobs = await (
await fetch(`${protocol}//${host}/api/allJobs.js`)
).json()
return {
jobs
}
}

So what is going on with this line here?
let host = req ? req.headers.host : window.location.hostname

There we are checking for a req object. If it exists then Page.getInitialProps is running on the server, and we can get the hostname with req.header.host. However, if we have navigated to this page via next/link, then Page.getInitialProps is running on the client side, which means we have to get the hostname from window.location.hostname. If our hostname happens to contain localhost, then it’s probably running locally and is not a deployment URL, and in this case, we are setting the host to localhost:8004.

That is a bit much to remember and copy/paste on every page that makes API calls in getInitialProps, so I made a module to make this a bit easier.

Inside the directory of your Next.js app ( /www in my case) run the following command to install next-absolute-url, a module that abstracts all the checking above.

npm i next-absolute-url

✅ Then you can use it in your Next.js app. Feel free to copy this:

The second argument in the absoluteUrl call is completely optional but allows you to modify the hostname if you want to be calling APIs from a different localhost address.

This enables you to test your code locally and deploy it to a live URL without needing to change any hardcoded hostnames in between steps.

The module is just this small function which you are welcome to copy/paste if you’d prefer.

function absoluteUrl (req, setLocalhost) {
var protocol = 'https:'
var host = req ? req.headers.host : window.location.hostname
if (host.indexOf('localhost') > -1) {
if (setLocalhost) host = setLocalhost
protocol = 'http:'
}
return {
protocol: protocol,
host: host
}
}
module.exports = absoluteUrl

Conclusion

Now you should have a Next.js app running on localhost:3000, Node.js Lambda functions running on localhost:8004, and an entire application that works the same locally as it does on live deployments. Lambda functions called in .getInitialProps works on the server, in the browser, locally, and live.

I hope this helps anyone exploring Now v2 for their current application, but be prepared for the official now dev to drop at any moment 🎉

P.S.

Need someone to build your Next.js app? Contact Digital Surgeons and we’ll be happy to talk about partnering with you 😸

Digital Surgeons Engineering

A Digital Surgeons Engineering Blog

Jake Burden

Written by

Software Engineer at GitLab

Digital Surgeons Engineering

A Digital Surgeons Engineering Blog

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade