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 应用。

相关推荐
恋猫de小郭9 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木9 小时前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮9 小时前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati9 小时前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉9 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain
wuhen_n9 小时前
双端 Diff 算法详解
前端·javascript·vue.js
UrbanJazzerati9 小时前
Vue 3 纯小白快速入门指南
前端·面试
雮尘9 小时前
手把手带你玩转Android gRPC:一篇搞定原理、配置与客户端开发
android·前端·grpc
光影少年9 小时前
说说闭包的理解和应用场景?
前端·javascript·掘金·金石计划
是一碗螺丝粉9 小时前
LangChain 核心组件深度解析:模型与提示词模板
前端·langchain·aigc