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, }) } EOF

2.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

核心设计要点:

  1. //go:embed all:frontend/dist - 将前端构建产物嵌入到二进制文件
  2. /api/* 路径始终由后端处理,不回退
  3. 带扩展名的静态资源必须存在,否则返回 404
  4. 无扩展名的路径视为 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-amd64
  • yourmodule-darwin-arm64
  • yourmodule-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

这种设计确保:

  1. API 请求不会被前端路由拦截
  2. 静态资源文件不存在时返回 404,而非回退到首页
  3. 前端 SPA 路由(如 /dashboard/settings)能正常工作

总结

通过本文的步骤,你已经成功构建了一个完整的 Go + React 单文件 Web 应用模板。该模板具有以下特点:

  • 单一可执行文件:生产环境只需部署一个文件
  • 高效开发模式:前端热更新 + API 代理
  • 清晰的项目结构:前后端职责分明
  • 跨平台支持:支持 Linux、macOS、Windows
  • SPA 路由支持:完美支持前端路由

你可以在此基础上继续扩展,添加数据库集成、用户认证、中间件等功能,构建完整的 Web 应用。

Could not load content