Teams using Next.js and Vercel have an advantage

During my time at Palo Alto Networks, I spent most of my time working on a product called AutoFocus. It helped cyber security research teams analyze files traversing our firewalls for signs of malware. It was pretty cool, fronted by a large React application, with a bunch of disparate backend services and databases scattered around.

One of the things that was difficult to do was deploy our software. We were on a roughly 3 month release cycle to begin with, which meant several things:

  • Out-of-band bug fix releases were expensive
  • We didn't get much practice deploying, so when we did, it was a team effort, error prone and took a long time.
  • Trying to estimate and scope 3 months of work for a team of 10 is a fool's errand

Deployment meant getting most of the team into a war room, manually uploading build files to various places, doing a sort of canary deploy, seeing if things seemed ok, then rolling out to the rest of the world. Sometimes we decided to roll out architectural changes to reverse proxies and things at the same time, just for fun.

When I became engineering manager of the UI team for AutoFocus, my top priority was to change all that. We spent 6 months of solid effort to modernize how we built our software, culminating in fully automated CICD to production, staging and unlimited dev environments. It was awesome. It cost us 6 months to get what Vercel gives you out of the box. Don't build it yourself.

CICD

It's 2024. If you're not doing CICD you're seriously behind the curve at this point. The top 5 things that prevent engineers from actually building value for your product are, in no particular order:

CICD takes care of that second item, by making deployment a non-event. These days, CICD is so easy to achieve with the right technology selections that I'd advise making it one of the first things you do when you start a new project. If you're not automatically deploying code to some environment by the end of the first day, you've probably left it longer than you ought to.

Review Apps and the joy of unlimited Environments

Your application needs to run in a bunch of different places. There is the canonical instance your users mainly use, which we usually call production. Then there are instances of your application running on your developers' laptops, which we call development.

It used to be a common pattern to have a single canonical staging environment, which was a place where one could deploy code before putting it in production. This is generally a poor approach these days. Instead of a single staging instance, which always encounters contention when multiple developers want to deploy code there, multiple PMs and sales folks want to demo from there, QA folks want to test against, and so on, you need to set up your application to run in infinitely many different environments.

In a setup like this, production, demo and other persistent environments are nothing special compared to any other environment. But in addition to this, it is enormously powerful to have the tip of every branch continually deployed to a unique environment. Some people call these ephemeral environments Review Apps, others Preview Deployments. They are automatically created when a branch is first pushed, and automatically destroyed when the branch is deleted.

Suddenly all of the stakeholders for your application can visit a predictable url like https://my-branch-name.myapp.com and see exactly what the application will look like when that code is deployed to production. Automated integration tests can run against these environments, with the results automatically attached to the PR.

Prerequisites and what Vercel does for you

Why did it take us 6 months to roll this stuff ourselves? Well, to pull this off, you need a bunch of things in place:

  • Infrastructure as Code (IaC)
  • Reliable and trustable automated testing
  • CICD plumbing code (GitHub or GitLab Actions, Jenkins, etc)
  • DNS for your Review Apps
  • SSL cert generation for your environments

Some of this will depend on the complexity of your application. CICD Pipeline development is fun, but it's time consuming and usually consists of hundreds of commits and pushes to see if the machine is working as expected. It's a lot of work to get right, and encompasses quite a diverse range of skills. Certainly I had to learn a bunch about Kubernetes, Docker, Terraform, and a bunch of other stuff to get it right, as well as orchestrating DNS entries, SSL certs, ingress controllers, and so on.

Vercel effectively handles the CICD plumbing, DNS hookup and SSL cert generation for you. It does the branch deployment to a Review App environment with an SSL cert and DNS entry out of the box. That's a huge win.

You're still on the hook for automated testing that you actually trust. If you can keep your app architecture simple then you don't actually need to define any IaC code - the key is more that you don't need a human in the loop to create or destroy environments. As far as testing goes, best in class is a set of integration tests that execute against a controlled data set, running on every push to every branch.

More complex architectures

There is a point at which this starts to break down. Getting simple apps up and running is very easy, but incorporating a more complex architecture can be difficult to the point of maddening (Heroku suffers spectacularly from this - some of my most inventive cursing ever emanated from trying to deploy to heroku as part of a larger environment deployment). The simpler you can keep your application architecture, the easier it is to build on top of the Vercel platform.

Sometimes that's not realistic though. AutoFocus, for example, relied on a collection of backend services, databases, and other things that are not easily deployable to Vercel. Often there will be petabytes of data in the picture, sometimes attached to written promises that it won't leave your own data center. That massive Elastic Search cluster that holds 10 years of customer data isn't getting migrated into Vercel any time soon.

So how do we deal with this? It will depend on your application, but the general solution is to split your front-end out from the backend stuff. Keep that front end deployable to Vercel and keep all of the productivity benefits that come with it. If your backend is just a relational database and maybe some object storage, keep it all together and deploy to Vercel. If it's significantly more complex than that, split the frontend (and frontend-adjacent) stuff into its own repo so your frontend team can keep moving fast and leverage the productivity benefits of Vercel.

Your backend can't be a ghetto, though. It needs to be IaC, and it almost certainly needs some kind of controlled dataset. Ideally you have a 1:1 mapping between each frontend environment and a pristine backend environment, so that you can run integration tests against the entire system. You can still deploy the UI-centric portion of your application to Vercel using Deploy Hooks - this is useful if you need to trigger a backend deployment as part of your frontend deployment.

A short dev loop

UI development benefits enormously from a sub-second developer loop. Within a second of hitting save on a file, I expect to see the following:

  • Updated UI rendered in my browser
  • Test suite execution and results

Messing around with getting CSS right is a miserable process if I have to keep switching between windows, hitting refresh on a browser, or far worse having to build something first. With the proper hardware an engineer can keep their entire development environment in view at all times, and see the effect of their changes immediately. Next.js does this out of the box - you just need the pixels to see it all.

It's hard to overstate how liberating this is for a frontend-centric developer. Coupled with fast CICD, you can iterate at high velocity at all stages of the development process and keep yourself in that high-productivity flow state for much longer.

Beyond what Next.js gives you out of the box, you basically need 2 additional scripts to keep engineers working on a local environment running at high speed:

  • Test suite runner (with file watching). If this takes more than a second on decent hardware, you've probably written the wrong tests.
  • Controlled Dataset loader. A one-liner to load a realistic dataset into your local environment. Developing against empty datasets leads to crappy user experiences.

Relationship with React

It has been commented upon that Next.js and React are pretty close bedfellows these days. New features like React Server Components are only feasible in a handful of frameworks, one of the small number of which is Next.js. Not everyone is thrilled with this state of affairs, grumbling with some justification that you need to be using Next.js to get the most out of React, where React should be a standalone library.

And they're right: to get the most out of React, you should be using Next.js (or another framework like it - though your options are not many). This may have negative implications for the React ecosystem, but if your objective is to build a UI as quickly as possible, you should be using Next.js, and you should probably be deploying to Vercel. A lot of the new tech like RSCs just aren't going to work otherwise.

The ball is being pushed forward rapidly here, especially in the realm of application performance. RSC, SSR, SSG and ISR are all TLAs that can be game changers for the way your application feels when humans use it. Stuff is going to break and wrong turns are going to be made - the leading edge is a better place to be than the bleeding edge, but the rewards for your team and your users are enormous.

Share Post:

What to Read Next