Deploying Next.js Application on Vercel, Heroku, and a Custom Static Server


In this post, we will be looking at Next.js app deployment on different types of servers and using different technologies, such as:

  • Vercel;

  • Heroku;

  • and custom static server via SSH.

We will go through deployment setup on each technology step by step and show the code.


Some time ago web-developers shipped their applications in production by hand. They might use FTP or some other protocols to copy built application assets to production server. This approach has a lot of downsides.

  1. It means that every developer that wants to ship a new version of an application should have access to production server. Along with the security risks it is also inconvenient because it complicates the onboarding process and access and roles management.

  2. It also means that every developer must have all the equipment for building the whole project. Otherwise, it is impossible to build a new version of an application.

  3. There might be collisions in builds. Let’s say we have a team of 3 developers and 2 of them decide to ship a new version, but those versions are slightly different from each other. Manual shipping may result in an inconsistent application state or even a broken application.

Automatic deployment solves these problems by extracting the shipping process from development. Thus, it makes it possible to ship applications consistently, continually and automatically.

Why You Need Automatic Deployment#

Deployment in general is all the processes required to make an application available for use. In a case with a web-app typical deployment usually consists of building an app and uploading its assets to a server (production or staging).

Deployment is automatic if all those actions happen without human interaction. So if a developer has to build an application themselves it’s not automatic deployment.

Automatic deployment has many advantages:

  1. It makes it easier to ship software more often and, thus, receive feedback more often and in more detail and in a specific way. For example, if we ship our app every time we create a new feature, it will be more clear to determine which feature will cause issues.

  2. It decreases the amount of errors and erases the human factor. People can be tired and inattentive because of what they can make mistakes in the deployment process. This may lead to incorrect configuration and unavailable application in production. Automatic deploy solves this by removing people from the process.

  3. It integrates developers and allows a team to distribute development. When everyone can trigger an update simply by merging their changes in the repo it accelerates feature creation.

  4. It allows to set up different and sometimes difficult schemes of deployment. For example, when your code should not only be built and shipped but should pass some tests and acceptance criteria. Automatic deployment makes it easier to integrate such actions.

In our case, the deployment will consist of building an app and delivering its assets to a server.


What You Will Learn#

At the end of this post you will learn how to set up automatic deployments with Vercel, Heroku, and on your own server via SSH from your GitHub repo.


We suppose you have a GitHub account—it will be needed throughout the whole post. We will use pull-requests as triggers to deployments.

Also, we suppose you know what Next.js is and how to create applications using it. If you don’t know it yet, you can learn it from the 5th chapter of “Fullstack React with TypeScript” book and for this tutorial you can use our app starter pack.

For the last section of this tutorial, we suppose you have a server with SSH access to it. You’re going to need one to be able to allow GitHub and GitHub Actions to upload app assets.


Deploying an App to Vercel#

Vercel is a solution for deploying applications built with Next.js from creators of Next.js. As it put in official Next.js documentation, it is “the easiest way to deploy Next.js to production”. Let’s try it.


Setup a GitHub Repository#

To connect Vercel and start deploying you’re going to need a GitHub repository. It will allow connecting Vercel to codebase and trigger deployment on new commits in the master branch. If you already have a repository with an application built with Next.js you can skip this section.

Create a new repo at the “New” page. If you don’t want to create a repository, you can fork ours with app starter files.

Clone the project repository on your local machine and open the project.

If the repository is empty, add application code, commit and push it.


Create an Account on Vercel#

Vercel account allows you to connect your projects’ repositories and monitor deployments as well as see the deployment history.

Create an account on the signup page. If you already have an account, you can skip this section.


Import a Project to Vercel#

When you created an account you should have access to Vercel’s Dashboard.

The dashboard is like a control panel. Here you will be able to see the last deployments of every application and recent activity of an imported project. Let’s import the project repo.

Find the “Import project” button and hit it. You will be redirected to the “Import” page. On that page find the “Import Git Repository” section. Click ”Continue“ button in that section.

You will see a form with an input that requires an URL of a git repository. Enter your project’s repository URL there and submit the form.

When it’s done Vercel will redirect you to GitHub. There you will be asked by GitHub for permissions to repository. Allow Vercel read and write access to the selected repository. It is possible to allow third-party applications to read and write every repository you have, but not recommended by security reasons. Try to limit third-party application’s access as minimal as possible.

