Разработка фронтенда dApp на Nuxt.js
Vue-экосистема в Web3 проигрывает React по количеству готовых библиотек, но не по возможностям. Основная сложность — большинство Web3 tooling написано React-first: wagmi, ConnectKit, RainbowKit работают только с React. Для Nuxt нужен либо другой стек, либо обёртки. Разберём, что реально работает в 2024–2025.
Web3-стек для Nuxt 3
Подключение кошельков
@wagmi/core + @web3modal/wagmi — wagmi core работает без React, Vue-обёртка @wagmi/vue появилась в wagmi v2. Web3Modal поддерживает Vue через отдельный пакет @web3modal/wagmi/vue. Это наиболее зрелый вариант:
// plugins/wagmi.client.ts
import { createWeb3Modal } from '@web3modal/wagmi/vue'
import { createConfig, http } from '@wagmi/vue'
import { mainnet, arbitrum } from '@wagmi/vue/chains'
import { walletConnect, injected } from '@wagmi/vue/connectors'
export default defineNuxtPlugin(() => {
const config = createConfig({
chains: [mainnet, arbitrum],
connectors: [
walletConnect({ projectId: useRuntimeConfig().public.wcProjectId }),
injected(),
],
transports: {
[mainnet.id]: http(),
[arbitrum.id]: http(),
},
})
createWeb3Modal({
wagmiConfig: config,
projectId: useRuntimeConfig().public.wcProjectId,
})
})
Плагин помечен .client.ts — важно, потому что Web3 библиотеки несовместимы с SSR: обращаются к window, localStorage, ethereum инжектору.
viem для чтения данных с блокчейна — работает в любом окружении:
// composables/useContract.ts
import { createPublicClient, http, parseAbi } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({
chain: mainnet,
transport: http(),
})
export const useTokenBalance = (address: Ref<`0x${string}` | undefined>) => {
return useAsyncData(
`balance-${address.value}`,
() => address.value
? client.readContract({
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
abi: parseAbi(['function balanceOf(address) view returns (uint256)']),
functionName: 'balanceOf',
args: [address.value],
})
: Promise.resolve(0n),
{ watch: [address] }
)
}
SSR-специфика
Nuxt 3 по умолчанию использует SSR, и это создаёт трения с Web3:
Проблема hydration — window.ethereum существует только в браузере. Любой компонент, который читает состояние кошелька, должен быть либо в <ClientOnly>, либо проверять process.client:
<template>
<ClientOnly>
<WalletButton />
<template #fallback>
<button disabled>Загрузка...</button>
</template>
</ClientOnly>
</template>
Nuxt plugin с ssr: false — альтернатива <ClientOnly>:
// nuxt.config.ts
export default defineNuxtConfig({
plugins: [
{ src: '~/plugins/wagmi.client.ts', mode: 'client' }
]
})
useNuxtApp().$wagmi — после инициализации в плагине wagmi config доступен через provide/inject паттерн Nuxt.
Composables для Web3
Разница между React hooks и Vue composables в контексте Web3: Vue реактивность через ref/computed работает лучше для derived state, который меняется при смене аккаунта или сети.
// composables/useWalletState.ts
export const useWalletState = () => {
const { address, isConnected, chain } = useAccount()
const shortAddress = computed(() =>
address.value
? `${address.value.slice(0, 6)}...${address.value.slice(-4)}`
: null
)
const isWrongNetwork = computed(() =>
isConnected.value && chain.value?.id !== mainnet.id
)
return { address, isConnected, shortAddress, isWrongNetwork }
}
Транзакции и состояние
Управление статусом транзакции — типичный pain point. wagmi v2 предоставляет useWriteContract + useWaitForTransactionReceipt:
const { writeContract, data: hash } = useWriteContract()
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash })
Паттерн для UI: показываем три состояния — "подпишите в кошельке", "транзакция отправлена (hash)", "подтверждено". Nuxt + Pinia удобно держат этот стейт глобально для toast-уведомлений.
Типичный стек проекта
| Задача | Библиотека |
|---|---|
| Framework | Nuxt 3 (Vue 3 + Vite) |
| Wallet connect | @web3modal/wagmi + @wagmi/vue |
| On-chain reads | viem PublicClient |
| State | Pinia |
| Styling | Tailwind CSS 3 / UnoCSS |
| Data fetching | useAsyncData + TanStack Query |
Срок разработки фронтенда dApp на Nuxt — 1–2 недели в зависимости от сложности: базовое подключение кошелька и чтение контрактов занимает 3–4 дня, полноценный dApp с несколькими экранами, транзакциями и обработкой ошибок — неделю-полторы.







