欢迎光临
我们一直在努力

Next.js DApp 跨场景架构总结:NFT 市场、DeFi 仪表盘与 DAO 治理的前端模式提取

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 天然适合处理不依赖用户状态的链下数据查询。

二、跨场景架构分层

架构的核心是将三个场景的共同部分下沉为基础设施层,差异部分上浮为场景适配层。

基础设施层(蓝色)是三个场景共同的底座。钱包会话封装了连接、切换链、签名、SIWE(Sign-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?: () => Promise<void>;
/** 确认成功后的后处理,用于更新 UI、刷新缓存、触发通知等 */
onConfirmed?: (receipt: TransactionReceipt) => Promise<void>;
/** 失败后的清理逻辑,用于回滚乐观更新等 */
onFailed?: (error: string) => void;
}

export function useTransactionManager() {
const { wallet, signer } = useWallet();
const { getProvider } = useMultiRPC();

const [status, setStatus] = useState<TxStatus>({ type: 'idle' });

const sendTransaction = useCallback(async (
txFn: () => Promise<Hash>,
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
): Promise<TransactionReceipt> {
/**
* 确认等待策略说明:
* 选择固定间隔轮询而非指数退避,因为交易确认时间主要取决于区块时间
* (而非网络拥塞程度),固定间隔更可预测。
* 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 呈现层。TransactionManager + EventSubscriber + WalletContext + MultiRPCProvider 这四个组件构成了可复用的基础设施骨架。

部署新 DApp 时的标准流程是:初始化 Next.js App Router 项目 → 引入基础设施包 → 注册合约 ABI → 编写 2-4 个场景适配器 → 开工写页面组件。前三个步骤不超过半天,后面的时间全部花在业务逻辑和 UI 细节上。这种效率的提升来自于架构层面做了正确的分层决策。

赞(0)
未经允许不得转载:171主机测评 » Next.js DApp 跨场景架构总结:NFT 市场、DeFi 仪表盘与 DAO 治理的前端模式提取
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址