After you grant permissions to Vercel, GitHub will redirect you back to the “Import” page. There you will see import settings:

Keep the project name the same as the repository name to make it less ambiguous.

The last two options may be useful if you have, for example, a custom build script. Let’s say, to build a project you use instead of standard npm run build some another command. In that case, you can override default command with your own in the “Build and Output Settings” section.

Same with the “Environment variables”. Sometimes you might need to configure the build process from outside. Usually, it is done with environment variables that are passed via command line. The most often example is NODE_ENV variable, which configures what kind of build should be triggered. Its value set to production usually tells that this is a production build and should be optimized.

In a case with Next.js application, we don’t need to configure anything except for a project name. When you set it up, hit the “Deploy” button. You will see the congratulations screen with a “Visit” link, which will lead to a freshly deployed app on domain. Hit this link to open and inspect the current deployment.

And that’s it! You just deployed an application and made it available for users!


Production and Preview Deployments#

Now, return to the Dashboard for a minute. The new project will appear in a list of your imported applications. Click a link to your project.

On a project page, you will see the ”Production Deployment“ section.

It contains information about current production deployment, such as:

  • Repository branch from which application was deployed. By default, it is master but can be changed in settings. You may want to change the default branch due to company policy for example.

  • Build logs. Those are useful when a build is failed for some reason. Build logs will help to investigate an incident and find a problem that lead to failure.

  • All the domains associated with this project. By default, Vercel hosts a project on a subdomain of domain. You can also configure this in project settings.

Below, you should see a “Preview Deployments” section. This section contains non-production deployments, such as staging and testing deployments. Let’s say you want to test some features in the environment very close to production, but don’t want to ship an untested feature in production. Preview deployments will help you do that.

By default to create a preview deployment, you need to create a pull-request (or merge-request) to default branch in your repository. Vercel will analyze the state of a repo and if there are some pull-requests to the default branch, it will deploy every new commit in this pull-request as preview deployment.

To test it, create a new branch in the repository. Checkout to this branch on your local repo, make some changes in your code, commit and push them to the created branch. After that create a pull-request from a new branch to default.

When it’s done, Vercel-bot will automatically deploy this pull-request as a preview and leave a comment right in pull-request. You won’t even need to return to the Dashboard to inspect your project, the link to preview will be in the bot’s comment!

And of course, the preview deployment will appear in the list of preview deployments in Dashboard.

Deploying an App to Heroku#

Heroku is a container-based cloud platform for deploying and managing applications. It also allows you to automate deploys and trigger them by pushing to repository's default branch.


Setup a GitHub Repository#

The first step is basically the same as in the previous section. You’re going to need a GitHub repository. New commits in the master branch will trigger deployments on Heroku as well. If you already have a repository with an application built with Next.js you can skip this section.

Create a new repo at the “New” page. If you don’t want to create a repository, you can fork ours with app starter files.

Clone the project repository on your local machine and open the project.

If the repository is empty, add application code, commit and push it.


Create an Account on Heroku#

Heroku also allows you to monitor your connected apps and their activity. To connect Heroku with your GitHub repository you’re going to need a Heroku account.

Go to the signup page and create an account. If you already have one, you can skip this section.


Create a New Heroku App#

When you have an account on Heroku, you should have access to its Dashboard. The Dashboard contains lists of all the connected apps and services.

To create a new app find a “New” button and hit it. In a select chose “Create new app” option. It will redirect you to a page with the new app settings screen. There you will be able to choose a name for your app, and a region.

The region can affect performance and download time. For example, for users in Europe app deployed to the US region might load a bit slower than for users in the US because of the distance a request should pass between a user and a server.

When it’s done, add a new pipeline with the button below. A Heroku pipeline is a set of actions being performed over your application. Let’s say you want not to just deploy an app, but to test is first and only then deploy—this set of testing and deploying actions is a pipeline.

Heroku Pipelines represent steps of the continuous delivery workflow. In this case you can select “Production” since we won’t run any tests and want to just deploy the application.

After it’s done, you will be asked about the deployment method. There might be 3 options:

  • Heroku Git, uses Heroku CLI (we will talk about it a bit later);

  • GitHub;

  • and Container Registry.

