Static sites are fast to serve and simple to host, but the build-and-deploy cycle is easy to neglect when it requires manual steps. A change merged to the repository sits undeployed until someone remembers to run the build command and push the output. For a blog or documentation site, that delay is an unnecessary friction point.
GitHub Actions automates the entire cycle. A push to the main branch triggers a build and deployment without any manual intervention. Pull requests can generate live preview URLs before changes merge. The setup requires a workflow file and a few repository settings.
This guide covers building and deploying a static site with GitHub Actions, with specific examples for GitHub Pages, Netlify, and Vercel, plus optional PR preview deployments.
What this covers:
Structuring the repository for CI deployment
A complete workflow for Hugo with GitHub Pages
Deployment options for Netlify and Vercel
Preview deployments for pull requests
Step 1: Prepare the Repository Structure
The workflow assumes the site builds from a single command. Before creating the workflow file, confirm the build runs cleanly locally:
hugo # Hugo
jekyll build # Jekyll
npm run build # Vite, Astro, Eleventy, or other Node-based generators
A typical repository layout for a Hugo site:
your-site/
├── content/ # Markdown files and page content
├── themes/ # Theme directory
├── public/ # Build output (generated, not committed)
├── config.yaml # Site configuration
└── .github/
└── workflows/
└── deploy.yml
The build output directory (public/, dist/, or _site/ depending on the generator) should be listed in .gitignore. The workflow builds it fresh on every run rather than committing generated files to the repository.
Step 2: Create the Workflow File
Create .github/workflows/deploy.yml:
name: Deploy Static Site
on:
push:
branches:
- main
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
submodules: true # Required if the theme is a Git submodule
fetch-depth: 0 # Required for Hugo's .GitInfo and lastmod features
- name: Set Up Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true # Required for Hugo themes that use SCSS
- name: Build Site
run: hugo --minify
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
What this workflow does:
Triggers on every push to
mainChecks out the repository, including any Git submodules (common for Hugo themes)
Installs Hugo
Builds the site with minification enabled
Deploys the
public/directory to thegh-pagesbranch, which GitHub Pages serves
The GITHUB_TOKEN secret is automatically available in every GitHub Actions workflow and does not need to be created manually.
For Jekyll or Node-based generators, replace the Hugo setup and build steps with the appropriate equivalent. The checkout and deployment steps remain the same.
Step 3: Deploy to Netlify or Vercel
GitHub Pages is the simplest option for static sites, but Netlify and Vercel offer additional features such as edge functions, form handling, and more granular deployment controls.
Netlify
Replace the GitHub Pages deploy step with:
- name: Deploy to Netlify
uses: nwtgck/netlify-action@v2
with:
publish-dir: public
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
netlify-auth-token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
netlify-site-id: ${{ secrets.NETLIFY_SITE_ID }}
The NETLIFY_AUTH_TOKEN is generated in the Netlify account settings under User settings > Applications. The NETLIFY_SITE_ID is found in the site settings on the Netlify dashboard. Both are stored in GitHub Secrets under repository Settings > Secrets and variables > Actions.
Vercel
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
github-token: ${{ secrets.GITHUB_TOKEN }}
The VERCEL_TOKEN is generated in the Vercel account settings. The VERCEL_ORG_ID and VERCEL_PROJECT_ID are found in the project settings on the Vercel dashboard. Store all three in GitHub Secrets.
Step 4: Preview Deployments for Pull Requests (Optional)
Preview deployments create a live URL for every pull request, making it straightforward to review design changes or new content before they merge to the main branch. This is particularly useful for documentation sites and blogs with multiple contributors.
A workflow that handles both production deployments on push and preview deployments on pull requests:
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
submodules: true
- name: Set Up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Build Site
run: npm run build
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
github-token: ${{ secrets.GITHUB_TOKEN }}
alias-domains: pr-{{PR_NUMBER}}.your-domain.com
When a pull request is opened or updated, the workflow builds and deploys the branch to a unique URL. Vercel posts the preview URL as a comment on the pull request automatically when the github-token is provided.
Common Issues and Fixes
The build succeeds but the site is not updating on GitHub Pages. Confirm that GitHub Pages is configured to serve from the gh-pages branch rather than the main branch or the /docs folder. Check under repository Settings > Pages.
The Hugo build fails with a theme-related error. If the theme is included as a Git submodule, the submodules: true option in the checkout step is required. Without it, the theme directory is checked out empty.
The Hugo build succeeds but CSS is missing. Some Hugo themes require the extended version of Hugo to compile SCSS. Add extended: true to the Hugo setup step.
Netlify or Vercel secrets are not found. Secret names in GitHub Actions are case-sensitive and cannot contain hyphens. If the secret was created with a name like netlify-auth-token, it will not be found when referenced as NETLIFY_AUTH_TOKEN. Use uppercase with underscores for all secret names.
Key Takeaways
The build output directory should be in
.gitignore. The workflow generates it fresh on every run.The
GITHUB_TOKENsecret is available automatically in every workflow and does not need to be created.Hugo themes loaded as Git submodules require
submodules: truein the checkout step, and themes that use SCSS requireextended: truein the Hugo setup step.Netlify and Vercel credentials need three separate secrets each: an authentication token, an organization ID, and a project ID.
GitHub Actions secret names are case-sensitive. Use uppercase with underscores consistently.
Conclusion
Automating static site deployments removes the manual step that causes changes to sit undeployed after merging. The workflow file is compact, the required secrets are straightforward to configure, and the result is a site that updates within minutes of every push.
The setup described here is a foundation. Adding a linting step for Markdown, a link checker, or a scheduled rebuild for content that changes without a repository push all follow the same pattern and can be added to the same workflow file without restructuring what is already there.
Using a different static site generator or hosting platform? Share your setup in the comments.




