Skip to content

本地开发代理配置

本文档介绍如何配置 VitePress 开发服务器的代理,解决开发环境的跨域问题和 API 调用需求。

为什么需要代理?

在开发过程中,前端通常运行在 localhost:5173,而后端 API 可能运行在不同端口或域名:

  • 跨域问题:浏览器阻止跨域请求
  • Cookie 问题:跨域请求无法携带 Cookie
  • 开发便利:统一 API 请求路径

代理可以让开发服务器转发请求到后端,避免跨域问题。

基础配置

简单代理

ts
// docs/.vitepress/config.mts
import { defineConfig } from 'vitepress'

export default defineConfig({
  vite: {
    server: {
      proxy: {
        // 将 /api 请求代理到后端
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true
        }
      }
    }
  }
})

配置说明

选项说明
target代理目标地址
changeOrigin修改请求头中的 Origin 为目标地址
rewrite重写请求路径
secure是否验证 SSL 证书
ws是否代理 WebSocket
configure自定义代理配置

路径重写

移除前缀

ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, '')
        }
      }
    }
  }
})

请求转换:

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

添加前缀

ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/v1': {
          target: 'http://api.example.com',
          changeOrigin: true,
          rewrite: (path) => `/api${path}`
        }
      }
    }
  }
})

请求转换:

前端请求: /v1/users
代理到:   http://api.example.com/api/v1/users

多代理配置

不同路径代理到不同目标

ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        // API 服务
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true
        },
        
        // 认证服务
        '/auth': {
          target: 'http://localhost:3001',
          changeOrigin: true
        },
        
        // 静态资源
        '/static': {
          target: 'http://localhost:3002',
          changeOrigin: true
        },
        
        // WebSocket
        '/ws': {
          target: 'ws://localhost:4000',
          ws: true
        }
      }
    }
  }
})

使用正则匹配

ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        // 匹配所有以 /api 开头的请求
        '^/api/.*': {
          target: 'http://localhost:3000',
          changeOrigin: true
        }
      }
    }
  }
})

高级配置

自定义请求头

ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true,
          configure: (proxy, options) => {
            proxy.on('proxyReq', (proxyReq, req, res) => {
              // 添加自定义请求头
              proxyReq.setHeader('X-Custom-Header', 'value')
            })
          }
        }
      }
    }
  }
})

响应拦截

ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true,
          configure: (proxy) => {
            proxy.on('proxyRes', (proxyRes, req, res) => {
              console.log(`[Proxy] ${req.method} ${req.url} -> ${proxyRes.statusCode}`)
              
              // 修改响应头
              proxyRes.headers['x-proxy'] = 'vitepress'
            })
          }
        }
      }
    }
  }
})

错误处理

ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true,
          configure: (proxy) => {
            proxy.on('error', (err, req, res) => {
              console.error('[Proxy Error]', err.message)
              
              // 返回错误响应
              if (!res.headersSent) {
                res.writeHead(502, { 'Content-Type': 'application/json' })
                res.end(JSON.stringify({ 
                  error: 'Proxy Error',
                  message: err.message 
                }))
              }
            })
          }
        }
      }
    }
  }
})

HTTPS 代理

代理到 HTTPS 服务

ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/api': {
          target: 'https://api.example.com',
          changeOrigin: true,
          secure: true,  // 验证 SSL 证书
          // 或忽略证书验证(仅开发环境)
          // secure: false
        }
      }
    }
  }
})

开发服务器启用 HTTPS

ts
export default defineConfig({
  vite: {
    server: {
      https: {
        key: fs.readFileSync('./cert/key.pem'),
        cert: fs.readFileSync('./cert/cert.pem')
      },
      proxy: {
        '/api': {
          target: 'https://api.example.com',
          changeOrigin: true
        }
      }
    }
  }
})

使用 mkcert 生成本地证书

bash
# 安装 mkcert
brew install mkcert  # macOS
choco install mkcert # Windows

# 安装本地 CA
mkcert -install

# 生成证书
mkcert localhost 127.0.0.1 ::1
ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true,
          // Cookie 域名重写
          cookieDomainRewrite: {
            '*': ''  // 清除域名限制
          }
        }
      }
    }
  }
})
ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true,
          configure: (proxy) => {
            proxy.on('proxyRes', (proxyRes) => {
              const cookies = proxyRes.headers['set-cookie']
              if (cookies) {
                // 修改 Cookie 属性
                proxyRes.headers['set-cookie'] = cookies.map(cookie => 
                  cookie.replace(/; Secure/gi, '')  // 开发环境移除 Secure
                )
              }
            })
          }
        }
      }
    }
  }
})

WebSocket 代理

基础 WebSocket 代理

ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/ws': {
          target: 'ws://localhost:4000',
          ws: true  // 启用 WebSocket 代理
        }
      }
    }
  }
})

前端使用

vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const message = ref('')
let socket = null

onMounted(() => {
  socket = new WebSocket(`ws://${location.host}/ws`)
  
  socket.onmessage = (event) => {
    message.value = event.data
  }
})

onUnmounted(() => {
  socket?.close()
})

function send(data) {
  socket?.send(data)
}
</script>

Mock 数据

使用代理拦截实现 Mock

