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 安装)

验证安装:

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

核心设计要点:

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

这种设计确保:

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

总结

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

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

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

相关推荐
卜锦元2 小时前
Golang后端性能优化手册(第一章:数据库性能优化)
大数据·开发语言·数据库·人工智能·后端·性能优化·golang
雨季~~2 小时前
前端使用ffmpeg进行视频格式转换 (WebM → MP4)
前端·typescript·ffmpeg·vue
星火飞码iFlyCode2 小时前
iFlyCode实践规范驱动开发(SDD):招考平台报名相片质量抽检功能开发实战
java·前端·python·算法·ai编程·科大讯飞
小北方城市网2 小时前
第 9 课:Node.js + Express 后端实战 —— 为任务管理系统搭建专属 API 服务
大数据·前端·ai·node.js·express
世界唯一最大变量2 小时前
此算法能稳定求出柏林52城问题最优解7540.23(整数时为7538),比传统旅行商问题的算法7544.37还优
前端·python·算法
小高Baby@2 小时前
map的数据结构,扩容机制,key是无序的原因
数据结构·golang·哈希算法
Nan_Shu_6142 小时前
学习:TypeScript (1)
前端·javascript·学习·typescript
沛沛老爹2 小时前
Web开发者快速上手AI Agent:基于Advanced-RAG的提示词应用
前端·人工智能·langchain·llm·rag·web转型·advanced-rag
5967851543 小时前
HTML元素
前端·html