Vue Builds on GitHub Actions
Cache the package manager store beside the Vite cache directory, run type checks and component tests as separate steps, and size warp- runners for vite build.
Last verified:
To build a Vue application on GitHub Actions, cache the package manager store and the Vite cache directory, run type checking and component tests as their own steps, and put the job on a 4 to 8 vCPU runner label. A default workflow reinstalls every dependency on a fresh machine and then runs a single-threaded Rollup build on the smallest available box, which is where most of the wall time goes.
This page covers the cache configuration for npm, pnpm, and yarn alongside the bundler cache directory, a workflow file that runs component tests and a production build on a warp- label, the sizing call between 4, 8, and 16 vCPU, and the three bottlenecks that dominate Vue pipelines.
Overview
Every GitHub Actions job starts on a fresh virtual machine, so nothing written by the previous run is on disk. For a Vue repository that means three directories start empty: the package manager store that the install reads from, the Vite cache directory that holds pre-bundled dependencies, and the webpack filesystem cache in Vue CLI projects.
The build itself has three distinct phases, and they respond to different levers. vue-tsc type checks the single-file components on one thread. vite build hands the module graph to Rollup, which walks and transforms it on one thread, then minifies the output chunks with esbuild across every core it sees. Component tests written with @vue/test-utils run through Vitest, which spawns a worker pool sized from the reported CPU count.
Vue work belongs on the Linux x64 sizes, and moving an existing job across is a one-line change to runs-on. WarpBuild Cache is available on all WarpBuild runners and is enabled by default, per the caching documentation, so the cache steps below need no extra setup.
One boundary before the configuration. This page covers the build and component test path for a single-package Vue application. A full browser suite that boots the built application and drives it end to end has a different worker model and a different browser install path, and that configuration lives on the Playwright test suites on GitHub Actions page.
Configuration
Three directories are worth carrying between runs, and they are written by different tools:
| Directory | Written by | Key inputs |
|---|---|---|
~/.npm, pnpm store path, or yarn cache dir | the install command | lockfile hash |
node_modules/.vite | Vite dependency pre-bundling, read by Vitest | lockfile plus vite.config.* |
node_modules/.cache | webpack 5 filesystem cache in Vue CLI projects | lockfile plus vue.config.* |
The store is handled by WarpBuilds/setup-node, a drop-in replacement for actions/setup-node whose cache input accepts npm, yarn, or pnpm and routes the entries through WarpBuild Cache. The full input list for that action and its siblings is in the setup actions documentation. The bundler directory needs its own cache step:
name: vue
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: warp-ubuntu-latest-x64-4x
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: WarpBuilds/setup-node@v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Restore Vite cache directory
uses: WarpBuilds/cache@v1
with:
path: node_modules/.vite
key: ${{ runner.os }}-vite-${{ hashFiles('**/pnpm-lock.yaml', 'vite.config.*') }}
restore-keys: |
${{ runner.os }}-vite-
- run: pnpm run type-check
- run: pnpm vitest run --environment jsdom --maxWorkers=4
- run: pnpm vite build
- uses: actions/upload-artifact@v4
with:
name: dist
path: distTwo details in that file decide whether the cache earns its keep. The key hashes the lockfile and the Vite config together, because a config edit changes what gets pre-bundled as surely as a dependency bump does. The restore-keys prefix underneath is what keeps an automated dependency update cheap: when the exact key misses, the action restores the most recent entry with the same prefix and the pre-bundle step only redoes the packages that changed.
vite build runs Rollup and resolves dependencies from node_modules directly, so the restored node_modules/.vite directory pays off in the Vitest step rather than the build step. Keep that in mind when reading timings: a warm bundler cache shortens component tests, and a warm store shortens the install.
Vue CLI projects sit on webpack 5, which writes its filesystem cache under node_modules/.cache. Swap the path and the key inputs, and the rest of the step is identical:
- name: Restore webpack filesystem cache
uses: WarpBuilds/cache@v1
with:
path: node_modules/.cache
key: ${{ runner.os }}-vue-cli-${{ hashFiles('**/pnpm-lock.yaml', 'vue.config.*') }}
restore-keys: |
${{ runner.os }}-vue-cli-Two behaviors from the caching documentation matter before you rely on either entry. The cache is scoped to key, version, and branch, so seed it on main and let branch builds reach it through restore-keys. Entries expire 7 days after last use, so an active repository stays warm while an abandoned branch stops consuming storage on its own.
Cache usage is metered at $0.20 per GB-month of storage and $0.0001 per write or restore operation. A Vue repository with a 1.2GB store plus bundler directory and 3,000 operations a month adds about $0.54 to the bill.
Sizing
WarpBuild Linux x64 runners carry 4GB of memory per core across the range documented under cloud runners. Four sizes cover Vue work:
| Runner label | vCPU | Memory | Storage | Price per minute |
|---|---|---|---|---|
| warp-ubuntu-latest-x64-2x | 2 | 8GB | 150GB SSD | $0.004 |
| warp-ubuntu-latest-x64-4x | 4 | 16GB | 150GB SSD | $0.008 |
| warp-ubuntu-latest-x64-8x | 8 | 32GB | 150GB SSD | $0.016 |
| warp-ubuntu-latest-x64-16x | 16 | 64GB | 150GB SSD | $0.032 |
Size each phase for what it can use. Install is disk and network bound, and extra cores change little past 4 vCPU once the store is warm. Type checking runs vue-tsc on one core, so memory is the constraint that bites first; the 16GB on 4x covers most applications, and a heap failure is the signal to move that job to 8x. Production build splits: the Rollup graph walk holds one thread, and esbuild minification uses the rest, so a large application with many chunks sees a return on 8x while a small one does not. Component tests scale with the worker pool, which is the phase where 8x and 16x pay for themselves on a wide suite.
Set --maxWorkers to the vCPU count of the label rather than leaving it to the default, so the worker count and the machine agree.
Worked cost model
GitHub publishes per-minute list prices in its Actions minute multipliers reference. Checked on 2026-08-13, the 4-core Linux larger runner is $0.012 per minute. Take a Vue application running 1,200 pull-request runs a month at 6 minutes each with a warm store, which is 7,200 runner minutes:
| Scenario | Rate | Monthly minutes | Monthly cost |
|---|---|---|---|
| GitHub-hosted Linux larger runner, 4 vCPU | $0.012/min | 7,200 | $86.40 |
| warp-ubuntu-latest-x64-4x, 4 vCPU | $0.008/min | 7,200 | $57.60 |
| Cache storage and operations (1.2GB, 3,000 ops) | see above | n/a | $0.54 |
Stated as list-price arithmetic: warp-ubuntu-latest-x64-4x (4 vCPU, 16 GB) costs $0.008 per minute against $0.012 per minute for the 4-core Linux larger runner (4 vCPU, 16 GB): 33 percent lower list price. GitHub list price checked on 2026-08-13.
Rates for every size and platform are on the pricing page.
Bottlenecks
Cold installs. Without a restored store, every job pulls the full dependency tree from the registry before the first component test starts, and a Vue application with a component library and a test stack carries a few thousand entries. The fix is the cache configuration above. Verify it from the install log: a warm pnpm install reports most packages as reused from the store instead of fetched.
Single-threaded bundling. Rollup walks and transforms the module graph on one thread, so a 16 vCPU runner does not shorten that phase. Buying cores to fix a slow vite build works only when minification dominates, which shows up as a build that gets faster on 8x and then flattens. The lever that does move the graph walk is graph size: trim barrel files that pull whole libraries into the entry, and check whether a heavy dependency is being bundled when it could be external.
Browser test memory. Component tests that drive a real browser instead of jsdom start a browser context per worker, and each context holds its own renderer process. With the pool sized from the CPU count, a 16 vCPU runner opens enough contexts to exhaust memory before it exhausts cores. Pin --maxWorkers below the core count on browser-backed suites, or keep those specs on 8x where 32GB gives each worker room.
When a job is slow and the cause is unclear, WarpBuild CI observability shows system metrics from the runner agent correlated with GitHub Actions job logs, which separates a CPU-bound minify phase from an install stuck on the network. The Action Debugger pauses a workflow and opens an SSH session on the runner, so you can list node_modules/.vite and confirm the restore landed.
Proof
Every cost and performance number on this page carries a source link and a checked-on date, and the same numbers appear on the pricing page, so the arithmetic is re-runnable rather than asserted.
Public repositories running warp- labels are citable evidence, and reading a runs-on line takes a few seconds. The Trigger.dev TypeScript monorepo runs its end-to-end matrix on warp-ubuntu-latest-x64-4x and warp-windows-latest-x64-8x, which you can read in triggerdotdev/trigger.dev's e2e.yml (checked on 2026-08-13).
If the same repository installs dependencies for other jobs, the store configuration is covered on the Node.js on GitHub Actions page, and the equivalent decisions for a different single-file component toolchain are on the Angular on GitHub Actions page. For splitting a wide suite across labels, see the guide to sizing runners for Node test suites.
FAQ
Should I cache node_modules or the Vite cache directory?
Neither replaces the other. Cache the package manager store so the install reads tarballs from local disk, and cache node_modules/.vite so the component test run reuses the pre-bundled dependencies. Skip node_modules itself, because the install command rebuilds it and a restored copy breaks when the Node version changes.
What runner size does a Vue production build need?
warp-ubuntu-latest-x64-4x covers most single-package Vue applications, since the Rollup module graph walk runs on one thread and only minification spreads across cores. Move the component test job to 8x when the tests drive a real browser, and keep type checking on 4x.
What changes when a Vue workflow moves to WarpBuild?
The runs-on label on each job, and actions/cache becomes WarpBuilds/cache.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.