Here at Dawn Labs, we've been ZEIT users since the early days. From Next.js to Now, their ecosystem has been one of the most effective tools for streamlining our development workflow and structure. Ever since Carbon 10x'ed it's traffic this past year, we've been in the market for an affordable way to scale out our deployment. Luckily, Now 2.0 came along just in time offering a sleek developer experience for the serverless runtime.
The first step on our 2.0 migration journey was updating the Carbon frontend. For those of you that are unfamiliar, Carbon is a tool that lets you create and share beautiful screenshots of your code.
Updating the frontend was super straightforward, since it did not require any architecture changes when shifting to the new serverless paradigm. As ZEIT’s Timothy Lorimer put it, “Now 2.0 is simply a different platform on top of those [same CDN nodes].” This meant we could make the upgrade knowing we weren’t going to see any degradation in request latency, something we had come to love from Now static builds.
Since Carbon was already static, making the upgrade was as easy as adjusting our config. This also meant we could remove our Dockerfile altogether, which did little more than to simply install-build-and-export our static app. Keeping this file around in the first place seemed like overkill, given that all we needed was to push up a static frontend.
Next up, our backend, which had a few more hurdles to jump over…
Our backend handles a few different tasks: Twitter uploads, Unsplash downloads, Oembed payloads, and image creation (which we use to support Safari on the frontend). These seemingly disparate tasks fit perfectly in a serverless model, when each doesn’t need to know about the other’s existence. Even more fittingly, all of these tasks were already separate in our code. Each task was contained in a file that exports a
(req, res) => Promise function, all under a
handlers/ directory. Our server existed as an Express application, but it was clear our code was meant for serverless all along.
Going into this update, we were expecting a few problems. First of which, we didn’t know how we could fit puppeteer in a serverless package — the
puppeteer package is 300MB, which far surpasses the limit of a single lambda on its own.
Thankfully, this problem was taken care of with
chrome-aws-lambda, which fits Chromium into a 35MB package. Following this example, we hooked together
puppeteer-core and we were able to support our chrome-headless API without
puppeteer at all.
With Express, we were used to doing everything all at once. Need compression? Just add
server.use(compression()). Letting Express handle routing, compression, and CORS all at once was an easy life to live, but I never realized it could be even simpler by letting the deployment platform handle these things for us, letting our application focus only on the task at hand.
Routing is maybe the simplest and most elegant abstraction in the new Now platform. We were able to delete all the routing code in our Express server and replace it with a static
routes config in our
As you can tell, we were still able to dictate which HTTP methods were supported, and even handle parameter rerouting to make our application code simpler.
This was another feature where instead of having to think about how we should move the functionality into our serverless functions, we just let the platform handle it for us. Now’s routing layer supports Brotli and Gzip compression out of the box, so we were good to go.
Finally, handling CORS was a lot simpler after the transition. All we had to do was add some
"headers" to our config and we were up and running.
Finally, the last step was just to resolve how the request body was parsed, and how responses should be returned, which were previously both handled by Express’ abstractions. To limit the amount of code changes as much as possible, we used
micro’s utility functions —
json for parse the body and
send for sending all of our response. While these are simple utilities (you could easily write them yourself), using them made our upgrade that much simpler.
The biggest difference was in the
diff — upgrading deleted almost three times more code than it added! The upgrade made the code base simpler, and it was easier to understand the intent of each function. Not to mention that since each function now has its own lambda, they are each virtually infinitely scalable, which unlocks new possibilities for the Carbon backend altogether.
Need help migrating your frontend or API to Now 2.0? We’re Next.js and Now experts. Please don't hesitate to reach out!