Union 官网 site/:Astro + Contentful + Nix 构建的营销站点工程实现
Union 的官方站点(union.build)承载了协议介绍、团队、路线图与博客等内容,其全部源码位于仓库的 site/ 目录。本文基于 site/README.md 的原始说明展开,并结合仓库中的构建配置、Contentful 集成代码与页面实现,完整讲解该站点的技术栈构成、一键本地开发流程(nix run .#site-dev-server)、生产构建管线以及 CMS 数据获取与渲染链路,帮助读者理解一个面向 DeFi 受众的营销站点是如何在 Nix 环境中被开发、类型检查与打包发布的。
站点定位:README 中的原始定义
site/README.md 对站点的定位非常直接:
- 站点域名为 union.build,用于介绍 Union 协议并承载其 blog;
- 启动方式是一条 Nix 命令:
nix run .#site-dev-server,启动后编辑site/下的文件即可在浏览器中看到即时变更; - 技术架构一句话概括:它是一个 Astro 站点,从 Contentful CMS 拉取内容,使用 Tailwind 做样式,使用 Spline 制作 3D 模型。
下面各节将把这几句话逐一落到仓库中的真实配置与源码上。
本地开发:nix run .#site-dev-server 到底做了什么
README 给出的 Quickstart 命令由 site/site.nix 中的 apps.site-dev-server 定义(site/site.nix 第 46–61 行)。展开来看,它是一个 pkgs.writeShellApplication,执行逻辑等价于:
# 1. 确保当前处于仓库根目录(ensureAtRepositoryRoot)
cd site/
export PUPPETEER_SKIP_DOWNLOAD=1
pnpm install
pnpm dev -- --host
几个值得注意的细节:
ensureAtRepositoryRoot是 flake 提供的守卫,保证无论从哪里执行nix run .#site-dev-server,脚本都会先校验/回到仓库根目录,再进入site/;PUPPETEER_SKIP_DOWNLOAD=1跳过 Puppeteer 浏览器下载,在 CI 与离线环境下避免无谓的大文件拉取;pnpm dev -- --host对应 site/package.json 中的"dev": "NODE_ENV='development' astro dev",加上--host后开发服务器会监听网络接口,便于在容器/远程开发机中访问;- 该应用声明了
runtimeInputs = [python3, stdenv.cc, pkg-config](deps),用于满足 pnpm 依赖中可能的原生模块构建需求。
此外 site.nix 还定义了第二个应用 site-check(第 62–72 行),执行 npm_config_yes=true npx astro check,用于对 Astro 组件做类型检查;这与 package.json 里的 check 脚本(astro check)对应。如果不走 Nix,也可以直接在 site/ 目录下用 Node 22(package.json 的 engines.node 要求 22.x)运行 pnpm install && pnpm dev,这是 Nix 封装的等价替代。
site 同时是仓库 pnpm workspace 的一员:site.nix 中 pnpmWorkspaces = [ "site" ] 与根目录的 pnpm-workspace.yaml 配合,说明该目录复用整个仓库的统一依赖管理(如 catalog: 形式的版本引用,见 site/package.json 的 svelte: "catalog:svelte")。
生产构建:buildPnpmPackage 与 Vercel serverless 产物
site.nix 的 packages.site(第 19–43 行)用 buildPnpmPackage 声明了可复现的构建包,其关键行为是:
buildPhase = ''
export PUPPETEER_SKIP_DOWNLOAD=1;
export ASTRO_TELEMETRY_DISABLED=1;
export NODE_OPTIONS="--no-warnings";
pnpm --filter=site build
'';
installPhase = ''
mkdir -p $out
cp -r ./site/.vercel/output/* $out
'';
- 构建阶段用
pnpm --filter=site build在 workspace 内只构建 site 这一个包,并禁用 Astro 遥测; - 安装阶段直接把
site/.vercel/output拷贝为 Nix 产物$out,这说明站点最终是按 serverless 输出格式发布的。
这一点与 site/astro.config.ts 一致:README 中描述部署为 Netlify,而当前仓库的构建配置实际使用的是 @astrojs/vercel/serverless 适配器(astro.config.ts 第 26–28 行,imageService: true),site.nix 也据此以 .vercel/output 为交付物。astro.config.ts 中与架构相关的关键配置包括:
| 配置项 | 值 | 含义 |
|---|---|---|
site |
https://union.build |
站点 canonical URL,供 sitemap/SEO 使用 |
output |
"server" |
混合渲染模式:默认按需服务端渲染 |
experimental.clientPrerender |
true |
对服务端路由启用客户端预渲染优化 |
trailingSlash |
"ignore" |
忽略 URL 尾斜杠差异 |
image.domains |
cdn.contentful.com 等 |
Astro Image 允许处理的远程图源,其中两条正是 Contentful 的 CDN 域名 |
server |
{ port: Number(PORT) } |
开发端口默认 4321(由 PORT 环境变量覆盖) |
redirects |
/feed → /rss.xml,/logo → /union-logo.zip |
入口级重定向 |
prefetch |
prefetchAll: true, defaultStrategy: "viewport" |
视口内自动预取,提升页面跳转体验 |
vite.assetsInclude |
**/*.splinecode |
将 Spline 3D 工程文件纳入 Vite 资源管线(见下文) |
integrations 里注册了四个集成:astro-icon()(Iconify 图标)、tailwind()(显式指向 tailwind.config.ts 且 applyBaseStyles: false)、svelte()(Astro 与 Svelte 组件混用)、sitemap()(自动生成站点地图)。
Contentful 集成:客户端、查询与富文本渲染
站点内容并不写死在仓库里,而是运行时从 Contentful 拉取。相关实现集中在 site/src/lib/contentful/ 目录:
1. 客户端与环境变量 — site/src/lib/contentful/client.ts:
export const contentfulClient = createClient({
space: env.CONTENTFUL_SPACE_ID,
environment: env.CONTENTFUL_ENVIRONMENT,
host: import.meta.env.DEV ? "preview.contentful.com" : "cdn.contentful.com",
accessToken: import.meta.env.DEV ? env.CONTENTFUL_PREVIEW_TOKEN : env.CONTENTFUL_DELIVERY_TOKEN,
})
开发环境走 preview.contentful.com + Preview Token(可看到草稿),生产走 cdn.contentful.com + Delivery Token。所需的环境变量在 site/src/lib/constants/env.ts 中以 Object.freeze 集中声明:PUBLIC_CONTENTFUL_SPACE_ID、PUBLIC_CONTENTFUL_ENVIRONMENT、PUBLIC_CONTENTFUL_DELIVERY_TOKEN、PUBLIC_CONTENTFUL_PREVIEW_TOKEN 等,缺失时会在启动时直接 raise 报错,避免带病运行。
2. GraphQL 查询 — site/src/lib/contentful/queries.ts 定义了 blogPostsQuery(按 date_DESC 取最多 100 篇博客,返回 title/author/description/cover/content 等字段)与 blogPostQuery(按 slug 精确查询单篇,支持 preview 参数)。
3. 列表页数据流 — site/src/pages/blog/index.astro 中设置了 prerender = false(纯服务端渲染),通过 contentfulClient.getEntries({ content_type: "blog", order: "-fields.date", limit: 100 }) 拉取条目,并用 hidden 字段过滤——生产模式下隐藏标记为 hidden: true 的文章,开发模式则全部显示;同时响应头设置 cache-control: public, max-age=0, must-revalidate,让浏览器总是校验内容新鲜度。封面图 URL 还拼接了 Contentful 的 ?fit=fill&f=center&fm=avif&w=1344&h=706 参数,由其 CDN 按需转码为 AVIF。
4. 富文本渲染 — site/src/lib/contentful/render.ts 基于 @contentful/rich-text-html-renderer 的 documentToHtmlString 提供 renderRichText、renderTitle、renderTerms 等函数,通过自定义 renderMark/renderNode 把加粗、下划线、H1/H2、段落映射为带 Tailwind 类名的 HTML(例如加粗渲染为 <span class="text-accent-500">),保证 CMS 内容与站点视觉体系一致。依赖侧对应 site/package.json 中的 @contentful/rich-text-html-renderer、@contentful/rich-text-links、@contentful/live-preview 等包。
Markdown 渲染管线:KaTeX、mermaid 与 TOC
博客与文档类内容的 Markdown 处理统一收敛在 site/markdown.config.ts,并被 astro.config.ts 以 markdown: markdownConfiguration 引用:
gfm: true启用 GitHub 风格 Markdown;- remark 插件链:自定义
mermaid()、remark-math、remark-smartypants、remark-toc(自动在heading: "contents"处生成目录,前缀toc-); - rehype 插件链:
rehypeHeadingIds、rehype-slug、rehype-autolink-headings(behavior: "wrap")、rehype-katex与rehype-mathjax(数学公式双方案)。
其中 mermaid() 是一个自定义 remark 插件:遍历 AST 中的 code 节点,凡语言标记为 mermaid 的围栏代码块,就地转换为 <div class="mermaid">…</div>(内容经 escapeHTML),从而让 CMS 里的 Markdown 内容可以直接嵌入 mermaid 流程图。
Spline 3D 资产与页面结构
README 提到的 Spline 3D 模型在仓库中体现为两层:
- 资产层:site/src/spline/ 下存放 7 个
.splinecode工程文件,如union-polygon.splinecode、micro-cometbls-optimized.splinecode、micro-voyager-no-zoom.splinecode、lighthouse.splinecode等,文件名可直接对应 Union 的 CometBLS、Voyager 等核心概念;astro.config.ts通过vite.assetsInclude: ["**/*.splinecode"]让 Vite 能将这些文件作为静态资源处理,运行时由@splinetool/runtime/@splinetool/viewer(见package.json)加载渲染,组件侧由src/components/Spline.astro、Spline.svelte、SplineCover.astro、SplineResizing.astro封装。 - 页面层:site/src/pages/ 包含首页
index.astro、blog/(列表与[...slug].astro详情页)、docs/[...slug].ts、learn.astro、team.astro、roadmap.astro、ecosystem.astro、airdrop-terms-and-conditions.astro等;首页各分区由 site/src/components/sections/landing/ 下的ConsensusSection、ZkSection、ExecutionSection等组件构成,正好对应 Union 的共识验证(CometBLS)、零知识桥(ZK)与执行层三条主线;布局统一走src/layouts/中的BaseLayout/AnimationLayout,RSS 与 robots 由src/pages/rss.xml.ts、robots.txt.ts生成。
小结与文件索引
site/ 目录是一个典型的 “Nix 封装 + Astro 混合渲染 + Contentful 运行时取数” 的营销站点工程:本地开发只需 nix run .#site-dev-server,生产构建由 buildPnpmPackage 产出 Vercel serverless 格式的可复现 Nix 包,内容侧则通过带预览/生产双通道的 Contentful 客户端、GraphQL 查询与可定制 rich-text 渲染器保持 CMS 内容与站点样式的一致性。核心文件索引如下:
| 关注点 | 文件 |
|---|---|
| 原始说明文档 | site/README.md |
| Nix 开发/检查应用与构建包 | site/site.nix |
| Astro 配置(适配器、端口、资源) | site/astro.config.ts |
| 依赖与脚本(Node 22) | site/package.json |
| Markdown 管线 | site/markdown.config.ts |
| Contentful 客户端 | site/src/lib/contentful/client.ts |
| GraphQL 查询 | site/src/lib/contentful/queries.ts |
| 富文本渲染 | site/src/lib/contentful/render.ts |
| 环境变量声明 | site/src/lib/constants/env.ts |
| 博客列表页数据流 | site/src/pages/blog/index.astro |
| Spline 3D 资产 | site/src/spline/ |
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00