How to deploy to Vercel with GitHub Actions
Published on Sunday, August 14, 2022
Today, you will learn how to use GitHub Actions to create production and preview deployments on Vercel. The default integration with Vercel listens for events on GitHub to automatically trigger builds and deployments. While this works well, there are cases where a bit more control over the build and deployment process is necessary.
Add Vercel CLI to your project
To deploy to Vercel from GitHub Actions, use the NPM package vercel
. You can install vercel
globally as suggested in their docs, or you can install it as a dev dependency in your project as shown here.
yarn add --dev vercel
For the initial setup, you must authenticate vercel
in order to create or link a project.
yarn vercel login
After authenticating, run the following command to create a new project or link an existing one. This will create a new file at .vercel/project.json
.
yarn vercel link
Create a new Vercel Access Token on your account tokens page, and then find your project and organization ids in .vercel/project.json
. Finally, add VERCEL_ORG_ID
, VERCEL_PROJECT_ID
, and VERCEL_TOKEN
to your GitHub repo as secrets.
Create preview deployments on Pull Requests
Create a new workflow under the .github/workflows
directory called preview.yml
. There are a number of events you can use to trigger this workflow, but I recommend starting with pull_request
and workflow_dispatch
for now.
# .github/workflows/preview.ymlname: previewon: pull_request: workflow_dispatch:
To scope vercel
to your current project and organization, you need to expose the corresponding secrets you stored on GitHub earlier as environment variables. The Vercel Access Token is used directly in the job, so you do not need to expose it here.
# .github/workflows/preview.ymlenv: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
The last section is where you configure the actual code to run when the workflow is invoked. This workflow defines a single job called preview
that installs the project dependencies with yarn
. It then pulls the project settings from Vercel for the preview
environment.
# .github/workflows/preview.ymljobs: preview: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 cache: yarn - run: yarn install --immutable - run: yarn vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
To build and deploy the app, you need to add two more steps to the job.
# .github/workflows/preview.yml - run: yarn vercel build --token=${{ secrets.VERCEL_TOKEN }} - run: yarn vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
The vercel build
command will invoke the build
script in your package.json
file. It will then transform the output to match Vercel's Build Output API specification. The vercel deploy
command will then deploy the resulting build to Vercel. Take note of the --prebuilt
flag. Without it, Vercel will push your source files instead and attempt to run vercel build
on their platform.
While this configuration works as it is, I recommend following the next section to configure this workflow to automatically add deployment links to your Pull Requests. If you do not want deployment links automatically added to your Pull Requests, feel free to skip ahead to production deployments.
Automatically add deployment links to Pull Requests
On Pull Requests, GitHub will add deployment links to the Conversation tab when an environment is specified.
# .github/workflows/preview.ymljobs: preview: runs-on: ubuntu-latest environment: name: preview url: https://preview.deployment.example
Vercel generates unique links when you run vercel deploy
, and you can use those dynamic links as the environment URL with some modifications to the workflow. Outputs allow you to store data for use in subsequent steps or jobs.
Assign an id to the vercel deploy
step called deploy
so that we can reference it later. Store the result of the deploy command in an output called url
.
# .github/workflows/preview.yml - id: deploy run: echo "::set-output name=url::$(yarn vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})"
In the environment
section, update the url
field to use the url
output of the deploy
step.
# .github/workflows/preview.yml url: ${{ steps.deploy.outputs.url }}
The full preview workflow
The final result of the preview workflow should look something like this.
# .github/workflows/preview.ymlname: previewon: pull_request: workflow_dispatch:env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}jobs: preview: runs-on: ubuntu-latest environment: name: preview url: ${{ steps.deploy.outputs.url }} steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 cache: yarn - run: yarn install --immutable - run: yarn vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} - run: yarn vercel build --token=${{ secrets.VERCEL_TOKEN }} - id: deploy run: echo "::set-output name=url::$(yarn vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})"
Create production deployments on commits to main
The workflow for production deployments is very similar to the workflow for preview deployments. Start by creating another workflow under the .github/workflows
directory called deploy.yml
. For this workflow, use the push
and workflow_dispatch
events.
# .github/workflows/deploy.ymlname: deployon: push: branches: - main workflow_dispatch:
Then, expose the same environment variables we used for preview deployments.
# .github/workflows/deploy.ymlenv: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
Aside from the workflow triggers, the main difference is in the vercel
commands. The vercel pull
command must specify the environment as production
, and the vercel build
and vercel deploy
commands require the --prod
flag to be set.
# .github/workflows/deploy.ymljobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 cache: yarn - run: yarn install --immutable - run: yarn vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - run: yarn vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} - run: yarn vercel deploy --prod --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
The full production workflow
The final result of the production workflow should look something like this.
# .github/workflows/deploy.ymlname: deployon: push: branches: - main workflow_dispatch:env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 cache: yarn - run: yarn install --immutable - run: yarn vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - run: yarn vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} - run: yarn vercel deploy --prod --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
I like to avoid vendor lock-in where possible, and the documenation around this approach seems to be pretty sparse. I hope this post helps you as much as figuring out how to do this has helped me. If you want to see more posts like this one, leave a comment down below.
As always, happy coding. ✌️