Angular is a web framework for building scalable applications, with a powerful CLI, signals-based reactivity, and a batteries-included approach to routing, forms, and testing.
Void is a Vite plugin and deployment platform built on Cloudflare. It deploys your app to Cloudflare's global edge network with a single command — no Cloudflare account required.
This guide shows how to create a new Angular application or add to an existing one and deploy it to Void as a single-page app.
Void is still in private beta currently, but will open up for public beta in the future. Visit https://void.cloud for more info on availability.
Creating a new Angular application
To create a new project, generate it with the Angular CLI:
npx @angular/cli new angular-void
The CLI asks a few setup questions. When prompted for Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering), answer No — Void deploys the build as a static SPA, so a client-only app keeps the setup simple.
Once the project is generated, move into it:
cd angular-void
For an existing project, skip this step and go straight to installing Void.
Installing Void
Install the Void CLI as a dev dependency using your package manager of choice:
npm install -D void
Angular doesn't expose Vite directly — the Angular CLI owns its own build pipeline — so there's no vite.config.ts to add voidPlugin() to. Instead, you point Void at the Angular build through a small config file, and keep developing with the Angular CLI as usual:
npm start
Configuring the build for Void
Void deploys the output of your build command. Add a void.json at the project root telling Void this is a SPA, how to build it, and where the build output lands:
{
"$schema": "./node_modules/void/schema.json",
"output": "static",
"inference": {
"appType": "spa",
"build": "npm run build",
"outputDir": "dist/angular-void/browser"
},
"worker": {
"compatibility_date": "2026-05-11"
}
}
The $schema field gives you autocomplete and validation for void.json in your editor. The outputDir matches the Angular CLI's default output path — dist/<project-name>/browser. With appType set to spa, Void falls back all non-file paths to index.html, so the Angular router works out of the box.
If you created the project with SSR enabled, open angular.json and set the build target's outputMode to static, then remove the ssr entry and server option. Void serves the prerendered output as a static SPA:
"outputMode": "static"
Deploying
Log in to Void first:
npx void auth login
Then deploy:
npx void deploy
Void runs your build command, uploads the static assets, and makes the site live:
┌ void deploy
│
◇ Building...
│ (ng build output)
│
◇ Checking assets...
◇ Uploading assets...
◇ Packaging...
◇ Deploying...
◇ Deployed!
│
│ ╭─────────────────────────────────────────╮
│ │ https://<project-name>.void.app │
│ ╰─────────────────────────────────────────╯
│
└ Done!
On the first deploy, Void creates or links the project and saves the link to .void/project.json, so subsequent void deploy runs go straight to the same project. Run void project status to see recent deployments, and void project rollback to instantly switch traffic back to a previous one.
Shaping requests at the edge
Once the app is live, you can control how requests are served — without writing any worker code — by adding a routing block to void.json. These rules run on Cloudflare's edge before the response is served, so they add no latency.
Send old URLs to new ones with routing.redirects:
{
"routing": {
"redirects": {
"/home": "/",
"/blog/*": { "to": "/posts/:splat", "status": 301 }
}
}
}
Keys are source patterns where * matches any characters. A value is either a plain string (a 302 by default) or an object with an explicit status. :splat in the destination carries over whatever * matched.
Attach response headers — security policies, cache rules — by URL pattern with routing.headers:
{
"routing": {
"headers": {
"/*": [
"X-Frame-Options: DENY",
"X-Content-Type-Options: nosniff",
"Referrer-Policy: strict-origin-when-cross-origin"
]
}
}
}
Deploying from CI
To deploy on every push instead of from your machine, let Void scaffold a GitHub Actions workflow for you:
npx void init --github
This creates .github/workflows/deploy.yml with the right package manager commands to build and run void deploy on every push to main.
CI needs a deploy token instead of an interactive login. Run void auth token to copy one to your clipboard, then add it as a repository secret named VOID_TOKEN. Set VOID_PROJECT to your project slug so the CLI knows which project to target.
If you'd rather write the workflow by hand, a minimal version looks like this:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm ci
- run: npx void deploy
env:
VOID_TOKEN: ${{ secrets.VOID_TOKEN }}
VOID_PROJECT: angular-void
VOID_TOKEN skips the interactive login, and VOID_PROJECT tells the CLI which project to target.
Adding a custom domain
Point your own domain at the project with void domain add:
npx void domain add example.com
The CLI prints a CNAME target to add at your DNS provider. Check verification and SSL status anytime with void domain status example.com, or list every domain on the project with void domain list.
Conclusion
With void.json in place, deploying an Angular app to Cloudflare's global edge is a single void deploy away — no Cloudflare account and no Wrangler config required. Void runs the Angular CLI build untouched and serves the output as a SPA, while routing rules, custom domains, and a small CI workflow are there when you need them.
If you enjoyed this post, click the ❤️ so other people will see it. Follow Brandon Roberts on Bluesky, and subscribe to my YouTube Channel for more content!
Top comments (0)