Skip to main content
Vue is a progressive JavaScript framework for building user interfaces. This guide shows you how to build a Vue 3 application with Mizu as the backend.

Why Vue?

Vue 3 is known for its gentle learning curve, excellent documentation, and progressive framework approach. You can adopt it incrementally - from a simple script tag to a full-featured SPA. The Composition API brings powerful reactivity and composability, while maintaining the simplicity of the Options API for smaller projects. Key strengths:
  • Progressive framework: Start small, scale as needed
  • Excellent documentation: Clear, comprehensive, well-organized
  • Gentle learning curve: Intuitive API, easy to get started
  • Performance: Fast virtual DOM with compiler optimizations
  • Single File Components: Template, script, and styles in one file
  • Flexible: Works with any backend, easy to integrate
  • TypeScript support: First-class TypeScript integration

Vue vs Other Frameworks

Vue + Mizu vs Standalone Vue

Quick Start

Create a new Vue project with the CLI:
Visit http://localhost:3000 to see your app!

Architecture

Development Mode

Production Mode

Project Structure

Backend Setup

The backend serves your Vue app and provides API endpoints. See app/server/app.go:
How it works:
  • //go:embed all:../../dist embeds the built Vue app into the Go binary
  • frontend.ModeAuto automatically switches between dev (proxy) and production (embedded FS)
  • DevServer proxies to Vite during development
  • IgnorePaths: []string{"/api"} ensures API routes aren’t proxied

API Routes Example

Frontend Setup

frontend/src/main.ts

frontend/src/App.vue

frontend/src/router/index.ts

frontend/src/components/Layout.vue

frontend/src/pages/Home.vue

Composition API vs Options API

Vue 3 supports both the Composition API (recommended) and the Options API (Vue 2 style). The Composition API provides better TypeScript support, code organization, and reusability.

Options API

Characteristics:
  • Properties organized by options (data, computed, methods)
  • Uses this to access properties
  • Familiar to Vue 2 developers
  • Works well for simple components

Composition API

Characteristics:
  • Logic organized by feature (not by type)
  • No this keyword
  • Better TypeScript inference
  • Code is more reusable (extractable to composables)
  • <script setup> reduces boilerplate

Vue Reactivity System

Vue 3’s reactivity system is built on ES6 Proxies, providing fine-grained reactive tracking.

ref() - Reactive Primitives

Use ref() for primitives (strings, numbers, booleans). Access/modify with .value in JavaScript, but not in templates.

reactive() - Reactive Objects

Use reactive() for objects and arrays. No .value needed.
When to use ref() vs reactive():
  • Use ref() for primitives and when you need to reassign the entire value
  • Use reactive() for objects that you’ll mutate properties of
  • ref() can hold any value type, reactive() only works with objects

computed() - Derived State

Computed values are cached and only re-compute when dependencies change.

watch() - Side Effects

Watch reactive sources and perform side effects when they change.

watchEffect() - Automatic Dependency Tracking

Runs immediately and automatically tracks dependencies.

Lifecycle Hooks

Vue 3 Composition API lifecycle hooks:
Lifecycle order:
  1. setup() / <script setup> - Component created
  2. onBeforeMount() - Before inserting into DOM
  3. onMounted() - After inserting into DOM
  4. onBeforeUpdate() - Before reactive data change causes re-render
  5. onUpdated() - After re-render
  6. onBeforeUnmount() - Before removing from DOM
  7. onUnmounted() - After removing from DOM

Vue Router

Basic Routing

We’ve already seen basic routing. Let’s expand with more patterns.

Dynamic Routes

Nested Routes

Programmatic Navigation

Route Guards

Vite Configuration

frontend/vite.config.ts

Update tsconfig.json to match aliases:

Composables (Custom Hooks)

Composables are reusable pieces of stateful logic. They follow the naming convention use*.

Data Fetching Composable

Debounce Composable

LocalStorage Composable

State Management with Pinia

Pinia is the official state management library for Vue 3, replacing Vuex.

Setup

Creating a Store

Options API Style Store

Using Stores in Components

Store Composition

Stores can use other stores:

Component Patterns

Props and Emits with TypeScript

Slots

Scoped Slots

v-model

Multiple v-models

Directives

v-model Advanced

Custom Directives

More complex directive:

Advanced Features

Teleport

Render content outside the component’s DOM hierarchy (useful for modals, notifications).

Suspense

Handle async components with loading states.

KeepAlive

Cache component instances to preserve state when switching routes/components.

Transitions

List transitions:

Form Handling

Basic Forms

Form Validation with VeeValidate

Styling Options

Scoped Styles

Vue’s scoped styles are built-in:

CSS Modules

Tailwind CSS

Install Tailwind:
Configure tailwind.config.js:
Add to src/styles/index.css:
Use in components:

Performance Optimization

v-once and v-memo

Async Components

Virtual Scrolling

Error Handling

Error Boundary (Composition)

Global Error Handler

Complete Real-World Example: Task Manager

Let’s build a complete task management application using Vue, Pinia, Vue Router, and Mizu backend.

Backend (Go)

Frontend - Store

Frontend - Components

Building for Production

This runs:
  1. cd frontend && npm run build - Builds Vue app to dist/
  2. go build -o bin/server cmd/server/main.go - Builds Go binary with embedded frontend
Run in production:

Troubleshooting

HMR Not Working

Problem: Changes in Vue files don’t hot-reload. Solution:
  • Check Vite dev server is running on port 5173
  • Verify vite.config.ts has correct HMR config:
  • Check browser console for WebSocket errors

Router 404 in Production

Problem: Refreshing on /about gives 404 in production. Solution: The frontend middleware handles this automatically with SPA fallback. Verify frontend.Options:

State Not Persisting

Problem: Pinia state resets on page refresh. Solution: Use pinia-plugin-persistedstate:

TypeScript Errors with Refs

Problem: TypeScript complains about .value. Solution: Ensure you’re using .value in <script> but NOT in <template>:

Props Not Reactive

Problem: Mutating props doesn’t update child component. Solution: Props are one-way. Use emit to notify parent, or use v-model:

When to Choose Vue

Choose Vue if:
  • You want a gentle learning curve with great documentation
  • You prefer Single File Components (.vue files)
  • You need a progressive framework (start small, scale up)
  • You want official state management (Pinia) and routing (Vue Router)
  • You value developer experience and tooling (Vite, DevTools)
  • You’re building apps of any size (Vue scales from simple to complex)
Consider alternatives if:
  • React: You need the largest ecosystem or you’re integrating with existing React code
  • Svelte: You want the smallest bundle size and compile-time optimizations
  • Angular: You need a full framework with everything included (batteries-included approach)
  • HTMX/Alpine: You want to enhance server-rendered HTML with minimal JavaScript

Next Steps

Nuxt Guide

Try Nuxt for SSR and static generation

Pinia Docs

Learn more about Vue state management

Vue Router Docs

Deep dive into Vue Router

Deployment

Build and deploy your app