本文将详细介绍如何从零构建一个 Go 后端(Gin) + 前端(Vite + React) 的单文件 Web 应用模板。最终构建产物为单一可执行文件,适用于工具型应用、私有化部署系统或需统一交付的 Web 项目。
技术栈
| 层级 | 技术选型 |
|---|---|
| 后端语言 | Go 1.24+ |
| 后端框架 | Gin |
| 前端框架 | React 19.x |
| 前端构建 | Vite 7.x |
| 前端语言 | TypeScript 5.x |
| 样式方案 | Tailwind CSS 4.x |
项目目录结构
gin-frontend-template/
├── main.go # 程序入口:路由分流与 embed 静态资源
├── go.mod # Go 模块定义
├── backend/ # 后端业务代码
│ └── router.go # /api 路由注册
├── frontend/ # 前端项目
│ ├── package.json # 前端依赖配置
│ ├── vite.config.ts # Vite 构建配置
│ ├── tsconfig.json # TypeScript 根配置
│ ├── tsconfig.app.json # TypeScript 应用配置
│ ├── tsconfig.node.json # TypeScript Node 配置
│ ├── eslint.config.js # ESLint 配置
│ ├── index.html # HTML 入口
│ ├── public/ # 静态资源目录
│ ├── src/ # 前端源码
│ │ ├── main.tsx # React 应用入口
│ │ ├── App.tsx # 主应用组件
│ │ └── index.css # 全局样式
│ └── dist/ # 构建产物(会被 embed 到可执行文件)
├── scripts/
│ ├── build.sh # Unix/Linux/macOS 构建脚本
│ └── build.ps1 # Windows 构建脚本
└── build/ # 构建输出目录
第一步:环境准备
确保你的开发环境已安装以下工具:
- Go 1.20 或更高版本(推荐 1.24)
- Node.js 18 或更高版本
- npm(随 Node.js 安装)
验证安装:
bash
go version # 应输出 Go 1.24.x
node --version
npm --version
第二步:初始化 Go 项目
bash
# 创建项目目录
mkdir -p gin-frontend-template
cd gin-frontend-template
# 创建后端目录
mkdir backend
# 初始化 Go 模块
go mod init yourmodule
# 添加 Gin 依赖
go get github.com/gin-gonic/gin@v1.11.0
2.1 创建后端路由文件
bash
# 创建路由文件
cat > backend/router.go << 'EOF'
package backend
import (
"net/http"
"github.com/gin-gonic/gin"
)
// Register 只负责注册 API
func Register(r *gin.Engine) {
api := r.Group("/api")
{
api.GET("/ping", ping)
api.POST("/echo", echo)
}
}
func ping(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
}
func echo(c *gin.Context) {
var body map[string]any
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"data": body,
})
}
EOF
2.2 创建主入口文件
bash
cat > main.go << 'EOF'
package main
import (
"embed"
"io/fs"
"mime"
"net/http"
"path"
"path/filepath"
"strings"
"github.com/gin-gonic/gin"
"yourmodule/backend"
)
//go:embed all:frontend/dist
var assets embed.FS
func frontendHandler(dist fs.FS) gin.HandlerFunc {
return func(c *gin.Context) {
reqPath := c.Request.URL.Path
// API 永不回退
if strings.HasPrefix(reqPath, "/api/") {
c.Status(http.StatusNotFound)
return
}
// 根路径映射 index.html
if reqPath == "/" {
reqPath = "/index.html"
}
// 规范化路径,防止 ../
filePath := strings.TrimPrefix(path.Clean(reqPath), "/")
ext := filepath.Ext(filePath)
data, err := fs.ReadFile(dist, filePath)
if err != nil {
// 仅对「页面路由(无扩展名)」回退 SPA
if ext == "" {
if data, err = fs.ReadFile(dist, "index.html"); err == nil {
c.Data(http.StatusOK, "text/html; charset=utf-8", data)
return
}
}
c.Status(http.StatusNotFound)
return
}
ct := mime.TypeByExtension(ext)
if ct == "" {
ct = "application/octet-stream"
}
c.Data(http.StatusOK, ct, data)
}
}
func main() {
r := gin.Default()
// 注册后端 API(backend 不创建 Engine)
backend.Register(r)
// 前端 embed
distFS, err := fs.Sub(assets, "frontend/dist")
if err != nil {
panic(err)
}
r.NoRoute(frontendHandler(distFS))
// 启动
r.Run(":8080")
}
EOF
核心设计要点:
//go:embed all:frontend/dist- 将前端构建产物嵌入到二进制文件/api/*路径始终由后端处理,不回退- 带扩展名的静态资源必须存在,否则返回 404
- 无扩展名的路径视为 SPA 路由,回退到
index.html
第三步:初始化前端项目
3.1 使用 Vite 创建 React + TypeScript 项目
bash
# 在项目根目录下创建前端项目
npm create vite@latest frontend -- --template react-ts
# 进入前端目录
cd frontend
# 安装依赖
npm install
# 安装 Tailwind CSS
npm install @tailwindcss/vite -D
3.2 配置 Vite(vite.config.ts)
bash
# 编辑 vite.config.ts,替换为以下内容:
cat > vite.config.ts << 'EOF'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
server: {
port: 5173,
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true
}
}
}
})
EOF
配置说明:
- 开发服务器运行在 5173 端口
/api请求代理到后端localhost:8080,避免跨域问题
3.3 创建占位文件
bash
# 在 dist 目录创建占位文件,防止空目录导致 embed 失败
touch frontend/dist/.keep
3.4 返回项目根目录
bash
cd ../..
第四步:创建构建脚本
仅支持 unix 系统
bash
# 创建构建脚本目录
mkdir -p scripts
# 创建构建脚本
cat > scripts/build.sh << 'SCRIPT'
#!/usr/bin/env bash
set -e
# script -> root
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
BUILD_DIR="$ROOT_DIR/build"
# ========================
# Resolve app name from go.mod
# ========================
if [ ! -f "$ROOT_DIR/go.mod" ]; then
echo "error: go.mod not found in project root"
exit 1
fi
MODULE_PATH="$(grep '^module ' "$ROOT_DIR/go.mod" | awk '{print $2}')"
APP_NAME="${MODULE_PATH##*/}"
# ========================
# Target platform
# ========================
GOOS="${GOOS:-$(go env GOOS)}"
GOARCH="${GOARCH:-$(go env GOARCH)}"
EXT=""
if [ "$GOOS" = "windows" ]; then
EXT=".exe"
fi
OUT_NAME="${APP_NAME}-${GOOS}-${GOARCH}${EXT}"
echo "module : $MODULE_PATH"
echo "app : $APP_NAME"
echo "target : $GOOS / $GOARCH"
echo "output : build/$OUT_NAME"
mkdir -p "$BUILD_DIR"
# ========================
# Build frontend
# ========================
echo "build frontend"
cd "$ROOT_DIR/frontend"
npm install
npm run build
touch dist/.keep
# ========================
# Build backend
# ========================
echo "build backend"
cd "$ROOT_DIR"
GOOS="$GOOS" GOARCH="$GOARCH" \
CGO_ENABLED=0 \
go build \
-trimpath \
-ldflags="-s -w" \
-o "$BUILD_DIR/$OUT_NAME"
echo "build success"
echo "$BUILD_DIR/$OUT_NAME"
SCRIPT
# 添加执行权限
chmod +x scripts/build.sh
第五步:运行与测试
5.1 开发模式
终端 1 - 启动后端:
bash
go run .
后端服务运行在 http://localhost:8080
终端 2 - 启动前端开发服务器:
bash
cd frontend
npm run dev
前端开发服务器运行在 http://localhost:5173,并自动代理 /api 请求到后端。
访问 http://localhost:5173,你将看到 API 测试界面,可以测试:
GET /api/ping- 健康检查POST /api/echo- 回声测试
5.2 生产构建
Unix/macOS/Linux:
bash
chmod +x scripts/build.sh
./scripts/build.sh
构建产物位于 build/ 目录,命名格式:
yourmodule-linux-amd64yourmodule-darwin-arm64yourmodule-windows-amd64.exe
5.3 交叉编译
bash
# Linux
GOOS=linux GOARCH=amd64 scripts/build.sh
# Windows
GOOS=windows GOARCH=amd64 scripts/build.sh
# macOS (Intel)
GOOS=darwin GOARCH=amd64 scripts/build.sh
# macOS (Apple Silicon)
GOOS=darwin GOARCH=arm64 scripts/build.sh
5.4 运行生产版本
bash
# Linux/macOS
./build/yourmodule-linux-amd64
# Windows
.\build\yourmodule-windows-amd64.exe
服务同样运行在 http://localhost:8080。
请求路由策略
本项目的路由策略设计如下:
| 路径模式 | 处理方式 |
|---|---|
/api/* |
始终由 Go 后端处理,永不回退 |
带扩展名(.js、.css、.png 等) |
必须存在,否则返回 404 |
| 无扩展名路径 | 视为 SPA 路由,回退至 index.html |
这种设计确保:
- API 请求不会被前端路由拦截
- 静态资源文件不存在时返回 404,而非回退到首页
- 前端 SPA 路由(如
/dashboard、/settings)能正常工作
总结
通过本文的步骤,你已经成功构建了一个完整的 Go + React 单文件 Web 应用模板。该模板具有以下特点:
- 单一可执行文件:生产环境只需部署一个文件
- 高效开发模式:前端热更新 + API 代理
- 清晰的项目结构:前后端职责分明
- 跨平台支持:支持 Linux、macOS、Windows
- SPA 路由支持:完美支持前端路由
你可以在此基础上继续扩展,添加数据库集成、用户认证、中间件等功能,构建完整的 Web 应用。