Skip to main content
Mizu provides seamless integration with popular frontend frameworks. Serve your SPA from the same Go binary, with hot reload in development and embedded assets in production.

What is Frontend Integration?

Frontend integration lets you:
  • Develop: Proxy to Vite/webpack dev server with hot reload
  • Build: Compile your frontend with your Go app
  • Deploy: Embed frontend assets in a single Go binary
  • Serve: Serve SPA with proper routing and caching
import (
    "github.com/go-mizu/mizu"
    "github.com/go-mizu/mizu/frontend"
)

func main() {
    app := mizu.New()

    // API routes
    app.Get("/api/users", listUsers)

    // Frontend (React, Vue, etc.)
    app.Use(frontend.New(frontend.Config{
        Directory: "web/dist",
    }))

    app.Listen(":3000")
}

Supported Frameworks

FrameworkTemplateDev Server
Reactfrontend:reactVite
React Routerfrontend:reactrouterVite
Vuefrontend:vueVite
Sveltefrontend:svelteVite
SvelteKitfrontend:sveltekitVite
Angularfrontend:angularAngular CLI
HTMXfrontend:htmxVite
Next.jsfrontend:nextNext.js
Nuxtfrontend:nuxtNuxt
Preactfrontend:preactVite
Alpine.jsfrontend:alpineVite

Quick Start

1. Create a Project

# React frontend
mizu new myapp --template frontend:react

# Or Vue
mizu new myapp --template frontend:vue

# Or Svelte
mizu new myapp --template frontend:svelte

2. Project Structure

myapp/
├── cmd/
│   └── server/
│       └── main.go         # Go backend
├── web/                     # Frontend app
│   ├── src/
│   │   ├── App.tsx
│   │   └── main.tsx
│   ├── index.html
│   ├── package.json
│   └── vite.config.ts
├── go.mod
└── Makefile

3. Development Mode

# Terminal 1: Start frontend dev server
cd web && npm run dev

# Terminal 2: Start Go server with proxy
mizu dev
Or use the combined command:
make dev  # Runs both concurrently

4. Production Build

# Build frontend
cd web && npm run build

# Build Go with embedded frontend
go build -o server ./cmd/server

Development Workflow

Proxy to Dev Server

In development, Mizu proxies frontend requests to Vite:
import "github.com/go-mizu/mizu/frontend"

func main() {
    app := mizu.New()

    // API routes handled by Go
    api := app.Group("/api")
    api.Get("/users", listUsers)

    // Everything else proxied to Vite
    if os.Getenv("ENV") == "development" {
        app.Use(frontend.DevProxy("http://localhost:5173"))
    } else {
        app.Use(frontend.Static(frontend.Config{
            Directory: "web/dist",
        }))
    }

    app.Listen(":3000")
}

Hot Module Replacement

With the proxy, you get full HMR support:
  1. Edit React/Vue/Svelte components
  2. Browser updates instantly
  3. No page refresh needed
  4. State preserved

Production Deployment

Embedding Assets

Embed frontend assets in your Go binary:
import "embed"

//go:embed web/dist/*
var frontendFS embed.FS

func main() {
    app := mizu.New()

    // API routes
    app.Get("/api/users", listUsers)

    // Embedded frontend
    app.Use(frontend.Embed(frontendFS, "web/dist"))

    app.Listen(":3000")
}

Build Script

# Makefile
build:
    cd web && npm run build
    go build -o server ./cmd/server

docker:
    docker build -t myapp .

Dockerfile

# Build frontend
FROM node:20 AS frontend
WORKDIR /app/web
COPY web/package*.json ./
RUN npm install
COPY web/ ./
RUN npm run build

# Build Go app
FROM golang:1.22 AS backend
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
COPY --from=frontend /app/web/dist ./web/dist
RUN CGO_ENABLED=0 go build -o server ./cmd/server

# Final image
FROM alpine:3.19
COPY --from=backend /app/server /server
EXPOSE 3000
CMD ["/server"]

Framework Examples

React

// web/src/App.tsx
import { useState, useEffect } from 'react';

interface User {
  id: string;
  name: string;
}

function App() {
  const [users, setUsers] = useState<User[]>([]);

  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(setUsers);
  }, []);

  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

Vue

<!-- web/src/App.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue';

interface User {
  id: string;
  name: string;
}

const users = ref<User[]>([]);

onMounted(async () => {
  const res = await fetch('/api/users');
  users.value = await res.json();
});
</script>

<template>
  <h1>Users</h1>
  <ul>
    <li v-for="user in users" :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>

Svelte

<!-- web/src/App.svelte -->
<script lang="ts">
  import { onMount } from 'svelte';

  interface User {
    id: string;
    name: string;
  }

  let users: User[] = [];

  onMount(async () => {
    const res = await fetch('/api/users');
    users = await res.json();
  });
</script>

<h1>Users</h1>
<ul>
  {#each users as user}
    <li>{user.name}</li>
  {/each}
</ul>

Configuration

Frontend Config

frontend.New(frontend.Config{
    // Static file directory
    Directory: "web/dist",

    // Index file for SPA routing
    Index: "index.html",

    // Cache control
    CacheControl: "public, max-age=31536000",

    // Compression
    Compress: true,

    // Custom headers
    Headers: map[string]string{
        "X-Frame-Options": "DENY",
    },
})

Vite Config

// web/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    proxy: {
      '/api': 'http://localhost:3000',
    },
  },
  build: {
    outDir: 'dist',
  },
});

Environment Variables

Inject environment variables at runtime:
app.Get("/env.js", func(c *mizu.Ctx) error {
    c.Header().Set("Content-Type", "application/javascript")
    return c.Text(200, fmt.Sprintf(`
        window.ENV = {
            API_URL: "%s",
            VERSION: "%s"
        };
    `, os.Getenv("API_URL"), version))
})
<!-- index.html -->
<script src="/env.js"></script>
// In your app
const apiUrl = window.ENV?.API_URL || '/api';

SPA Routing

Handle client-side routing:
app.Use(frontend.New(frontend.Config{
    Directory: "web/dist",
    Index:     "index.html",
    // Returns index.html for all non-file requests
    SPAMode:   true,
}))
This ensures routes like /users/123 return index.html so React Router/Vue Router can handle them.

Caching Strategies

Immutable Assets

// Assets with hashes get long cache
app.Get("/assets/*", func(c *mizu.Ctx) error {
    c.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
    return next(c)
})

HTML Files

// HTML files should never be cached
app.Get("/", func(c *mizu.Ctx) error {
    c.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
    return serveIndex(c)
})

Security

Content Security Policy

app.Use(func(next mizu.Handler) mizu.Handler {
    return func(c *mizu.Ctx) error {
        c.Header().Set("Content-Security-Policy",
            "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'")
        return next(c)
    }
})

CORS for API

import "github.com/go-mizu/mizu/middlewares/cors"

api := app.Group("/api")
api.Use(cors.New(cors.Config{
    AllowOrigins: []string{"http://localhost:5173"},  // Dev
}))

Learn More

Next Steps