How to Deploy a Vue.js App (Vite, Vue Router and Pinia)
A Vue 3 app made with npm create vue@latest deploys to Chajio Cloud with no changes at all. Push it to GitHub and the platform builds it and serves it over HTTPS, with Vue Router links that keep working when a visitor refreshes the page. This guide shows what happens on each deploy, how environment variables reach your app, and the few settings worth knowing.
On this page
Before you start
- A Chajio Cloud App Hosting plan. The Free plan is enough to follow along. Once you order, you get an email with your sign-in details, and the first time you sign in you choose your own password.
- Your code on GitHub, in a repository you own or can access. A public repository you don't own works too.
- A Vue app that builds on your computer with
npm run build, with its lockfile (package-lock.json,yarn.lockorpnpm-lock.yaml) committed to the repository.
Step 1: Get your app ready
There is nothing to change. A package.json that depends on vite and has no start script is recognised as a single-page app: your dependencies install from the lockfile, npm run build runs, and the dist folder is served over HTTPS. The build log reads, shortened:
▸ Serving it as a static site (Vite single-page app)
▸ Build-time variables: VITE_API_URL
▸ Stack: Node.js · Static web server · Vite
- Checking Node.js version
- Detected Node.js version range: ^22.18.0 || >=24.12.0
- Installing node modules
- Running npm ci
- Running scripts
- Running npm run build
✓ built in 1.41s
- Configuring default processes
- Skipping default web process (no start script defined)Three things follow from that. Vue Router’s history mode works with no setup: any path that isn’t a real file gets your index.html, so /about and /about/deep/link work on refresh and when shared. A file that doesn’t exist under /assets/ still returns a real 404, not your home page. And only dist is served, so your source code, package.json and node_modules stay private.
Choose your Node.js version
create-vue writes an engines range into package.json, ^22.18.0 || >=24.12.0, which builds on the newest Node.js release. To build on the version you develop on, narrow it:
{
"engines": { "node": "24.x" }
}With no engines at all, the build uses the current long-term-support release. An .nvmrc file isn’t read.
Type errors stop the build
create-vue’s build script runs vue-tsc before vite build, so a TypeScript error fails the deploy, and your current version keeps serving visitors. That’s by design: the build log shows the error. Run npm run build on your computer before you push to catch it first.
Keep it a single-page app
Two things change how your app is treated. A start script that runs anything other than Vite itself makes the platform run your app as a Node.js server instead of serving dist; one that runs vite, vite dev or vite preview is fine, because those are only for your computer. And a base setting in vite.config.ts, left over from hosting in a subfolder, gives a blank page with 404s for your scripts. Your app is served from the root of its domain, so remove it.
Step 2: Create a project
A project is the home for one app, along with its database and domain. In your dashboard, open Projects and click New project:
- Name
- Lowercase letters, numbers and hyphens, up to 24 characters. It becomes part of your app’s free web address and can’t be changed later, so pick something short, like shop or api.
- Plan
- The plan the project draws its resources from.
- Resources
- CPU, memory and storage for this project, pre-filled with what your plan has left. The defaults suit most apps.
Click Create project. Setting it up takes about a minute, and you land on the deploy guide straight away, which walks you through three short steps: Source, Configure and Deploy. You can pick your repository while the project finishes setting up.
Step 3: Connect your repository
On the Source step, choose where your code comes from:
- Your repository
- Click Connect GitHub and approve access, which lets Chajio Cloud read your code and hear about your pushes. Then choose the repository and the branch to deploy, usually main. From now on, every push to that branch deploys automatically.
- Public repository
- Paste the URL of any public GitHub repository, no GitHub sign-in needed. Since you don’t own it, pushes can’t trigger deploys: deploy by hand, or tick Auto-deploy new releases to follow the project’s published releases.
If your app lives in a subfolder of the repository, as in a monorepo, set Build context to that folder, for example apps/web. Otherwise leave it as ., the repository root. Then click Connect repository and Continue.
Step 4: Configure
The Configure step has two runtime settings, both changeable later without a rebuild, and your environment variables.
- Container port
- Leave it at 3000. The server that serves your app listens on whatever port is set here.
- Health check path
/, the default. Every path answers with your app, so there is nothing to change.
Variables starting with VITE_ reach your code as import.meta.env. Add them here, for example the address of your API:
VITE_API_URL=https://api.example.com<script setup lang="ts">
const apiUrl = import.meta.env.VITE_API_URL ?? 'unset'
</script>
<template>
<p>API: {{ apiUrl }}</p>
</template>They are copied into your JavaScript when your app is built, and the build log lists them under “Build-time variables”. Two consequences: anything in a VITE_ variable is readable by every visitor, so use them for public values like API addresses and publishable keys, never passwords; and a new value needs a redeploy, not just a restart.
Variables live on the project's Environment tab. Add each one as a name and a value, then click Save & restart: your app restarts with the new values in moments, no rebuild needed.
Your build receives the variables too. A Dockerfile gets the ones it declares with an ARG line, and an auto-detected build gets all of them. A variable that went into your current build is marked Build. Change one of those and the dashboard offers to Redeploy, because only a new build picks up the new value.
PORT is set by the platform from the container port, so you can't set it yourself. If you attach a database, its connection variable appears here too, locked, because the platform manages it for you.
Click Save and continue.
Step 5: Deploy
The Deploy step shows a summary of the repository, branch and build context. Click Deploy now and the build log starts streaming: your code is fetched, dependencies are installed and your app is built and started. The deployment moves through Queued, Building and Deploying to Success.
The first build takes a few minutes. Later builds reuse the parts that didn't change, so they are usually quicker. When it finishes, your app is live on a free live.chajio.cloud address with HTTPS already switched on. You'll find the address on the project's Overview.
If the deployment fails, the dashboard names the stage that broke and why, and the build log shows the details. Our troubleshooting guide covers the usual causes.
A first Vue build took about four minutes in our tests, most of it installing packages. Once live, your app is ready in under a second and uses about 11 MB of memory.
Connecting to your API
Deploy your API as an app of its own, using the Node.js guide, the Python guide or another for its stack. Set VITE_API_URL to its address and redeploy the Vue app, then allow the Vue app’s domain in the API’s CORS settings so the browser lets the two talk.
Connect your domain
Your app already works on its free address. To serve it from your own domain, open the project's Domains tab:
- Enter your domain, for example
www.example.com, and click Add domain. - The dashboard shows two DNS records to add where your domain's DNS is managed. A TXT record proves the domain is yours. A CNAME record routes visitors to your app, or an A or ALIAS record for a root domain like
example.com. Copy the values exactly as shown. - Click Verify. Once both records check out, a free HTTPS certificate is issued automatically, usually in under a minute, and your app is live on your domain.
DNS changes can take a while to spread, so if Verify doesn't find a record yet, give it a few minutes; the dashboard also re-checks on its own. The full walkthrough, including root domains and Cloudflare, is in How to connect your domain to your app.
After launch
- Every push deploys. Push to your branch and a new deployment starts on its own. Your current version keeps serving visitors until the new one passes its health check, so updates don't cause downtime. You can also deploy by hand from the Deployments tab.
- Roll back in seconds. If a release misbehaves, open Deployments and click Rollback to this version on an earlier successful deployment. Nothing is rebuilt; the previous version simply comes back.
- Watch it run. The Logs tab streams your app's output live, with secrets hidden. The Overview shows your app's status and address, with Redeploy, Restart and Stop buttons.
Your app runs as two copies
Every plan runs two copies of your app, so a crash or an update never takes it offline. That has one consequence worth designing for: anything an app keeps only in its own memory or on its own disk isn't shared with the other copy, and it's gone after a redeploy.
A Vue app keeps its state in the visitor’s browser, Pinia stores included, so this rarely affects it. It matters for the API behind it: keep anything you need to keep in a database, not in the server’s memory or on its disk.
Common problems
The build fails at vue-tsc --build
A TypeScript error. Run npm run build on your computer, fix what it reports, and push again.
A variable is undefined in the browser
It needs the VITE_ prefix, and a redeploy after you add or change it. Accept the Redeploy prompt on the Environment tab.
A blank page, with 404s for your JavaScript files
A base setting in vite.config.ts points at a subfolder. Remove it and push again.
The app is run as a server instead of being served from dist
Your package.json has a start script that runs something other than vite, vite dev or vite preview, such as npm run preview. Remove it, and your app is served from dist on the next deploy.
For anything else, Deployment Failed? How to Find and Fix the Cause explains how to read a failed deployment. Plans and a summary of everything here are on the Vue.js hosting page.
Learning path
Deploy your first app
- 1
- 2
- 3
- 4
Keep reading
How to Deploy a React App
Take a React app built with Vite or Create React App from your GitHub repository to a live HTTPS address, with environment variables, your own domain and automatic deploys on every push.
How to Deploy a Static Website from GitHub (HTML, CSS and JavaScript)
Put a hand-written HTML site online from GitHub in minutes, over HTTPS and on your own domain. How to lay out your files, link between pages and keep private files out of view.
How to Deploy a Node.js App (Express, Fastify or NestJS)
Deploy a Node.js API or server from GitHub, JavaScript or TypeScript. The two lines of code every app needs, choosing a Node version, and shutting down cleanly for seamless updates.
How to Connect Your Domain to Your App (with Free HTTPS)
Put your app on your own domain in three DNS steps. Covers www versus root domains, what each DNS record does, Cloudflare, and what to check when a domain will not verify.
Host your Vue.js app on Chajio Cloud
Free for your first month, with a managed database and two replicas of your app on every plan.