Skip to main content

Deploy a Vue SPA to CloudBase Hosting

In one sentence: Build your project with Vite/Vue CLI to produce a dist folder, then run tcb 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 to index.html in 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.

DimensionHostingCloudBase Run
Deployment artifactStatic files (HTML/CSS/JS/images)Container image (Dockerfile)
RuntimeNone (CDN serves directly from COS)Node.js / Python / custom runtime process
BillingStorage + trafficInstance uptime + traffic
Best forSPA / docs site / landing pageSSR / 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

DependencyVersion
Node.js≥ 18 (minimum requirement for Vite 5/6)
Vue2.7.x or 3.x
Vite or Vue CLIVite 5+ / Vue CLI 5+
@cloudbase/clilatest (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.

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:

ParameterDescription
--frameworkFramework 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:

CommandPurpose
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 / ECONNRESET during upload, the network middleware is likely terminating the SDK's keep-alive connection. Disable it and retry:
    export COS_SDK_KEEPALIVE=false
    tcb 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:

  1. Go to CloudBase Console → Static Website Hosting
  2. Switch to the "Settings" tab
  3. Find the "Error Page" configuration
  4. Set the 4xx error page to index.html
  5. 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

  1. 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).
  2. SSL certificate: Apply for a free DV certificate in the SSL Certificate Console, or upload an existing certificate.

Configuration Steps

  1. Go to CloudBase Console → HTTP Access Service
  2. Click "Add Domain" and enter your custom domain (e.g. app.example.com)
  3. Upload an SSL certificate or select an existing one
  4. For CDN type, select "CloudBase CDN" (optimized for Hosting, configures CDN acceleration automatically)
  5. After adding, the Console provides a CNAME value — copy it
  6. Go to your DNS provider (Tencent Cloud DNS / Alibaba Cloud / Cloudflare, etc.) and add a CNAME record pointing to that value
  7. 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 TypeRecommended TTL
Images, fonts30 days
CSS / JS (with hash suffix)7 days
index.html1 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:

  1. Open the default domain home page and navigate through all routes (/about/users/123) — everything should work.
  2. On the /about page, press F5 to refresh directly — this must not return 404. This is the key test for confirming the fallback is configured correctly.
  3. Once the custom domain DNS is active, repeat the tests with https://app.example.com.

Common Errors

SymptomCauseFix
tcb hosting deploy ./dist -e xxx reports ENOENT: no such file or directoryThe dist path is wrong, or the current directory is not the project rootcd to the project root and confirm ls dist shows index.html before deploying
Home page loads, but refreshing /about returns 404SPA 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 pathsvite.config.js base does not match the actual cloudPath used during deploymentUse / for root deployment, /my-app/ for sub-path /my-app; rebuild and redeploy after changing
Custom domain shows ERR_CERT_COMMON_NAME_INVALIDThe SSL certificate is bound to a different domain, or the certificate has expiredRe-issue a matching certificate in the SSL Certificate Console and re-bind it
Custom domain DNS still resolves to old IPDNS has not switched to CloudBase's CNAME, or local DNS cache is staleRun 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 403Domain has not completed ICP filing, or the filing has expiredCheck filing status at the ICP filing system; file if not yet registered
{ message: 'socket hang up', code: 'ECONNRESET' } during bulk uploadCOS SDK keep-alive connection terminated by network middlewareRun export COS_SDK_KEEPALIVE=false and retry
Vue Router history mode: sub-route refresh returns 200 but page is blankThe fallback returns index.html, but the asset paths in the HTML include the base prefix, which does not match the createWebHistory baseConfirm 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.

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.