Next.js DApp 跨场景架构总结NFT 市场、DeFi 仪表盘与 DAO 治理的前端模式提取一、引言Web3 前端开发的碎片化程度比传统 Web 严重得多。同一个团队维护三个 DApp——一个 NFT 交易市场、一个 DeFi 数据仪表盘、一个 DAO 治理面板——分别选用了三种数据获取策略、两套状态管理方案、四类钱包连接实现的结果就是上下文切换成本高得无法忍受。但深挖下去这三个场景在前端架构层面共享着同一套骨架链上数据同步层、用户钱包会话层、交易生命周期管理层、链下索引查询层。差异只体现在 UI 的呈现方式和特定业务的数据转换逻辑上。本文从实际踩过的架构坑出发提取出一套跨场景的 Next.js App Router 架构模式。目标是做到新开一个 DApp 时80% 的前端基础设施可以直接复用剩下 20% 是场景特定的页面组件和数据适配器。选取 Next.js 而非纯 React SPA 的原因在于服务端渲染对 SEO 的增益、App Router 的文件系统路由对页面组织的心智负担降低、以及 Server Components 天然适合处理不依赖用户状态的链下数据查询。二、跨场景架构分层架构的核心是将三个场景的共同部分下沉为基础设施层差异部分上浮为场景适配层。基础设施层蓝色是三个场景共同的底座。钱包会话封装了连接、切换链、签名、SIWESign-In With Ethereum的完整状态机。多 RPC 负载均衡器处理节点故障转移和速率限制。缓存层以 React Query 为主进行客户端状态管理Redis 处理跨用户共享的链下索引数据。合约 ABI 注册中心集中管理所有交互合约的接口定义和地址映射。通用业务层横跨基础设施和场景之间提供场景无关的业务能力。TransactionManager封装了发送交易、等待确认、解析回执、处理 revert 的全流程是所有 DApp 都必须有但写法最不统一的部分。EventSubscriber监听链上事件并驱动 UI 更新——NFT 市场的 Transfer 事件更新所有权、DeFi 的 Swap 事件刷新余额、DAO 的 ProposalCreated 事件生成新卡片。三、TransactionManager 实现与场景适配以下是跨场景通用的TransactionManager核心实现。设计要点将交易生命周期拆分为五个离散状态每个状态对应明确的 UI 展示策略支持场景特定的交易预处理和后处理钩子内置 gas 估算失败的重试与 fallback 逻辑。// lib/transactions/TransactionManager.ts import { type Address, type Hash, type TransactionReceipt } from viem; import { useWallet } from /providers/WalletProvider; import { useMultiRPC } from /providers/MultiRPCProvider; /** * 交易状态枚举 * 设计决策使用 discriminated union 而非 string 状态标记—— * TypeScript 可以对每个状态做类型窄化编译期就能发现漏处理的状态分支 */ export type TxStatus | { type: idle } | { type: simulating } | { type: pending; hash: Hash } | { type: confirming; hash: Hash; confirmations: number } | { type: confirmed; receipt: TransactionReceipt } | { type: failed; error: string; hash?: Hash }; /** * 场景钩子每个场景可以注入自己的预处理和后处理逻辑。 * 设计决策使用函数式注入而非 class 继承—— * 组合优于继承场景钩子之间保持无状态可以被随意组合和测试 */ interface SceneHooks { /** 交易发送前的预处理用于场景特定的参数校验和数据准备 */ beforeSend?: () Promisevoid; /** 确认成功后的后处理用于更新 UI、刷新缓存、触发通知等 */ onConfirmed?: (receipt: TransactionReceipt) Promisevoid; /** 失败后的清理逻辑用于回滚乐观更新等 */ onFailed?: (error: string) void; } export function useTransactionManager() { const { wallet, signer } useWallet(); const { getProvider } useMultiRPC(); const [status, setStatus] useStateTxStatus({ type: idle }); const sendTransaction useCallback(async ( txFn: () PromiseHash, hooks?: SceneHooks ) { try { await hooks?.beforeSend?.(); setStatus({ type: simulating }); const hash await txFn(); setStatus({ type: pending, hash }); // 等待交易确认使用轮询而非 websocket 订阅 // 因为部分 RPC 节点对 websocket 的支持不稳定 const receipt await waitForConfirmation(hash); setStatus({ type: confirmed, receipt }); await hooks?.onConfirmed?.(receipt); return receipt; } catch (err: any) { const errorMsg err?.message ?? Unknown transaction error; setStatus({ type: failed, error: errorMsg }); hooks?.onFailed?.(errorMsg); throw err; } }, []); return { status, sendTransaction }; } async function waitForConfirmation( hash: Hash, maxAttempts 60, interval 2000 ): PromiseTransactionReceipt { /** * 确认等待策略说明 * 选择固定间隔轮询而非指数退避因为交易确认时间主要取决于区块时间 * 而非网络拥塞程度固定间隔更可预测。 * maxAttempts * interval 120s超过这个时间视为超时 * 实际生产中 95% 的交易在 5 个区块内确认。 */ for (let i 0; i maxAttempts; i) { await new Promise((r) setTimeout(r, interval)); // 实际实现中通过 getTransactionReceipt 查询 } throw new Error(交易确认超时); }场景侧的使用极为简洁。NFT 铸造场景只需注入铸造前的元数据上传钩子和铸造后的缓存清理钩子// app/nft/mint/page.tsx - NFT 铸造页面 const { status, sendTransaction } useTransactionManager(); const handleMint async () { await sendTransaction( () nftContract.mint(metadataHash), { beforeSend: async () { // 先上传元数据到 IPFS再发交易 await uploadMetadata(metadataHash); }, onConfirmed: async (receipt) { // 铸造成功刷新 NFT 列表缓存 await queryClient.invalidateQueries({ queryKey: [nfts] }); }, } ); };DeFi 仪表盘的交易钩子聚焦于资产数据的实时更新DAO 治理页面的钩子额外处理投票委托状态的一致性。三个场景共享相同的状态流转和错误处理逻辑差异仅在于钩子函数的实现内容。四、边界与架构约束Server Components 与链上数据的冲突。链上数据本质上是依赖用户钱包连接的状态——你无法在服务端渲染时获取当前用户的持仓因为服务端没有用户的私钥。实践中将 Server Components 的职责限定为渲染纯静态内容布局、SEO 元数据、白皮书等所有链上数据查询都下沉到 Client Components 的 React Query hooks 中。钱包多连接状态的复杂度。同时支持 MetaMask、WalletConnect、Coinbase Wallet 三种连接方式时状态管理的复杂度不是线性的——每种钱包对链切换、断开、重连的行为语义不同。方案是在useWallet内部用 adapter 模式统一接口但调试时需要三组测试设备。缓存失效的时机判断。React Query 的 stale 时间设置为多少合适对于 NFT 元数据几乎不变可以设 5 分钟对于 DeFi 价格数据必须 10 秒。这里不是技术难题而是配置敏感性问题——一个过长的 stale 时间可能让用户看到错误的价格信息一个过短的时间导致 RPC 节点被频繁查询超出速率限制。跨链数据的统一抽象。同时支持 Ethereum、Polygon、Arbitrum 三条链时每条链的 RPC 端点、区块时间、gas 估算策略、事件查询 API 都不相同。MultiRPCProvider 需要在 viem 的 transport 层做适配但以太坊的eth_getLogs和 Arbitrum 的 nitro 预编译在行为上有细微差异需要在集成测试中逐个覆盖。五、总结三个 DApp 场景的架构收敛验证了一个观点Web3 前端的基础设施层是可以跨场景稳定的变化只发生在数据转换逻辑和 UI 呈现层。TransactionManagerEventSubscriberWalletContextMultiRPCProvider这四个组件构成了可复用的基础设施骨架。部署新 DApp 时的标准流程是初始化 Next.js App Router 项目 → 引入基础设施包 → 注册合约 ABI → 编写 2-4 个场景适配器 → 开工写页面组件。前三个步骤不超过半天后面的时间全部花在业务逻辑和 UI 细节上。这种效率的提升来自于架构层面做了正确的分层决策。