以太坊DApp前端部署指南Skill frontend-playbook

这个技能提供了部署以太坊去中心化应用(DApp)前端的全面指南,包括使用分支模式、IPFS部署、Vercel配置、ENS设置等完整生产流程,帮助开发者高效地将DApp推向生产环境。关键词:以太坊、DApp、前端部署、IPFS、ENS、区块链开发、智能合约、Web3。

DApp开发 0 次安装 0 次浏览 更新于 3/24/2026

name: 前端部署手册 description: 用于以太坊 dApps 的完整构建到生产流程。包括分支模式设置、IPFS 部署、Vercel 配置、ENS 子域名设置和完整生产检查清单。基于 Scaffold-ETH 2 但适用于任何以太坊前端项目。在部署任何 dApp 到生产时使用。

前端部署手册

你可能做错的地方

“我会使用 yarn chain。” 错误。yarn chain 给你一个空的本地链,没有协议、没有代币、没有状态。yarn fork --network base 给你一个真实的 Base 副本,包含 Uniswap、Aave、USDC、真实的鲸鱼余额——一切。总是使用分支模式。

“我部署到 IPFS 并且它工作了。” CID 是否改变了?如果没有,你部署了过时的输出。路由是否工作?没有 trailingSlash: true,除了 / 外的每个路由都返回 404。你检查了 OG 图像吗?没有 NEXT_PUBLIC_PRODUCTION_URL,它指向 localhost:3000

“我会手动设置项目。” 不要。npx create-eth@latest 处理一切——Foundry、Next.js、RainbowKit、脚手架钩子。永远不要运行 forge init 或从头创建 Next.js 项目。


分支模式设置

为什么分支,不是链

yarn chain (错误)              yarn fork --network base (正确)
└─ 空的本地链            └─ 真实的 Base 主网分支
└─ 没有协议                 └─ Uniswap、Aave 等可用
└─ 没有代币                    └─ 真实的 USDC、WETH 存在
└─ 在隔离中测试         └─ 针对真实状态测试

设置

npx create-eth@latest          # 选择:foundry、目标链、名称
cd <项目名称>
yarn install
yarn fork --network base       # 终端 1:真实 Base 的分支
yarn deploy                    # 终端 2:部署合约到分支
yarn start                     # 终端 3:Next.js 前端

关键:链 ID 陷阱

使用分支模式时,前端目标网络必须是 chains.foundry(链 ID 31337),不是你要分支的链。

分支在本地 Anvil 上运行,链 ID 31337。即使你在分支 Base:

// scaffold.config.ts 在开发期间
targetNetworks: [chains.foundry],  // ✅ 不是 chains.base!

只有当部署合约到真实网络时,才切换到 chains.base

启用区块挖掘

# 在新终端中——对于时间依赖的逻辑是必需的
cast rpc anvil_setIntervalMining 1

没有这个,block.timestamp 保持冻结。任何使用时间戳的合约逻辑(截止时间、过期、归属)将无声地中断。

使其永久化 编辑 packages/foundry/package.json 在分支脚本中添加 --block-time 1


部署到 IPFS(推荐)

IPFS 是 SE2 推荐的部署路径。完全避免 Vercel 的内存限制。产生一个完全去中心化的静态站点。

完整构建命令

cd packages/nextjs
rm -rf .next out  # 总是先清理

NEXT_PUBLIC_PRODUCTION_URL="https://yourapp.yourname.eth.link" \
  NODE_OPTIONS="--require ./polyfill-localstorage.cjs" \
  NEXT_PUBLIC_IPFS_BUILD=true \
  NEXT_PUBLIC_IGNORE_BUILD_ERROR=true \
  yarn build

# 上传到 BuidlGuidl IPFS
yarn bgipfs upload out
# 保存 CID!

Node 25+ localStorage 填充(必需)

Node.js 25+ 自带一个内置的 localStorage 对象,缺少标准 WebStorage API 方法(getItemsetItem)。这会破坏 next-themes、RainbowKit 和任何在静态页面生成期间调用 localStorage.getItem() 的库。

你会看到的错误:

TypeError: localStorage.getItem is not a function
Error occurred prerendering page "/_not-found"

修复:packages/nextjs/ 创建 polyfill-localstorage.cjs

if (typeof globalThis.localStorage !== "undefined" &&
    typeof globalThis.localStorage.getItem !== "function") {
  const store = new Map();
  globalThis.localStorage = {
    getItem: (key) => store.get(key) ?? null,
    setItem: (key, value) => store.set(key, String(value)),
    removeItem: (key) => store.delete(key),
    clear: () => store.clear(),
    key: (index) => [...store.keys()][index] ?? null,
    get length() { return store.size; },
  };
}

为什么使用 --require 而不是 instrumentation.ts Next.js 为预渲染生成一个单独的构建工作进程。--require 注入到每个 Node 进程(包括工作进程)。next.config.ts 填充只运行在主进程。instrumentation.ts 不在构建工作进程中运行。只有 --require 有效。

