惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
博客园 - 司徒正美
Last Week in AI
Last Week in AI
博客园 - 聂微东
Jina AI
Jina AI
月光博客
月光博客
爱范儿
爱范儿
美团技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Hugging Face - Blog
Hugging Face - Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
T
Tailwind CSS Blog
博客园 - 【当耐特】
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
罗磊的独立博客
小众软件
小众软件
雷峰网
雷峰网
IT之家
IT之家
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Visual Studio Blog

博客园 - 丁少华

adb提取本机apk 安卓ADB调试安卓 高德地图车机版本9.5制作共存 Taskfile 拆分子文件 go项目的taskfile windows的终端像mac一样好用 pgsql指定用户看到某个表 vue不使用根组件 Huma + Orval 前后端 API 自动生成方案 Mac安装nginx 不喜欢Go的几点 代码写错分支拯救指南 React UI 库推荐 Loop Engineering Vue 3 UI 组件库 Neon白嫖免费pgsql 域名利用cloudflare免费图床 nginx反代CloudflarePages提示502 vite使用shadcn vite使用biome Window下Nginx winserver2022安装不上软件 mac已损坏无法打开 代码高亮 命令运行器之task 命令运行器之just windows开启wsl 白嫖Redis 白嫖MongoDB vercel无服务函数
Go 内嵌网页
丁少华 · 2026-09-05 · via 博客园 - 丁少华

参考项目:D:\code\go-homework

记住这条链路:前端 build → Go embed → fs.Sub → HTTP 提供网页 → go build → 运行程序、浏览器访问。

1. 目录结构

myapp/
├── go.mod
├── main.go
└── frontend/
    ├── package.json
    ├── src/
    └── dist/          # pnpm build 生成
        ├── index.html
        └── assets/

2. 前端调用同源接口

fetch('/api/hello')

生产环境不要写死 http://localhost:8080。网页由哪个地址打开,相对路径就请求那个地址。

开发时前端和 Go 分别启动,用 Vite 代理:

// vite.config.ts:合并到现有配置中
server: {
  proxy: {
    '/api': 'http://localhost:8080',
  },
},

3. Go 嵌入并提供网页

下面使用标准库,方便直接复用。现有项目用 Gin 时原理相同。

package main

import (
    "embed"
    "io/fs"
    "log"
    "net/http"
)

// 路径相对于当前 Go 文件,编译前必须存在构建产物。
//go:embed frontend/dist
var assets embed.FS

func main() {
    // 去掉 frontend/dist 前缀,让 index.html 成为文件系统根目录下的文件。
    frontend, err := fs.Sub(assets, "frontend/dist")
    if err != nil {
        log.Fatal(err)
    }

    mux := http.NewServeMux()

    // API 和网页由同一个服务提供。
    mux.HandleFunc("/api/hello", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte(`{"message":"hello"}`))
    })
    mux.HandleFunc("/api/", http.NotFound)

    // 从嵌入资源读取网页,不依赖运行目录下的 dist 文件夹。
    mux.Handle("/", http.FileServer(http.FS(frontend)))

    log.Println("打开 http://localhost:8080")
    log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux))
}

这个例子只监听本机。部署到服务器、需要其他机器访问时,按需改为 :8080

前端使用 Hash 路由(如 /#/todos)可以直接配合上述代码。使用 History 路由(如 /todos)时,需要额外为页面路由返回 index.html;不要把不存在的 API、JS、CSS 也返回成首页。

4. 按顺序构建

首次创建项目时,在根目录执行一次 go mod init example.com/myapp

每次发布,在项目根目录执行:

cd frontend
pnpm install
pnpm build
cd ..
go build -ldflags="-s -w" -o myapp.exe .

必须先构建前端,再编译 Go。 Go 编译时会把当时的 dist 内容装进程序;前端改完后,必须重新执行这两步。

5. 运行和交付

.\myapp.exe

浏览器打开 http://localhost:8080

网页资源随可执行文件交付,运行时不需要 Node.js 或单独的 dist 目录。数据库、上传文件、运行配置等仍按业务需求单独处理。

对照现有项目

通用示例 go-homework 中的位置
frontend/dist static/fe-project/dist
embedfs.Sub main.go
HTTP 提供网页 routers/index.gofrontendHandler
先前端、后 Go 的构建顺序 taskfile.ymltask build

一句话记忆:embed 装进去,fs.Sub 去前缀,HTTP 送出来;先打前端,再编 Go。

优化

对你这个 Gin 项目,最贴合的是 gin-contrib/static,配合 Go 自带的 embed 使用。

方案 做什么 适用场景
gin-contrib/static 把嵌入资源接到 Gin,省去自己写静态文件读取逻辑 你现在这种“运行 exe,浏览器访问”
Wails 整合前端构建、Go 编译、资源嵌入和桌面窗口 希望网页变成独立桌面软件
go.rice 通过生成 Go 代码或向二进制追加资源完成打包 需要这类特殊资源打包方式

gin-contrib/static,核心代码可以简化为:

import (
    "embed"

    "github.com/gin-contrib/static"
    "github.com/gin-gonic/gin"
)

//go:embed static/fe-project/dist
var assets embed.FS

func main() {
    r := gin.Default()

    frontend, err := static.EmbedFolder(
        assets, "static/fe-project/dist",
    )
    if err != nil {
        panic(err)
    }

    r.Use(static.Serve("/", frontend))

    // 在这里注册 /api 路由

    if err := r.Run(":8080"); err != nil {
        panic(err)
    }
}

安装:

go get github.com/gin-contrib/static

构建仍然是 pnpm build,再 go build。这个库负责提供静态网页,真正把网页装进可执行文件的仍然是 embed。History 路由的首页兜底需要另外处理。官方示例

你现在可以用 embed + gin-contrib/static;如果只是想少记代码,也可以把现有实现保存成项目模板,无需增加依赖。