Skip to content
与后端集成

与后端集成

本章涵盖 Vite 与主流后端(Node.js / Golang / Python 等)的集成方案,包括开发代理、生产部署和 SSR。


开发阶段:API 代理

解决前后端分离项目在开发阶段的跨域问题。

最简代理

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': 'http://localhost:8080',
    },
  },
})

前端请求 /api/users → 代理到 http://localhost:8080/api/users

路径重写

proxy: {
  '/api': {
    target: 'http://localhost:8080',
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\/api/, ''),  // /api/users → /users
  },
}

多个后端服务

proxy: {
  '/api': 'http://localhost:8080',
  '/auth': 'http://localhost:8081',
  '/ws': {
    target: 'ws://localhost:8082',
    ws: true,
  },
}

生产部署:静态文件 + 后端服务

构建产物是纯静态文件,可以部署到任何 Web 服务器或 CDN。

方式一:Nginx 反向代理

server {
    listen 80;
    server_name example.com;

    root /var/www/dist;
    index index.html;

    # API 代理到后端
    location /api/ {
        proxy_pass http://backend:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # SPA 回退:所有非文件请求返回 index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 静态资源强缓存(带 hash 的文件)
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

💡 最佳实践:带 hash 的构建产物可以设置 immutable 强缓存,index.html 必须设为 no-cache(每次协商)。

方式二:Node.js 后端直接托管

// server.js
import express from 'express'
import { fileURLToPath } from 'url'
import path from 'path'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const app = express()

// 静态文件服务
app.use(express.static(path.join(__dirname, 'dist')))

// API 路由
app.get('/api/hello', (req, res) => {
  res.json({ msg: 'Hello from backend' })
})

// SPA fallback
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'))
})

app.listen(3000)

方式三:Golang 嵌入静态文件

package main

import (
    "embed"
    "io/fs"
    "net/http"
)

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

func main() {
    // 去除 dist/ 前缀
    staticFS, _ := fs.Sub(dist, "dist")

    mux := http.NewServeMux()

    // API 路由
    mux.HandleFunc("/api/hello", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(`{"msg": "Hello"}`))
    })

    // 静态文件 + SPA fallback
    mux.Handle("/", spaHandler{fs: staticFS})

    http.ListenAndServe(":8080", mux)
}

中间件模式

当你的后端是 Node.js 且希望共用同一端口时,将 Vite 作为中间件引入:

// server.js
import express from 'express'
import { createServer as createViteServer } from 'vite'

async function createServer() {
  const app = express()

  // 1. 先创建 Vite 开发服务器(middlewareMode)
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom',
  })

  // 2. 将 Vite 中间件挂载到 Express
  app.use(vite.middlewares)

  // 3. 你的 API 路由
  app.get('/api/data', async (req, res) => {
    res.json({ data: 'hello' })
  })

  // 4. 这步可选——如果要用 Vite 的 HTML 处理
  app.use('*', async (req, res, next) => {
    const url = req.originalUrl
    try {
      let template = fs.readFileSync('index.html', 'utf-8')
      template = await vite.transformIndexHtml(url, template)
      res.status(200).set({ 'Content-Type': 'text/html' }).end(template)
    } catch (e) {
      vite.ssrFixStacktrace(e)
      next(e)
    }
  })

  app.listen(3000)
}

createServer()

💡 最佳实践:中间件模式也适用于测试框架(Vitest 的 pool: 'forks')和一些自定义 dev server 需求。


SSR(服务端渲染)

Vite 对 SSR 提供了一等公民的支持。

基础 SSR 架构

src/
├── entry-server.js    # 服务端入口
├── entry-client.js    # 客户端入口
├── App.vue(或 App.jsx)
└── router/

服务端入口

// src/entry-server.js
export async function render(url) {
  // 根据 URL 匹配路由、拉取数据、渲染为 HTML 字符串
  const html = renderToString(/* ... */)
  return { html, state: {} }
}

客户端入口

// src/entry-client.js
import { createApp } from './app'
const { app, router } = createApp()

router.isReady().then(() => {
  app.mount('#app')
})

SSR 服务器

// server.js
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import express from 'express'
import { createServer as createViteServer } from 'vite'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

async function createServer() {
  const app = express()

  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom',
  })

  app.use(vite.middlewares)

  app.use('*', async (req, res) => {
    try {
      const url = req.originalUrl

      // 1. 读取 index.html
      let template = fs.readFileSync(
        path.resolve(__dirname, 'index.html'),
        'utf-8'
      )

      // 2. 应用 Vite HTML 转换(注入 HMR 客户端等)
      template = await vite.transformIndexHtml(url, template)

      // 3. 加载服务端入口(Vite 实时编译)
      const { render } = await vite.ssrLoadModule('/src/entry-server.js')

      // 4. 渲染
      const { html, state } = await render(url)

      // 5. 注入渲染结果到 HTML 模板
      const responseHtml = template
        .replace('<!--ssr-outlet-->', html)
        .replace('<!--ssr-state-->', JSON.stringify(state))

      res.status(200).set({ 'Content-Type': 'text/html' }).end(responseHtml)
    } catch (e) {
      vite.ssrFixStacktrace(e)
      console.error(e)
      res.status(500).end(e.stack)
    }
  })

  app.listen(3000)
}

createServer()

💡 最佳实践:生产环境的 SSR 不需要 Vite 中间件,直接使用构建产物中的 entry-server.js 文件即可。例如 import { render } from './dist/server/entry-server.js'


部署配置

子路径部署(base)

当应用部署在非根路径(如 https://example.com/my-app/):

export default defineConfig({
  base: '/my-app/',
})
# 也可通过命令行指定
vite build --base=/my-app/

base 影响:

  • 资源 URL 前缀
  • <link> / <script>href/src
  • import.meta.env.BASE_URL

🚨 陷阱base 末尾的 / 很重要。/my-app/my-app/——缺少末尾斜杠可能导致资源路径错误。

CDN 部署

export default defineConfig({
  base: 'https://cdn.example.com/my-app/',
})

部署平台适配

平台 关键配置
Vercel 无需配置,自动检测 Vite
Netlify 构建命令 vite build,发布目录 dist,SPA 需 _redirects 文件
Cloudflare Pages 构建命令 vite build,输出 dist
GitHub Pages base: '/repo-name/' + GitHub Actions
Nginx 见上方 Nginx 配置
Docker 多阶段构建:先 npm run build,再 nginx:alpine 拷贝 dist/

Docker 部署示例

# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]