IPFS 路由——为什么路由中断

IPFS 网关提供静态文件。没有服务器处理路由。三件事必须为真:

1. output: "export" 在 next.config.ts ——生成静态 HTML 文件。

2. trailingSlash: true(关键) ——这是路由中断的第一原因:

  • trailingSlash: false(默认)→ 生成 debug.html
  • trailingSlash: true → 生成 debug/index.html
  • IPFS 网关自动解析目录到 index.html,但不是裸文件名
  • 没有尾部斜杠:/debug → 404 ❌
  • 有尾部斜杠:/debugdebug/debug/index.html

3. 页面必须在静态预渲染中幸存 ——任何在 yarn build 期间崩溃的页面(导入时的浏览器 API、localStorage)被静默跳过→ IPFS 上的 404。

完整的 IPFS 安全 next.config.ts 模式:

const isIpfs = process.env.NEXT_PUBLIC_IPFS_BUILD === "true";
if (isIpfs) {
  nextConfig.output = "export";
  nextConfig.trailingSlash = true;
  nextConfig.images = { unoptimized: true };
}

SE2 的区块浏览器页面 在导入时使用 localStorage 并在静态导出期间崩溃。如果不需,重命名 app/blockexplorerapp/_blockexplorer-disabled

过时构建检测

第一号 IPFS 陷阱: 你编辑代码,然后部署旧构建。

# 任何代码更改后强制:
rm -rf .next out                     # 1. 删除旧工件
# ... 运行完整构建命令 ...     # 2. 从头重新构建
grep -l "YOUR_STRING" out/_next/static/chunks/app/*.js  # 3. 验证更改存在

# 时间戳检查:
stat -f '%Sm' app/page.tsx           # 源修改时间
stat -f '%Sm' out/                   # 构建输出时间
# 源比 out/ 新 = 过时构建。先重建!

CID 是证据: 如果部署后 IPFS CID 没有改变,你部署了相同的内容。真正的代码更改总是产生新的 CID。

部署后验证路由

ls out/*/index.html                  # 每个路由有一个目录 + index.html
curl -s -o /dev/null -w "%{http_code}" -L "https://GATEWAY/ipfs/CID/debug/"
# 应该返回 200,不是 404

部署到 Vercel(替代方案)

SE2 是一个单体仓库——Vercel 需要特殊配置。

