Go + React 单文件 Web 应用模板开发指南
本文将详细介绍如何从零构建一个 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 安装)
验证安装:
go version # 应输出 Go 1.24.xnode --version npm --version 第二步:初始化 Go 项目
# 创建项目目录mkdir -p gin-frontend-template cd gin-frontend-template # 创建后端目录mkdir backend # 初始化 Go 模块 go mod init yourmodule # 添加 Gin 依赖 go get github.com/gin-gonic/[email protected] 2.1 创建后端路由文件
# 创建路由文件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, }) } EOF2.2 创建主入口文件
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 项目
# 在项目根目录下创建前端项目npm create vite@latest frontend -- --template react-ts # 进入前端目录cd frontend # 安装依赖npminstall# 安装 Tailwind CSSnpminstall @tailwindcss/vite -D 3.2 配置 Vite(vite.config.ts)
# 编辑 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 创建占位文件
# 在 dist 目录创建占位文件,防止空目录导致 embed 失败touch frontend/dist/.keep 3.4 返回项目根目录
cd../..第四步:创建构建脚本
仅支持 unix 系统
# 创建构建脚本目录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"];thenEXT=".exe"fiOUT_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"npminstallnpm 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 - 启动后端:
go run .后端服务运行在 http://localhost:8080
终端 2 - 启动前端开发服务器:
cd frontend npm run dev 前端开发服务器运行在 http://localhost:5173,并自动代理 /api 请求到后端。
访问 http://localhost:5173,你将看到 API 测试界面,可以测试:
GET /api/ping- 健康检查POST /api/echo- 回声测试
5.2 生产构建
Unix/macOS/Linux:
chmod +x scripts/build.sh ./scripts/build.sh 构建产物位于 build/ 目录,命名格式:
yourmodule-linux-amd64yourmodule-darwin-arm64yourmodule-windows-amd64.exe
5.3 交叉编译
# LinuxGOOS=linux GOARCH=amd64 scripts/build.sh # WindowsGOOS=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 运行生产版本
# 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 应用。