Deploy a Vue SPA to CloudBase Hosting
In one sentence: Build your project with Vite/Vue CLI to produce a
distfolder, then runtcb hosting deploy dist -e <env-id>to upload it to CloudBase static website Hosting in one shot — you get CDN acceleration, HTTPS, and custom domain support automatically; when using Vue Router history mode, set the error page toindex.htmlin the Console to resolve refresh 404s.Estimated time: 15 minutes | Difficulty: Beginner
Applicable Scenarios
This recipe covers the most common Vue frontend deployment scenario: a pure SPA with no SSR and no server-side logic, where the build output is a set of static files. CloudBase Hosting is backed by COS + CDN and billed by storage and traffic — no container instance fees.
- Applicable: Vue 2 / Vue 3 + Vite or Vue CLI SPA
- Applicable: China-hosted deployment that requires ICP filing + HTTPS
- Applicable: cost-sensitive projects that do not need a server-side process
- Not applicable: SSR / SSG / Nuxt server-side rendering — use CloudBase Run instead, see deploy-nextjs-to-cloudbase-run
- Not applicable: projects that need a BFF or backend API — use CloudBase Run or Cloud Functions
Hosting vs. CloudBase Run
One-line distinction: Hosting = static files + CDN distribution; CloudBase Run = containerized server-side process.
| Dimension | Hosting | CloudBase Run |
|---|---|---|
| Deployment artifact | Static files (HTML/CSS/JS/images) | Container image (Dockerfile) |
| Runtime | None (CDN serves directly from COS) | Node.js / Python / custom runtime process |
| Billing | Storage + traffic | Instance uptime + traffic |
| Best for | SPA / docs site / landing page | SSR / API / WebSocket / long connections |
A Vue SPA produces exactly two kinds of artifacts — one HTML file + one JS bundle + other static assets — all of which are cheapest to serve via Hosting.
Prerequisites
| Dependency | Version |
|---|---|
| Node.js | ≥ 18 (minimum requirement for Vite 5/6) |
| Vue | 2.7.x or 3.x |
| Vite or Vue CLI | Vite 5+ / Vue CLI 5+ |
@cloudbase/cli | latest (v3.x at time of writing) |
| A CloudBase environment with Hosting enabled | — |
Install and log in to the CLI globally:
npm i -g @cloudbase/cli
tcb login
Step 1: Configure the Vue Project
Before deploying a SPA to Hosting, two settings must be confirmed, otherwise you will hit classic problems like refresh 404s and asset 404s in Step 4.
1.1 Vite base Configuration
vite.config.js (or vite.config.ts):
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
base: '/', // use '/' when deploying to the root of your domain
// base: '/my-app/', // use '/my-app/' if deploying to a sub-path (e.g. default domain /my-app/)
build: {
outDir: 'dist',
},
});
base controls the prefix added to asset references in the built HTML. If you deploy to the root of the default domain (https://<env-id>.tcloudbaseapp.com/), use /. If you deploy to a sub-path with tcb hosting deploy dist /my-app -e <env-id>, change it to /my-app/ — both leading and trailing slashes are required.
For Vue CLI projects, the equivalent field in vue.config.js is publicPath, with the same meaning.
1.2 Vue Router History Mode
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory('/'), // must match the base set in vite.config.js
routes: [
{ path: '/', component: () => import('./views/Home.vue') },
{ path: '/about', component: () => import('./views/About.vue') },
],
});
export default router;
The argument passed to createWebHistory() is the router base — it must exactly match the base in vite.config.js. A mismatch produces the confusing symptom where the home page loads but navigating to /about results in every asset returning 404.
If you want to avoid the complexity of history mode entirely, you can use createWebHashHistory() instead. URLs will look like /#/about, and no server-side fallback configuration is needed. However, hash-based URLs are not SEO-friendly and are generally not recommended for production.
Step 2: Build dist
npm install
npm run build
After the build completes, a dist/ folder appears in the project root:
dist/
├── index.html
├── assets/
│ ├── index-a1b2c3d4.js
│ ├── index-e5f6g7h8.css
│ └── ...(various chunks and static assets)
└── favicon.ico
Open dist/index.html and verify: all <script> and <link> src/href values should begin with your configured base. If base is /my-app/, the HTML should reference /my-app/assets/index-xxxxx.js, not /assets/index-xxxxx.js. If the paths are wrong, fix vite.config.js and rebuild.
Step 3: Deploy to CloudBase Hosting
The CloudBase CLI offers two deployment paths depending on your workflow.
Path A: tcb app deploy (recommended — automated build + upload)
tcb app deploy runs the full pipeline — install dependencies → build → upload artifacts → configure routing — in a single command, with the build happening in the cloud:
tcb app deploy --framework vite -e <env-id>
The CLI reads name from package.json as the application name and infers the build command (npm run build) and output directory (./dist) from --framework vite. On the first run it prompts you to confirm these parameters interactively, then writes them back to cloudbaserc.json. Subsequent deployments only need tcb app deploy.
Common parameters:
| Parameter | Description |
|---|---|
--framework | Framework type: vite / vue / react / next / nuxt / angular / static |
-e, --env-id <envId> | Target environment ID |
--build-command <cmd> | Custom build command, overrides the framework default |
--output-dir <dir> | Build output directory, defaults to ./dist |
--deploy-path <path> | Hosting mount path, defaults to /service-name |
Full parameter reference: tcb app deploy command docs.
Path B: tcb hosting deploy (upload an existing dist directly)
If you want to keep the build local (e.g. CI has already run npm run build), you can upload dist/ directly:
# In the project root, after building locally
npm run build
# Deploy the entire dist directory to the cloud root
tcb hosting deploy dist -e <env-id>
# Or deploy to a sub-path
tcb hosting deploy dist /my-app -e <env-id>
tcb hosting deploy <localPath> [cloudPath] is a file-level tool: it does not run install or build, only upload. Full command reference:
| Command | Purpose |
|---|---|
tcb hosting deploy <localPath> [cloudPath] -e <env-id> | Upload a file or folder to the specified path |
tcb hosting list -e <env-id> | List files on the cloud |
tcb hosting detail -e <env-id> | View service info (default domain / status) |
tcb hosting delete <cloudPath> -e <env-id> | Delete a specific file; add --dir to delete a folder, --force to skip confirmation, --dry-run to preview |
tcb hosting delete -e <env-id> | Clear all files in Hosting |
Upload Limits
- Maximum file size: 50 TB; no limit on the number of files.
- If you see
socket hang up/ECONNRESETduring upload, the network middleware is likely terminating the SDK's keep-alive connection. Disable it and retry:export COS_SDK_KEEPALIVE=falsetcb hosting deploy dist -e <env-id>
Full command documentation: CLI Hosting Commands.
Step 4: Configure the SPA Fallback
This step is required for any Vue Router history mode deployment on Hosting. Skipping it produces this symptom: the home page loads, but any sub-route (/about, /users/123) returns 404 on direct access or refresh.
Why: history mode uses pushState to change the URL, but physically there is only one file on the cloud — index.html. When the CDN receives a request for /about, it cannot find /about.html and returns 404 by default.
Fix: configure the 4xx error page to serve index.html, which hands routing back to the frontend.
Steps:
- Go to CloudBase Console → Static Website Hosting
- Switch to the "Settings" tab
- Find the "Error Page" configuration
- Set the 4xx error page to
index.html - Save
After this change, accessing <default-domain>/about returns index.html with a 200 status code (note: 200, not a 404 with a modified response body). Vue Router then parses the URL and renders the correct route.
See the "Redirect Rules → Error Code Redirect" section in the Hosting Console Management documentation.
Step 5: Custom Domain + HTTPS
The default domain <env-id>.xxx.tcloudbaseapp.com works, but it has access rate limits. Production deployments should always use a custom domain.
Prerequisites
- ICP filing: Domains used for China-hosted access must complete ICP filing on Tencent Cloud first (individual filing takes roughly 7–20 days; enterprise is similar).
- SSL certificate: Apply for a free DV certificate in the SSL Certificate Console, or upload an existing certificate.
Configuration Steps
- Go to CloudBase Console → HTTP Access Service
- Click "Add Domain" and enter your custom domain (e.g.
app.example.com) - Upload an SSL certificate or select an existing one
- For CDN type, select "CloudBase CDN" (optimized for Hosting, configures CDN acceleration automatically)
- After adding, the Console provides a CNAME value — copy it
- Go to your DNS provider (Tencent Cloud DNS / Alibaba Cloud / Cloudflare, etc.) and add a CNAME record pointing to that value
- Wait 3–5 minutes for DNS to propagate globally; the Console status changes to "Active" once complete
The full process including a comparison of the three CDN types is covered in Custom Domains.
Cache Configuration (Optional)
Under Console "Settings → Cache Configuration", it is recommended to configure cache TTLs by resource type:
| Resource Type | Recommended TTL |
|---|---|
| Images, fonts | 30 days |
| CSS / JS (with hash suffix) | 7 days |
index.html | 1 hour |
index.html is intentionally set short — otherwise, after a new deployment, users whose browsers have cached the old HTML will still reference old JS bundle filenames and not see the update.
Verification
# 1. Home page via default domain (should return 200 + HTML)
curl -I https://<env-id>.xxx.tcloudbaseapp.com/
# 2. Sub-route direct access (with fallback configured, should return 200, not 404)
curl -I https://<env-id>.xxx.tcloudbaseapp.com/about
# 3. Static asset (confirms base is configured correctly)
curl -I https://<env-id>.xxx.tcloudbaseapp.com/assets/index-xxxxx.js
Also verify in the browser:
- Open the default domain home page and navigate through all routes (
/about→/users/123) — everything should work. - On the
/aboutpage, press F5 to refresh directly — this must not return 404. This is the key test for confirming the fallback is configured correctly. - Once the custom domain DNS is active, repeat the tests with
https://app.example.com.
Common Errors
| Symptom | Cause | Fix |
|---|---|---|
tcb hosting deploy ./dist -e xxx reports ENOENT: no such file or directory | The dist path is wrong, or the current directory is not the project root | cd to the project root and confirm ls dist shows index.html before deploying |
Home page loads, but refreshing /about returns 404 | SPA fallback not configured (Step 4) | In Console "Settings → Error Page", set 4xx to index.html |
| Home page opens but all CSS/JS return 404; Network tab shows wrong asset paths | vite.config.js base does not match the actual cloudPath used during deployment | Use / for root deployment, /my-app/ for sub-path /my-app; rebuild and redeploy after changing |
Custom domain shows ERR_CERT_COMMON_NAME_INVALID | The SSL certificate is bound to a different domain, or the certificate has expired | Re-issue a matching certificate in the SSL Certificate Console and re-bind it |
| Custom domain DNS still resolves to old IP | DNS has not switched to CloudBase's CNAME, or local DNS cache is stale | Run nslookup app.example.com to confirm the CNAME points to Tencent Cloud's CDN domain; flush local cache on macOS with dscacheutil -flushcache |
| Custom domain returns 403 | Domain has not completed ICP filing, or the filing has expired | Check filing status at the ICP filing system; file if not yet registered |
{ message: 'socket hang up', code: 'ECONNRESET' } during bulk upload | COS SDK keep-alive connection terminated by network middleware | Run export COS_SDK_KEEPALIVE=false and retry |
| Vue Router history mode: sub-route refresh returns 200 but page is blank | The fallback returns index.html, but the asset paths in the HTML include the base prefix, which does not match the createWebHistory base | Confirm vite.config.js base and the createWebHistory() argument are exactly the same; rebuild and redeploy |
Deployment error codes are listed in full at Error Code Reference.
Related Documentation
- Hosting Quick Start — Console and CLI upload paths
- Hosting Console Management — Error page, cache, hotlink protection, and other settings
- CLI Hosting Commands — Full
tcb hostingcommand reference - CLI App Deploy Commands — Full
tcb app deployparameters andcloudbaserc.jsonconfiguration - Custom Domains — Three CDN types + full domain ownership verification flow
- Deploy Next.js to CloudBase Run — The alternative path when SSR is required
Next Steps
Once your Vue SPA is live, the natural next step is connecting it to a backend. Put your LLM proxy in a Cloud Function to avoid exposing API keys in the browser — see connect-openai-api-cloud-function; add CloudBase authentication to your Vue site with add-auth-web-with-cloudbase-sdk, reusing the same environment; for a multi-environment (dev / staging / prod) secrets layering strategy that works with tcb hosting deploy in CI, see secure-secrets-in-cloud-function.