配置

  1. 根目录: packages/nextjs
  2. 安装命令: cd ../.. && yarn install
  3. 构建命令: 留默认(next build
  4. 输出目录: 留默认(.next
# 通过 API:
curl -X PATCH "https://api.vercel.com/v9/projects/PROJECT_ID" \
  -H "Authorization: Bearer $VERCEL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rootDirectory": "packages/nextjs", "installCommand": "cd ../.. && yarn install"}'

常见失败

错误 原因 修复
“未检测到 Next.js 版本” 根目录未设置 设置为 packages/nextjs
“cd packages/nextjs: 没有这样的文件” 构建命令有 cd 清除它——根目录处理这个
OOM / 退出码 129 SE2 单体仓库超过 8GB 使用 IPFS 替代,或 vercel --prebuilt

决策树

想部署 SE2?
├─ IPFS(推荐)→ yarn ipfs / 手动构建 + 上传
│   └─ 完全去中心化,无内存限制,与 ENS 配合
├─ Vercel → 设置根目录 + 安装命令
│   └─ 快速 CDN,但中心化。大型项目可能 OOM
└─ vercel --prebuilt → 本地构建,推送工件到 Vercel
    └─ 最佳两者:本地构建能力 + Vercel CDN

ENS 子域名设置

两个主网交易,将 ENS 子域名指向你的 IPFS 部署。

交易 1:创建子域名(仅新应用)

  1. 打开 https://app.ens.domains/yourname.eth
  2. 转到“子名”标签→“新子名”
  3. 输入标签(如 myapp)→ 下一步→跳过个人资料→打开钱包→确认
  4. 如果 gas 卡住:切换 MetaMask 到以太坊→活动标签→“加速”

交易 2:设置 IPFS 内容哈希

  1. 导航到 https://app.ens.domains/myapp.yourname.eth
  2. “记录”标签→“编辑记录”→“其他”标签
  3. 粘贴到内容哈希字段:ipfs://<CID>
  4. 保存→打开钱包→在 MetaMask 中确认

对于现有应用的更新:跳过交易 1,只做交易 2。

验证

# 1. 链上内容哈希匹配
RESOLVER=$(cast call 0x00000000000C2e074eC69A0dFb2997BA6C7d2e1e \
  "resolver(bytes32)(address)" $(cast namehash myapp.yourname.eth) \
  --rpc-url https://eth.llamarpc.com)
cast call $RESOLVER "contenthash(bytes32)(bytes)" \
  $(cast namehash myapp.yourname.eth) --rpc-url https://eth.llamarpc.com

# 2. 网关响应(缓存可能需 5-15 分钟)
curl -s -o /dev/null -w "%{http_code}" -L "https://myapp.yourname.eth.link"

# 3. OG 元数据正确(不是 localhost)
curl -s -L "https://myapp.yourname.eth.link" | grep 'og:image'

使用 .eth.link 不是 .eth.limo —— .eth.link 在移动端工作更好。


进入生产——完整检查清单

当用户说“发布它”,按照这个精确序列。

步骤 1:最终代码审查 🤖

  • 所有反馈已纳入
  • 没有重复 h1,没有原始地址,没有共享 isLoading
  • scaffold.config.tsrpcOverridespollingInterval: 3000

步骤 2:选择域名 👤

问:“你想要什么子域名?例如 myapp.yourname.eth → `myapp.yourname.eth.link”"

步骤 3:生成 OG 图像 + 修复元数据 🤖

  • 创建 1200×630 PNG(public/thumbnail.png)——不是库存 SE2 缩略图
  • 设置 NEXT_PUBLIC_PRODUCTION_URL 为实时域名
  • 验证 og:image 将解析为绝对生产 URL

步骤 4:清洁构建 + IPFS 部署 🤖

cd packages/nextjs && rm -rf .next out
NEXT_PUBLIC_PRODUCTION_URL="https://myapp.yourname.eth.link" \
  NODE_OPTIONS="--require ./polyfill-localstorage.cjs" \
  NEXT_PUBLIC_IPFS_BUILD=true NEXT_PUBLIC_IGNORE_BUILD_ERROR=true \
  yarn build

# 上传前验证:
ls out/*/index.html                        # 路由存在
grep 'og:image' out/index.html             # 不是 localhost
stat -f '%Sm' app/page.tsx                 # 源比 out/ 旧
stat -f '%Sm' out/

yarn bgipfs upload out                     # 保存 CID

步骤 5:分享供批准 👤

发送:“构建准备审查:https://community.bgipfs.com/ipfs/<CID> 在接触 ENS 前等待批准。

步骤 6:设置 ENS 🤖

创建子域名(如果新)+ 设置 IPFS 内容哈希。两个主网交易。

步骤 7:验证 🤖

  • 内容哈希在链上匹配
  • .eth.link 网关响应 200
  • OG 图像正确加载
  • 路由工作(/debug/ 等)

步骤 8:报告 👤

“实时在 https://myapp.yourname.eth.link —— ENS 内容哈希在链上确认,元数据设置完成。”


构建验证过程

构建不是在代码编译时完成。它在像真实用户一样测试后才完成。

阶段 1:代码质量保证(自动化)

  • 扫描 .tsx 文件中的原始地址字符串(应使用 <Address/>
  • 扫描多个按钮共享的 isLoading 状态
  • 扫描交易按钮上缺少的 disabled 属性
  • 验证 RPC 配置和轮询间隔
  • 验证 OG 元数据有绝对 URL
  • 验证任何文件中没有公共 RPC

阶段 2:智能合约测试

forge test                    # 所有测试通过
forge test --fuzz-runs 10000  # 模糊测试

测试边缘情况:零金额、最大金额、未经授权的调用者、重入尝试。

阶段 3:浏览器测试(真正的测试)

打开应用并进行完整演练:

  1. 加载应用 ——是否正确渲染?
  2. 检查页面标题 ——是否正确,不是“Scaffold-ETH 2”?
  3. 连接钱包 ——连接流程是否工作?
  4. 错误网络 ——在错误链上连接,验证“切换到 Base”出现
  5. 切换网络 ——点击切换按钮,验证工作
  6. 批准流程 ——验证批准按钮显示,点击,等待交易,验证操作按钮出现
  7. 主要操作 ——点击主要操作,验证加载器,等待交易,验证状态更新
  8. 错误处理 ——拒绝钱包中的交易,验证 UI 恢复
  9. 地址显示 ——所有地址显示 ENS/blockies,不是原始十六进制?
  10. 分享 URL ——检查 OG 展开(图像、标题、描述)

阶段 4:质量保证子代理(复杂构建)

对于大型项目,生成一个带有新上下文的子代理。给它仓库路径和部署 URL。它根据 UX 规则读取所有代码,打开浏览器,独立点击,并报告问题。


不要做这些

  • yarn chain ——使用 yarn fork --network <链>
  • forge init ——使用 npx create-eth@latest
  • ❌ 手动 Next.js 设置 —— SE2 处理它
  • ❌ 手动钱包连接 —— SE2 预配置了 RainbowKit
  • ❌ 编辑 deployedContracts.ts ——它是自动生成的
  • ❌ 在 scaffold.config.ts 中硬编码 API 密钥 ——使用 .env.local
  • ❌ 在生产中使用 mainnet.base.org ——使用 Alchemy 或类似

资源