The first one is convenient when you have a git repository and you want to deploy an app right from the command line. If you have a Heroku CLI installed, there is a special command for deploying from the command line:

But since we’re using GitHub select the “Connect to GitHub” method. Then select a repository to connect to from a list below. You might be asked for repository permissions. Again, try to keep third-party app access as minimal as possible. When you grant permissions to Heroku you can set up automatic deploys for some branch.

By default automatic deploys may be turned off, so don’t forget to check the checkbox in this section. When you turn them on, select a branch from which to deploy. By default it is master, but you can select any other branch in your repository.

Optionally check “wait for CI to pass before deploy” to ensure your tests pass before a project goes to production if there are any.


Setup GitHub Actions#

GitHub Actions are an automatization workflow for building, testing, deployment, and other routines. They allow you to create a custom life cycle for your app. So, you can setup code-linting, code-formatting, some code checks, and all. They are like robots that receive messages and do some stuff.

Actions are being set up by YAML-files, that describe what to do and what triggers this action.

To tell GitHub that there is an action that should be played, create a directory called .github, mind the dot in front of the title. This is the directory that contains all the actions and workflows for this repository.

Inside of that directory create another one called workflows. A workflow is a set of actions. So if you want to chain some actions together in a list you can use a workflow. In workflows directory create a file called main.yml, this is the workflow.

Open this file and paste this code inside:

Let’s break it down. The first line describes the name of this workflow. When GitHub will run this workflow, in a workflow dashboard you will see a list of all created workflows.

The next directive is on. It describes what events should trigger this workflow. In this case, workflow should be triggered on push event in the master branch of this repo. So when someone pushes to master branch this workflow will be played.

For deployment with Heroku, there is an action called “Deploy to Heroku”.

Open its page and scroll to the “Getting Started” section. There you will see an example of the workflow setup.

Let’s examine it. jobs contains all the work to do. In this case, there is only one job to do—build. This job runs on ubuntu-latest, this is the type of a machine that runs a workflow. The steps directive describes what steps to perform.

In the example, each step is a GitHub Action.

Latter is being run with some arguments that are described with with directive.

  • heroku_app_name is the name of your app in Heroku;

  • heroku_email is an email that a Heroku account is tied with;

  • heroku_api_key is an API token, that tells Heroku that this repository is the one it needs.

heroku_api_key should be generated and stored in the “Secrets” section in GitHub. For this, you’re going to need Heroku CLI.


Install Heroku CLI#

If you already have it installed you can skip this section.

Heroku CLI makes it easy to create and manage your Heroku apps directly from the terminal. It allows you for example deploy right from your terminal. However in this case you need it for another reason.

You need to generate heroku_api_key for the repository secrets section. This is done with Heroku CLI.

To install Heroku CLI go to its page and select an OS that you use. Note that different OSs use different installation methods.

When it’s done check if there are no errors by running:

Then, authenticate with this command:

You’ll be prompted to enter any key to go to your web browser to complete the login. The CLI will then log you in automatically. Thus, you will be authenticated in your terminal when you will use heroku CLI command.

Notice YOUR_EMAIL in its response, this should be the same you set in heroku email of main.yml.


Generate Heroku API Key#

To generate a new token open your terminal, check if Heroku CLI is installed and run

As a response, you will get a generated token. Copy its value and create a new GitHub Secret with it. Go to the “Secrets” section in GitHub: Settings → Secrets → New Secret. Set HEROKU_API_KEY as a name and generated token as a value.

Save this secret. GitHub will use this value and replace ${{secrets.HEROKU_API KEY}} with it at build time automatically.

Update start Script#

In your package.json update start script to be as an example below. Pay attention to $PORT environment variable, that must be specified.


Trigger Deploy#

When it’s done you should be able to trigger deploys by pushing changes to the master branch. Try to update code and push to master.

In Dashboard, you will see a new app in the list of apps. Click on it and you will be redirected to an “Overview” page. There you should see latest activity and all the settings for this project. Find the “Open App” button to visit your application and inspect it.

Deploying on a Custom Server via SSH#

