| name | vercel-lfs |
| description | Deploy a site with Git LFS assets (videos, images) to Vercel without serving 15-byte pointer files. Use when "videos not loading on Vercel", "LFS files not served", "Vercel serving pointer files", "15 bytes instead of video", "text/plain for video", or "deploy site with LFS assets". Covers the prebuilt-deploy workflow and required GitHub Actions secrets. |
Vercel + Git LFS: Deployment Guide
Trigger phrases
- "videos not loading on Vercel"
- "LFS files not served"
- "Vercel serving pointer files"
- "15 bytes instead of video"
- "text/plain for video"
- "deploy site with LFS assets"
How Vercel handles LFS
Vercel has two deployment modes, and they behave differently with LFS:
1. Git-source API (gitSource in REST API)
Vercel clones the repo from GitHub but does NOT fetch LFS objects.
LFS pointer files (~15 bytes) are deployed as-is.
BUT: Vercel detects pointer files and serves a 307 redirect to media.githubusercontent.com:
HTTP/2 307
location: https://media.githubusercontent.com/media/<owner>/<repo>/main/public/file.webm
- Private repo →
media.githubusercontent.com returns 404 → videos broken
- Public repo →
media.githubusercontent.com returns 200 + actual content → videos work
2. File-upload (vercel deploy --prebuilt)
Vercel receives actual file bytes. LFS files must be fetched during CI before build.
Requires actions/checkout@v4 with lfs: true.
Decision tree
Are LFS assets large (videos, binaries)?
│
├── YES: Is the repo public?
│ ├── YES → Use git-source API deployment. LFS redirect to GitHub CDN works automatically.
│ └── NO → Either:
│ (a) Make repo public (simplest — CDN redirects start working)
│ (b) Use file-upload deploy with LFS checkout in CI (see workflow below)
│
└── NO (just a few small files) → Commit directly, avoid LFS
Option A: Make repo public (simplest fix)
curl -s -X PATCH \
-H "Authorization: token $GH_TOKEN" \
"https://api.github.com/repos/<owner>/<repo>" \
-d '{"private": false}'
Then re-verify:
curl -sI "https://www.yoursite.com/video.webm"
Option B: File-upload deploy with LFS in CI
Use this when repo must stay private.
jobs:
deploy-production:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
lfs: true
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Build
run: npx vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
- name: Deploy
run: npx vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
Watch out: Vercel Hobby plan has a 5000 files/deployment limit. If build output exceeds this, upgrade to Pro or reduce file count.
Diagnosing LFS problems
git lfs ls-files
curl -sI "https://www.yoursite.com/video.webm"
LOCATION=$(curl -sI "https://www.yoursite.com/video.webm" | grep -i location | awk '{print $2}' | tr -d '\r')
curl -sI "$LOCATION" | grep -E "HTTP|content-type|content-length"
GitHub Actions budget exhaustion (private repos)
Symptom: jobs fail immediately with 0 steps, runner_id: 0.
Check via API:
curl -s -H "Authorization: token $GH_TOKEN" \
"https://api.github.com/repos/<owner>/<repo>/check-runs/<check_run_id>/annotations" \
| python3 -c "import sys,json; [print(a['message']) for a in json.load(sys.stdin)]"
Fix: make repo public (Actions are unlimited for public repos), or add billing.
Required GitHub secrets
For Vercel deployments via CI:
| Secret | Where to find |
|---|
VERCEL_TOKEN | Vercel dashboard → Settings → Tokens |
VERCEL_ORG_ID | .vercel/project.json → orgId |
VERCEL_PROJECT_ID | .vercel/project.json → projectId |
Set via browser at: https://github.com/<owner>/<repo>/settings/secrets/actions