|
| 1 | +--- |
| 2 | +title: Automating the deployment of a static website to Vercel with GitHub Actions |
| 3 | +description: | |
| 4 | + Use GitHub Actions and the Vercel CLI to automate the deployment of a static website to Vercel. |
| 5 | +publishDate: 2026-02-26 |
| 6 | +heroImage: '../../../../content/post/2026/02-26-vercel-static-github-actions/_images/hero-vercel-github-actions-astro.png' |
| 7 | +heroAlt: Vercel, Github Actions, Astro logos |
| 8 | +tags: |
| 9 | + - devops |
| 10 | + - serverless |
| 11 | + - astro |
| 12 | +category: tutorial |
| 13 | +toc: true |
| 14 | +draft: false |
| 15 | +--- |
| 16 | + |
| 17 | + |
| 18 | +## Introduction |
| 19 | + |
| 20 | +This article focuses specifically on deploying static websites to Vercel. In a previous article https://nemanjamitic.com/blog/2026-02-22-vercel-deploy-fastapi-nextjs, we covered in detail how to deploy a full-stack application using the Vercel CLI from a local development environment. This time, we will use the same CLI inside a GitHub Actions runner to automate redeploying a static website on every push, for example, after adding a new blog article in markdown. |
| 21 | + |
| 22 | +As an example, we will deploy the same blog website you are currently reading. The site itself is a statically built Astro application. |
| 23 | + |
| 24 | +## Vercel Github integration vs Github Actions |
| 25 | + |
| 26 | +Vercel supports deployments through a GitHub integration (documented here: https://vercel.com/docs/git/vercel-for-github). You provide Vercel with your GitHub repository URL and read access, and Vercel automatically redeploys your application on every push. If you prefer not to grant Vercel access to your source code or GitHub repository, or if you want more control over the deployment process, you can instead use GitHub Actions, the approach described in this article. |
| 27 | + |
| 28 | +## Vercel configuration files |
| 29 | + |
| 30 | +As with any Vercel deployment, you need to provide Vercel with additional information about the project's build process, such as the framework, build command, and output directory, as well as which files should be included or ignored during deployment. |
| 31 | + |
| 32 | +Before adding any configuration files, go to your Vercel dashboard, create a new project, give it a name, and set all required environment variables. |
| 33 | + |
| 34 | +### vercel.json |
| 35 | + |
| 36 | +The contents of the `vercel.json` file are mostly self-explanatory. We specify the `astro` framework, and the build command and output directory match those used in the local development environment. With this configuration, Vercel knows exactly how to build the application. |
| 37 | + |
| 38 | +https://github.com/nemanjam/nemanjam.github.io/blob/main/vercel.json |
| 39 | + |
| 40 | +```json title="vercel.json" |
| 41 | +{ |
| 42 | + "framework": "astro", |
| 43 | + "buildCommand": "pnpm build", |
| 44 | + "outputDirectory": "dist", |
| 45 | + "cleanUrls": true, |
| 46 | + "trailingSlash": false |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +### .vercelignore |
| 51 | + |
| 52 | +For performance reasons, it is important to avoid uploading files that are not used during the build and deployment process, such as dependencies, `.env*` files, documentation, or Docker-related configuration. The `.vercelignore` file is used to exclude these unnecessary files. Additionally, on the free tier, your deployment must stay below the 250 MB size limit. |
| 53 | + |
| 54 | +https://github.com/nemanjam/nemanjam.github.io/blob/main/.vercelignore |
| 55 | + |
| 56 | +```bash title=".vercelignore" |
| 57 | +# Node / package managers |
| 58 | +node_modules |
| 59 | +.pnpm-store |
| 60 | +.npm |
| 61 | +.yarn |
| 62 | + |
| 63 | +# ! Needed for commit info |
| 64 | +# Vercel omits it by default, now way to upload it |
| 65 | +# .git |
| 66 | +# .gitignore |
| 67 | + |
| 68 | +# Local env files |
| 69 | +.env |
| 70 | +.env.* |
| 71 | +!.env.*example |
| 72 | + |
| 73 | +# Logs |
| 74 | +npm-debug.log* |
| 75 | +yarn-debug.log* |
| 76 | +yarn-error.log* |
| 77 | + |
| 78 | +# Docker & tooling |
| 79 | +docker/ |
| 80 | +scripts/ |
| 81 | + |
| 82 | +# Documentation & notes |
| 83 | +docs/ |
| 84 | + |
| 85 | +# OS / editor junk |
| 86 | +.DS_Store |
| 87 | +.idea |
| 88 | +.vscode |
| 89 | + |
| 90 | +# Astro build cache |
| 91 | +.astro/* |
| 92 | +# Keep types if build needs them |
| 93 | +!.astro/types.d.ts |
| 94 | + |
| 95 | +# Github |
| 96 | +.github/ |
| 97 | +``` |
| 98 | + |
| 99 | +The exact contents of this file depend on your specific project. To ensure you have excluded all unnecessary paths, go to your Vercel dashboard and navigate to **My Project -> My Deployment -> Source**, where you can clearly see exactly which files are uploaded. |
| 100 | + |
| 101 | +## Github Actions workflow |
| 102 | + |
| 103 | +Once again, go to your Vercel dashboard and create an access token in your account settings. Add this token as the `VERCEL_TOKEN` Github repository secret. Then, in your Vercel project settings, copy your user (organization) ID and project ID and add them as the `VERCEL_ORG_ID` and `VERCEL_PROJECT_ID` Github repository secrets. |
| 104 | + |
| 105 | +With this setup, Github is aware of your Vercel project, and **NOT** the other way around. Vercel only receives the compiled application artifacts and has no access to your Github repository or source code. |
| 106 | + |
| 107 | +https://github.com/nemanjam/nemanjam.github.io/blob/main/.github/workflows/vercel__deploy-manual.yml |
| 108 | + |
| 109 | +```yml title=".github/workflows/vercel__deploy-manual.yml" |
| 110 | +name: Deploy to Vercel manually |
| 111 | + |
| 112 | +# Docs example: https://vercel.com/kb/guide/how-can-i-use-github-actions-with-vercel |
| 113 | + |
| 114 | +on: |
| 115 | + push: |
| 116 | + branches: |
| 117 | + - 'main' |
| 118 | + tags: |
| 119 | + - 'v[0-9]+.[0-9]+.[0-9]+' |
| 120 | + |
| 121 | + pull_request: |
| 122 | + branches: |
| 123 | + - 'disabled-main' |
| 124 | + |
| 125 | + workflow_dispatch: |
| 126 | + |
| 127 | +permissions: |
| 128 | + contents: read |
| 129 | + |
| 130 | +env: |
| 131 | + # Project vars |
| 132 | + # Redundant, vercel pull will define them |
| 133 | + # SITE_URL: 'https://nemanjam.vercel.app' |
| 134 | + # PLAUSIBLE_DOMAIN: 'nemanjamitic.com' |
| 135 | + # PLAUSIBLE_SCRIPT_URL: 'https://plausible.arm1.nemanjamitic.com/js/script.js' |
| 136 | + |
| 137 | + # Vercel vars |
| 138 | + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} # user id |
| 139 | + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} |
| 140 | + |
| 141 | +jobs: |
| 142 | + deploy-vercel: |
| 143 | + runs-on: ubuntu-latest |
| 144 | + |
| 145 | + steps: |
| 146 | + - name: Checkout code |
| 147 | + uses: actions/checkout@v4 |
| 148 | + with: |
| 149 | + fetch-depth: 1 |
| 150 | + |
| 151 | + - name: Print commit id and message |
| 152 | + run: | |
| 153 | + git show -s --format='%h %s' |
| 154 | + echo "github.ref -> ${{ github.ref }}" |
| 155 | +
|
| 156 | + - name: Set up Node.js |
| 157 | + uses: actions/setup-node@v4 |
| 158 | + with: |
| 159 | + node-version: 24.13.0 |
| 160 | + registry-url: 'https://registry.npmjs.org' |
| 161 | + |
| 162 | + - name: Install pnpm |
| 163 | + uses: pnpm/action-setup@v4 |
| 164 | + with: |
| 165 | + version: 10.30.1 |
| 166 | + |
| 167 | + - name: Install Vercel CLI |
| 168 | + run: pnpm add -g vercel |
| 169 | + |
| 170 | + - name: Pull Vercel production environment variables |
| 171 | + run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} |
| 172 | + |
| 173 | + - name: Build project using Vercel |
| 174 | + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} |
| 175 | + |
| 176 | + - name: Deploy prebuilt project to Vercel |
| 177 | + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} |
| 178 | +``` |
| 179 | +
|
| 180 | +### Repository secrets |
| 181 | +
|
| 182 | +Vercel's official tutorial already does a good job of explaining the basics and provides a solid starting workflow file: https://vercel.com/kb/guide/how-can-i-use-github-actions-with-vercel. In this article, we will focus on a specific use case: deploying a static website. |
| 183 | +
|
| 184 | +Let's start by explaining the Github repository secrets used in this workflow: |
| 185 | +
|
| 186 | +- `VERCEL_TOKEN` - an access token that the Github Actions runner uses to authenticate with Vercel and create a deployment |
| 187 | +- `VERCEL_ORG_ID` - a Github user or organization ID that identifies who owns the deployment |
| 188 | +- `VERCEL_PROJECT_ID` - identifies the Vercel project being deployed |
| 189 | + |
| 190 | +The `VERCEL_ORG_ID` and `VERCEL_PROJECT_ID` values are passed as environment variables and are defined at the workflow level, making them available to all jobs. The `VERCEL_TOKEN` is passed to individual commands as a command-line argument. |
| 191 | + |
| 192 | +### Set up Node.js and Vercel CLI |
| 193 | + |
| 194 | +The first part of the workflow is standard and straightforward. We simply check out the repository (`fetch-depth: 1` to fetch only the latest commit for speed), then install Node.js, pnpm, and the Vercel CLI. These steps set up the prerequisites needed to build and deploy the project in the following steps. |
| 195 | + |
| 196 | +### Environment variables |
| 197 | + |
| 198 | +Here we are referring to **your project's** environment variables. Since we are deploying a fully static website, all environment variables are strictly **build-time** variables, as explained here: https://nemanjamitic.com/blog/2025-12-21-static-website-runtime-environment-variables. The Vercel target environment does not need to define any variables because they are inlined during the build, immutable, and ignored afterward. This also means the build artifacts are specific to the environment they were built for. |
| 199 | + |
| 200 | +Although variables in the target environment are ignored at runtime, it is still a good practice to define them in the Vercel dashboard and use Vercel as the single source of truth for your deployment. This allows you to easily pull them into the GitHub Actions runner using: `vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}` |
| 201 | + |
| 202 | +The `--environment=production` flag selects the production environment. To deploy to preview environments, you can create a separate workflow `.yml` file triggered by feature branches (any branch other than `main`) and use `vercel pull` with the `--environment=preview` option to fetch the corresponding variables. |
| 203 | + |
| 204 | +```yml |
| 205 | +on: |
| 206 | + push: |
| 207 | + branches-ignore: |
| 208 | + - main |
| 209 | +``` |
| 210 | + |
| 211 | +**Note:** You can define your project's environment variables using the `env:` key at the workflow or job level, but this is generally not recommended. Doing so will lead to conflicts with variables pulled via `vercel pull` and issues with overriding priority, unless you are confident in managing them. Relying exclusively on variables from `vercel pull` ensures clarity and simplicity. |
| 212 | + |
| 213 | +### Building and deploying |
| 214 | + |
| 215 | +At this point, we are ready to build the project using: `vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}`. This command generates the application artifacts in the output folder specified in `vercel.json`, all within the GitHub Actions runner. After that, the Vercel CLI copies the framework's output folder (defined in `vercel.json`) inside the `.vercel/output` folder, creating a deployment-ready package that can be uploaded directly to Vercel. |
| 216 | + |
| 217 | +The final step is to upload the deployment-ready package inside the `.vercel/output` folder to Vercel using: `vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}`. The `--prebuilt` option tells Vercel to skip the build step since the application has already been built in the Github Actions runner. |
| 218 | + |
| 219 | +That's it. Add the shown `vercel.json`, `.vercelignore`, and `.github/workflows/vercel__deploy-manual.yml` files to your repository, then run `git push` to trigger the workflow. Once it completes, you can view your website at `<your-project-name>.vercel.app`. |
| 220 | + |
| 221 | +## Completed code |
| 222 | + |
| 223 | +- **Repository:** https://github.com/nemanjam/nemanjam.github.io |
| 224 | +- **Demo:** https://nemanjam.vercel.app |
| 225 | + |
| 226 | +The relevant files: |
| 227 | + |
| 228 | +```bash |
| 229 | +git clone git@github.com:nemanjam/nemanjam.github.io.git |
| 230 | +
|
| 231 | +# Files |
| 232 | +.github/workflows/vercel__deploy-manual.yml |
| 233 | +vercel.json |
| 234 | +.vercelignore |
| 235 | +
|
| 236 | +.dockerignore |
| 237 | +.gitignore |
| 238 | +
|
| 239 | +# Vercel configuration and workflow in a clear diff |
| 240 | +https://github.com/nemanjam/nemanjam.github.io/commit/c0d6c6739b3215a6841a463115ec5242ea76e492 |
| 241 | +``` |
| 242 | + |
| 243 | +## Conclusion |
| 244 | + |
| 245 | +CI/CD workflows are the standard way to handle deployments, and deploying to Vercel is no exception. By combining Github Actions with the Vercel CLI, you can implement a fully automated deployment pipeline with just a few lines of configuration. |
| 246 | + |
| 247 | +This approach gives you complete control over the build and deployment process while keeping your source code private and your security model explicit. Once in place, deployments become predictable, repeatable, and hands-off. |
| 248 | + |
| 249 | +How do you automate deployments to Vercel in your projects? Let me know in the comments. |
| 250 | + |
| 251 | +## References |
| 252 | + |
| 253 | +- Github Actions with Vercel, Vercel official tutorial https://vercel.com/kb/guide/how-can-i-use-github-actions-with-vercel |
| 254 | +- Astro on Vercel, Vercel docs https://vercel.com/docs/frameworks/frontend/astro |
| 255 | +- Github integration, Vercel docs https://vercel.com/docs/git/vercel-for-github |
| 256 | +- `vercel deploy --prebuilt` option, Vercel docs https://vercel.com/docs/cli/deploy#prebuilt |
0 commit comments