Sometimes third-party solutions wouldn’t work. It might happen for many reasons: it can cost a lot, or due to security reasons, but it might happen. In this case, there is an option to deploy an application on your own server.

In this section, we suppose you have a server with SSH access to it. It will be required later.

Setup a GitHub Repository#

The first step is basically the same as in the previous section. You’re going to need a GitHub repository. New commits in the master branch will trigger deploy. If you already have a repository with an application built with Next.js you can skip this section.

Create a new repo at the “New” page. If you don’t want to create a repository, you can fork ours with app starter files.

Clone the project repository on your local machine and open project.

If the repository is empty, add application code, commit and push it.


Setup GitHub Actions#

We explain in detail what GitHub Actions are in one of the previous sections. If you haven’t read it, we recommend you to.

You’re going to deploy an app via SSH. For this, there is an action called “ssh deploy”. It uses Node.js and integrates by YAML-file as other GitHub Action do.

This GitHub Action deploys a specific directory from GITHUB_WORKSPACE to a folder on a server via rsync over ssh. This workspace is a directory that is created by checkout action we used before. The workflow will be:

  • checkout code;

  • build the project;

  • export static files using special Next.js command;

  • upload built assets on a server.

Let’s create a new file in .github/workflows directory called custom.yml. This is the file that will describe your new workflow. In this file write the name of the workflow and events that should trigger it.

This code means that this workflow will be triggered on every new push in the master branch. The detailed explanation of every line of this code you can find in the section above. Then, describe jobs to do and steps.

Here we tell GitHub to check out this code, it will create a workspace that can be accessed with GITHUB_WORKSPACE. The second action sets up Node.js with version 12. (LTS on the moment this post is being written.) Then describe build step:

It will install all the dependencies, build the project, and export static files.

next export allows you to export your app to static HTML, which can be run standalone without the need of a Node.js server. It works by prerendering all pages to HTML. The reason we use npx is because we didn’t install next CLI tools globally, so it won’t be found in GitHub Action runtime, which will cause an error. npx on the other hand will execute a local package's binary.

The last step in the workflow is:

It tells GitHub Actions to use ssh-deploy action and pass some of the environment variables:

  • SSh_PRIVATE_KEY, a private key generated on a server, required to allow a third-party app to connect via SSH;

  • SOURCE, a directory from which to take assets to deploy;

  • REMOTE_HOST, a host on which to deploy;

  • REMOTE_USER, a user that will connect via SSH to a server;

  • TARGET, a directory in which to upload the app assets.

secrets. mean that the value will be requested in GitHub Secrets, and for this to work you're going to need to specify those secrets that should be stored in the “Secrets” section in your GitHub repository.


Setup Project Secrets#

Create 4 new secrets in your project. Go to the “Secrets” section in GitHub repository and create:

  • REMOTE_HOST, as a value specify the server’s host URL. It can be a domain name or an IP-address.

  • REMOTE_USER, a user that has SSH-access to a server. It will be used by ssh-deploy to connect to your server. Always try to minimize the scope of things a third-party app can do with your repo or a server. In the case of SSH-user, it is better to have a special user just for this task.

  • TARGET, a directory where to upload app assets. Specify the whole path to a directory. Depending on your server it might look like /var/username/www/html, or /www/html, or something like that.

  • SSH_PRIVATE_KEY, a private key, which is going to be generated on a server.

Generate Keys for GitHub Actions on a Server#

Connect to your server via SSH. (You may be asked a password if you connect the first time.)

When connected, generate a new key pair. Keep the passphrase empty. Specify the type of the key as RSA. You might want to set some unique name for the key, just to make it easier to find it later, so when a command-line ask you how to name the file, you can change it.

When generated, authorize those keys, otherwise, the server might not allow “ssh-deploy” to connect. Note the ~/.ssh/key-name—that’s the full path to the key file. It may vary depending on your file structure on a server.

Now copy the private key value and paste it as a value for SERVER_SSH_KEY in the “Secrets” section.

Trigger Deploy#

When everything’s set, you can trigger new deploy. Push some changes to the master branch, and GitHub will run the workflow, which will build the app, export it and let ssh-deploy deploy it.


In this post, we explained how to deploy your Next.js application using Vercel, Heroku, and how to deploy it on a custom server using SSH.


About Next and Vercel#

Additional resources#