Skip to content

Instantly share code, notes, and snippets.

@ky28059
Last active June 28, 2024 11:42
Show Gist options
  • Save ky28059/1c9af929a9030105da8cf00006b50484 to your computer and use it in GitHub Desktop.
Save ky28059/1c9af929a9030105da8cf00006b50484 to your computer and use it in GitHub Desktop.
Deploying to Vercel from an organization for free using GitHub actions

This gist was partially inspired by this blog about Next.js Vercel CI with GitHub actions.

The problem

An easy way to deploy and host websites for free is to use GitHub pages. If you've deployed a Next.js project to GitHub pages, you may have used a GitHub action similar to this in the past to automatically redeploy the site when a new commit is pushed:

# gh-pages-merge.yml
name: Deploy to gh-pages on merge
on:
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - env:
          CLIENT_EMAIL: ${{ secrets.CLIENT_EMAIL }}
          PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
          SPREADSHEET_ID: ${{ secrets.EVENTS_APP_SPREADSHEET_ID }}
        run: npm ci && CI=false npm run build && npm run export
      - uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: './out'

However, GitHub pages only serves static files, which means you'll have to export your app as static. In the process, you'll lose access to server-only features like server-side rendering, API routes, dynamic routes, and so on.

Unlike GitHub pages however, hosting with Vercel supports a plethora of frameworks and all their advanced, server-dependent features. Best of all, it is completely free for hobby users.

The issue lies with trying to deploy a repository from a GitHub organization. Typically, deploying a project on Vercel only involves importing a git repository, but attempting to import a repository owned by an organization will prompt Vercel to create a team, a feature only available to pro-plan users (at $20/month per user).

image

If you don't need the advanced features provided by the pro plan and can't afford such a steep cost for a small project, this is a problem.

The solution

The idea is to use GitHub actions as our CI and deploy the project through your personal (hobby) subscription via the Vercel CLI. Full docs on the CLI are linked, but all we need to do locally is to create and link the project to Vercel.

Install the Vercel CLI by running

npm i -g vercel

and in your project directory, link it to Vercel by running

vercel

The CLI will prompt you about project details; when asked for a deployment scope, choose your own username. This should initialize the project on Vercel under your personal account, and automatically detect and configure the framework and build presets.

image

After successfully initializing a project, the CLI will create a .vercel directory containing a generated project.json that looks something like this:

{"orgId": "...", "projectId": "..."}

GitHub actions

To automatically deploy our project to Vercel on push, we'll use amondnet/vercel-action.

A typical GitHub pages action builds the project to static files and then commits the built files to the gh-pages branch to be deployed by GitHub pages; for Vercel however, we want to deploy our unbuilt project to Vercel, then let Vercel handle the build and deployment1.

To use vercel-action you'll need a Vercel Account Token, which you can create here.

image

When created, add your orgId and projectId from project.json and your account token as repository secrets.

image

In the GitHub action, pass the secrets we created to vercel-action2. The workflow file should look something like this:

# vercel-merge.yml
name: Deploy to vercel on merge
on:
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          github-token: ${{ secrets.GITHUB_TOKEN }}
          vercel-args: '--prod'
          vercel-org-id: ${{ secrets.ORG_ID}}
          vercel-project-id: ${{ secrets.PROJECT_ID}}

The action should mimic Vercel's GitHub CI, deploying each new commit to Vercel and commenting on successful pushes.

Additional consideration: preview URLs

The vercel CLI can also generate preview URLs for pull requests.

# vercel-pull-request.yml
name: Create vercel preview URL on pull request
on:
  pull_request:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: amondnet/vercel-action@v20
        id: vercel-deploy
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          github-token: ${{ secrets.GITHUB_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID}}
          vercel-project-id: ${{ secrets.PROJECT_ID}}
      - name: preview-url
        run: |
          echo ${{ steps.vercel-deploy.outputs.preview-url }}

The above workflow will run on all pull requests to main, generating a preview URL for each that's updated for the latest commit on the branch.

image

(Note that unlike Vercel's actual CI preview URLs, these URLs are associated with a commit and not an entire pull request. Therefore, the URL will change every time a new push is made to the pull request branch.)

Additional consideration: environment variables

If you're using GitHub secrets to pass environment variables to your project at build time, letting Vercel handle project building means you can no longer do so. Instead, you should set the environment variables within Vercel with dkershner6/vercel-set-env-action.

The vercel-set-env-action README explains how environment variables should be formatted in the action arguments. Between the checkout and vercel-action, add the env variable setting in your workflow like so:

      - uses: dkershner6/vercel-set-env-action@v1
        with:
          token: ${{ secrets.VERCEL_TOKEN }}
          projectName: events-app
          envVariableKeys: CLIENT_EMAIL,PRIVATE_KEY,SPREADSHEET_ID
        env:
          CLIENT_EMAIL: ${{ secrets.CLIENT_EMAIL }}
          TARGET_CLIENT_EMAIL: preview,development,production
          TYPE_CLIENT_EMAIL: encrypted
          PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
          TARGET_PRIVATE_KEY: preview,development,production
          TYPE_PRIVATE_KEY: encrypted
          SPREADSHEET_ID: ${{ secrets.EVENTS_APP_SPREADSHEET_ID }}
          TARGET_SPREADSHEET_ID: preview,development,production
          TYPE_SPREADSHEET_ID: encrypted

The full workflow file should look something like this:

name: Deploy to vercel on merge
on:
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: dkershner6/vercel-set-env-action@v1
        with:
          token: ${{ secrets.VERCEL_TOKEN }}
          projectName: events-app
          envVariableKeys: CLIENT_EMAIL,PRIVATE_KEY,SPREADSHEET_ID
        env:
          CLIENT_EMAIL: ${{ secrets.CLIENT_EMAIL }}
          TARGET_CLIENT_EMAIL: preview,development,production
          TYPE_CLIENT_EMAIL: encrypted
          PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
          TARGET_PRIVATE_KEY: preview,development,production
          TYPE_PRIVATE_KEY: encrypted
          SPREADSHEET_ID: ${{ secrets.EVENTS_APP_SPREADSHEET_ID }}
          TARGET_SPREADSHEET_ID: preview,development,production
          TYPE_SPREADSHEET_ID: encrypted
      - uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          github-token: ${{ secrets.GITHUB_TOKEN }}
          vercel-args: '--prod'
          vercel-org-id: ${{ secrets.ORG_ID}}
          vercel-project-id: ${{ secrets.PROJECT_ID}}

Footnotes

  1. This is contrary to what is stated in vercel-action's README about skipping Vercel's build step, but for a Next.js project I found it hard to build locally (in the action) and deploy the built output to Vercel while still having it deploy the project as your selected framework. Easier is to just deploy the whole project and let Vercel build it for you.

  2. vercel-action's README contains more info about additional options you can pass, and the Vercel CLI docs contain more info about flags you can pass to vercel-args. Here, passing --prod to vercel-args deploys the project to a production domain instead of a temporary commit-specific preview URL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment