Vue 3 apps are typically built with Vite, which generates a dist/ directory of static HTML, CSS, and JavaScript. Getting that directory from your local machine to a production CDN is the deployment gap—some platforms require Docker, others need complex build configs, and most charge per build minute or per active site. PandaStack auto-detects the Vite build command, uploads the output to a global CDN, and gives you a live URL in under 90 seconds.
This post walks through deploying a Vue 3 SPA from a Git repo to a production URL, using all four deploy paths: the REST API, pandastack.json, the deploy button, and the panda CLI.
Starting with a fresh Vue 3 app
Create a new Vue 3 project with Vite:
npm create vue@latest
cd my-vue-app
npm installRun the dev server:
npm run devBuild the production bundle:
npm run buildVite generates a dist/ directory containing the static site. This is what you deploy to the CDN.
The simplest deploy: REST API
Push the repo to GitHub, then create a project via the PandaStack API:
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer psk_live_your_token" \
-H "Content-Type: application/json" \
-d '{
"slug": "static",
"name": "vue3-app",
"repositoryName": "yourusername/my-vue-app",
"branch": "main",
"autoDeploy": true
}'PandaStack clones the repo, detects package.json with "build": "vite build", runs npm install && npm run build, uploads dist/ to the CDN, and returns a live URL in the response:
{
"success": true,
"data": {
"projectId": 42,
"deploymentId": 123,
"url": "https://vue3-app-xyz.pandastack.app"
}
}Visit the URL, and your Vue app is live. No config file needed—the builder inferred the build command and output directory from package.json and Vite's defaults.
Adding environment variables
If your Vue app reads VITE_API_URL at build time, pass it via the env array:
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer psk_live_your_token" \
-H "Content-Type: application/json" \
-d '{
"slug": "static",
"name": "vue3-app",
"repositoryName": "yourusername/my-vue-app",
"branch": "main",
"autoDeploy": true,
"env": [
{ "name": "VITE_API_URL", "value": "https://api.example.com" }
]
}'The variable is injected during the build, and Vite inlines it into the bundled JavaScript. Your app reads it:
const apiUrl = import.meta.env.VITE_API_URL;Critical: Vue/Vite only exposes vars prefixed with VITE_. If your var is named API_URL (no prefix), it won't be available in the browser.
Using pandastack.json for deploy-time prompting
To prompt users for environment variables when they deploy, add a pandastack.json file:
{
"type": "static",
"buildCommand": "npm run build",
"outputDir": "dist",
"env": [
{
"key": "VITE_API_URL",
"description": "Backend API endpoint",
"value": "https://api.example.com"
}
]
}Now when someone deploys via the deploy button or dashboard, the deploy page shows an input field for VITE_API_URL pre-filled with the default. They can override it if needed, and the value is injected during the build.
This is the Vercel-style workflow and is better than hardcoding the API URL in the code or requiring users to manually configure env vars after deploying.
The deploy button approach
Add a deploy button to your README:
[](https://dashboard.pandastack.io/deploy?repo=yourusername/my-vue-app)When someone clicks it, PandaStack reads pandastack.json from the repo, pre-fills the deploy form, and prompts for any required env vars. The user confirms, and the deploy starts.
If you don't have a pandastack.json, pass settings via query params:
[](https://dashboard.pandastack.io/deploy?repo=yourusername/my-vue-app&type=static&buildCmd=npm%20run%20build&outputDir=dist)The pandastack.json approach is cleaner because it keeps the config in version control.
The CLI workflow
If you prefer terminal commands over web UIs, use the panda CLI:
panda login
panda projects create \
--name vue3-app \
--repo yourusername/my-vue-app \
--branch main \
--type static \
--build-cmd "npm run build" \
--output-dir distThe CLI triggers the same build pipeline as the API. The first deploy takes ~90 seconds (cold cache), and subsequent deploys take ~30 seconds (warm cache, dependencies cached).
To view logs:
panda projects logs 42Note: CLI log streaming is currently unreliable. For real-time logs, use the dashboard Logs tab instead.
Redeploying after changes
Every push to main triggers a deploy if you set "autoDeploy": true in the API payload or checked "Auto-deploy on push" in the dashboard. If auto-deploy is off, trigger a manual redeploy:
curl -X POST https://api.pandastack.io/v1/projects/42/deploy \
-H "Authorization: Bearer psk_live_your_token"Or via the CLI:
panda projects deploy 42The build runs, the new dist/ is uploaded, and the CDN cache is purged automatically. The live site updates within seconds.
Custom domains and SSL
Once deployed, add a custom domain via the dashboard or API:
curl -X POST https://api.pandastack.io/v1/projects/42/domains \
-H "Authorization: Bearer psk_live_your_token" \
-H "Content-Type: application/json" \
-d '{ "domain": "example.com" }'PandaStack provisions an SSL certificate via Let's Encrypt automatically. Point your DNS A record to the IP shown in the dashboard, and the site is live at https://example.com within a few minutes.
Wildcard certificates are not supported on the free tier—you need to add each subdomain individually or upgrade to a paid plan.
How the CDN works
PandaStack's static hosting serves files from a global CDN (Cloudflare-backed). HTML files are edge-cached with a short TTL (5 minutes), and hashed assets (main.abc123.js) are served immutable with a 1-year TTL. When you redeploy, the cache is purged automatically, so users see the new version immediately.
For Vue SPAs with client-side routing, the CDN serves index.html for all routes. For example, /about serves index.html, and Vue Router handles the routing client-side. If you get a 404 on /about, check that the output directory is correct (dist, not build).
Why this matters
Netlify and Vercel pioneered the "git push to CDN" workflow for SPAs, but they bundle it with expensive bandwidth (Netlify charges after 100 GB) and vendor lock-in (Vercel's edge functions are proprietary). PandaStack's static hosting is a simpler model: you get a CDN, auto-deploys, and SSL, without the platform-specific features or high egress costs.
The free tier includes unlimited static sites and 100 GB bandwidth per month. Paid plans start at $15/mo for 500 GB bandwidth and 1000 build minutes, which covers most small-to-medium projects.
Common issues
Build fails with "command not found: vite": The builder runs npm install before npm run build, so vite should be in devDependencies. If you're using Yarn or pnpm, ensure the lock file is committed.
404 on all routes except index: Verify the output directory is dist, not build. If Vite is configured with a custom output directory, update pandastack.json to match.
Environment variable undefined: Check that the var name starts with VITE_. Non-prefixed vars are not exposed to the browser.
Site shows old content after redeploy: The CDN cache is purged automatically, but browser cache may persist. Hard-reload (Ctrl+Shift+R) to bypass it. If the issue persists, check that the deploy actually completed (Logs tab).
Full example repo
package.json:
{
"name": "my-vue-app",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"vue": "^3.4.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"vite": "^5.0.0"
}
}pandastack.json:
{
"type": "static",
"buildCommand": "npm run build",
"outputDir": "dist",
"env": [
{
"key": "VITE_API_URL",
"description": "Backend API endpoint",
"value": "https://api.example.com"
}
]
}README.md:
# My Vue 3 App
[](https://dashboard.pandastack.io/deploy?repo=yourusername/my-vue-app)
Click the button to deploy this Vue 3 SPA to PandaStack in under 2 minutes.Push this to GitHub, click the deploy button, and the app is live.
References
- [Vue 3 Documentation](https://vuejs.org/)
- [Vite Documentation](https://vitejs.dev/)
- [Vite Environment Variables](https://vitejs.dev/guide/env-and-mode.html)
- [PandaStack Static Hosting](https://docs.pandastack.io/projects/static)