ts
// docs/.vitepress/config.mts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true,
          configure: (proxy) => {
            proxy.on('proxyReq', (proxyReq, req, res) => {
              // Mock 特定接口
              if (req.url === '/api/users') {
                // 直接返回 Mock 数据
                res.writeHead(200, { 'Content-Type': 'application/json' })
                res.end(JSON.stringify({
                  users: [
                    { id: 1, name: 'User 1' },
                    { id: 2, name: 'User 2' }
                  ]
                }))
                return
              }
            })
          }
        }
      }
    }
  }
})

使用 vite-plugin-mock

bash
npm add -D vite-plugin-mock mockjs
ts
// docs/.vitepress/config.mts
import { viteMockServe } from 'vite-plugin-mock'

export default defineConfig({
  vite: {
    plugins: [
      viteMockServe({
        mockPath: 'mock',
        enableDevelopment: true
      })
    ]
  }
})
ts
// mock/user.ts
import { MockMethod } from 'vite-plugin-mock'

export default [
  {
    url: '/api/users',
    method: 'get',
    response: () => {
      return {
        code: 0,
        data: [
          { id: 1, name: 'User 1' },
          { id: 2, name: 'User 2' }
        ]
      }
    }
  }
] as MockMethod[]

环境区分

开发/生产环境区分

ts
// docs/.vitepress/config.mts
import { defineConfig, loadEnv } from 'vitepress'

const env = loadEnv('', process.cwd())
const isDev = process.env.NODE_ENV === 'development'

export default defineConfig({
  vite: {
    server: isDev ? {
      proxy: {
        '/api': {
          target: env.VITE_API_URL || 'http://localhost:3000',
          changeOrigin: true
        }
      }
    } : {}
  }
})

不同环境不同代理

ts
// docs/.vitepress/config.mts
import { defineConfig, loadEnv } from 'vitepress'

const mode = process.env.NODE_ENV || 'development'

const proxyConfig = {
  development: {
    '/api': {
      target: 'http://localhost:3000',
      changeOrigin: true
    }
  },
  staging: {
    '/api': {
      target: 'https://staging-api.example.com',
      changeOrigin: true
    }
  }
}

export default defineConfig({
  vite: {
    server: {
      proxy: proxyConfig[mode] || proxyConfig.development
    }
  }
})

调试代理

日志记录

ts
export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true,
          configure: (proxy) => {
            proxy.on('proxyReq', (proxyReq, req) => {
              console.log(`[Proxy Request] ${req.method} ${req.url}`)
            })
            
            proxy.on('proxyRes', (proxyRes, req) => {
              console.log(`[Proxy Response] ${req.url} -> ${proxyRes.statusCode}`)
            })
            
            proxy.on('error', (err) => {
              console.error('[Proxy Error]', err)
            })
          }
        }
      }
    }
  }
})

使用 http-proxy-middleware

bash
npm add -D http-proxy-middleware
ts
// docs/.vitepress/config.mts
import { createProxyMiddleware } from 'http-proxy-middleware'

export default defineConfig({
  vite: {
    server: {
      proxy: {
        '/api': createProxyMiddleware({
          target: 'http://localhost:3000',
          changeOrigin: true,
          on: {
            proxyReq: (proxyReq, req) => {
              console.log(`[Proxy] ${req.method} ${req.url}`)
            }
          }
        })
      }
    }
  }
})

常见问题

Q: 代理不生效?

检查以下几点:

  1. 路径是否正确匹配
  2. changeOrigin 是否设置
  3. 目标服务是否正常运行
ts
// 调试配置
server: {
  proxy: {
    '/api': {
      target: 'http://localhost:3000',
      changeOrigin: true,
      configure: (proxy) => {
        proxy.on('proxyReq', (proxyReq, req) => {
          console.log('代理请求:', req.url)
        })
      }
    }
  }
}

Q: WebSocket 连接失败?

确保:

  1. 设置 ws: true
  2. 使用正确的 WebSocket 协议(ws:// 或 wss://)
ts
'/ws': {
  target: 'ws://localhost:4000',
  ws: true,
  changeOrigin: true
}
ts
'/api': {
  target: 'http://localhost:3000',
  changeOrigin: true,
  cookieDomainRewrite: {
    '*': ''
  }
}

完整示例

ts
// docs/.vitepress/config.mts
import { defineConfig, loadEnv } from 'vitepress'
import type { ServerOptions } from 'vite'

const env = loadEnv('', process.cwd())

const proxyConfig: ServerOptions['proxy'] = {
  // API 代理
  '/api': {
    target: env.VITE_API_URL || 'http://localhost:3000',
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\/api/, ''),
    configure: (proxy) => {
      // 请求日志
      proxy.on('proxyReq', (proxyReq, req) => {
        console.log(`[API] ${req.method} ${req.url}`)
      })
      
      // 错误处理
      proxy.on('error', (err, req, res) => {
        console.error('[Proxy Error]', err.message)
        if (!res.headersSent) {
          res.writeHead(502)
          res.end('Proxy Error')
        }
      })
    }
  },
  
  // WebSocket 代理
  '/ws': {
    target: 'ws://localhost:4000',
    ws: true
  },
  
  // 静态资源代理
  '/static': {
    target: 'http://localhost:3002',
    changeOrigin: true
  }
}

export default defineConfig({
  vite: {
    server: {
      port: 5173,
      host: true,
      proxy: proxyConfig
    }
  }
})

下一步

学习 Markdown 扩展语法 了解更多内容编写技巧。

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献