CI/CD for Remix with GitHub Actions
Typecheck, test, build, and deploy your server-rendered Remix app.
Remix builds both a server bundle and a client bundle, and modern Remix uses Vite under the hood. This recipe validates the app, builds it, and deploys the Node server output to your host of choice.
What the pipeline does
- install deps with npm ci
- typecheck with tsc
- lint with eslint
- run tests
- build with remix vite:build
- deploy the build output
The workflow
A Vite-based Remix build emits build/server and build/client. Deploy the server bundle to a Node runtime and serve the client assets from a CDN.
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx tsc --noEmit
- run: npm run lint --if-present
- run: npm test --if-present
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: remix-build
path: build
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Deploy build/ to your Node host, Fly.io, or platform adapter"Caching and speed
cache: npm covers installs, and Vite caches its dependency pre-bundle under node_modules/.vite. Remix builds two bundles, so build time scales with route count; running it on cheaper managed runners such as Latchkey keeps that step fast on every PR.
Deploying
Remix needs a Node runtime for the server bundle. Common targets are Fly.io, a Docker container on ECS or Cloud Run, Vercel, or Cloudflare via the appropriate preset. Serve build/client static assets from a CDN like CloudFront for cache hits, and point the server at them.
Key takeaways
- Remix produces separate server and client bundles.
- Deploy the server bundle to a Node runtime; CDN the client assets.
- Run tsc --noEmit to catch loader and action type errors.