Разработка фронтенда dApp на Next.js: Web3-интеграция и SSR

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1305 услуг
Разработка фронтенда dApp на Next.js: Web3-интеграция и SSR
Средний
~1-2 недели
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1374
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1256
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    965
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1208
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    667
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    954

Представьте: ваш DeFi-дашборд загружается 5 секунд из-за 20 отдельных RPC-запросов. Каждый запрос к блокчейну — это задержка. Мы решаем это через multicall, объединяя все вызовы в один. Итог — загрузка падает до 0.5 секунды. Это типичная задача, которую мы решаем каждый день для клиентов. За 5 лет работы над 50+ dApp мы выработали подход, который гарантирует быструю загрузку при сложной Web3-логике. В этой статье — конкретные решения по интеграции, оптимизации и разделению компонентов.

Next.js для Web3 — это прежде всего решение конкретного противоречия: блокчейн данные требуют client-side execution (wallet connection, подпись транзакций), а SEO и начальная загрузка требуют серверного рендеринга. Неправильная граница между server и client компонентами ломает либо wallet integration, либо производительность. Next.js документация рекомендует использовать Server Components для данных, не требующих интерактивности.

Как правильно разделить Server и Client компоненты в dApp?

Ошибка на этом этапе приводит к hydration mismatch или падению приложения. Рассмотрим решение.

Проблема гидрации с wagmi/viem

wagmi 2.x использует localStorage и window.ethereum — оба объекта недоступны на сервере. Наивный импорт useAccount в Server Component вызывает ошибку. Ещё хуже — hydration mismatch: сервер рендерит «не подключён», клиент после гидрации показывает «подключён к MetaMask», и React выдаёт warning или ломает UI.

Правильная структура:

// app/providers.tsx — CLIENT компонент, оборачивает всё приложение
'use client';

import { WagmiProvider, createConfig, http } from 'wagmi';
import { mainnet, base, arbitrum } from 'wagmi/chains';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ConnectKitProvider } from 'connectkit';

const config = createConfig({
  chains: [mainnet, base, arbitrum],
  transports: {
    [mainnet.id]: http(process.env.NEXT_PUBLIC_RPC_MAINNET),
    [base.id]: http(process.env.NEXT_PUBLIC_RPC_BASE),
    [arbitrum.id]: http(process.env.NEXT_PUBLIC_RPC_ARBITRUM),
  },
});

const queryClient = new QueryClient();

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <ConnectKitProvider>{children}</ConnectKitProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}
// app/layout.tsx — SERVER компонент, импортирует Providers
import { Providers } from './providers';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Статичный контент (navbar, footer, landing text) — Server Components. Кошелёк, балансы, транзакционные кнопки — Client Components с 'use client'.

SSR для on-chain данных

Публичные данные блокчейна (TVL протокола, список токенов, цены) можно загружать на сервере. Server Components в Next.js (App Router) + fetch с caching:

// app/protocol/page.tsx — Server Component
async function getProtocolStats() {
  const client = createPublicClient({
    chain: mainnet,
    transport: http(process.env.RPC_URL), // приватная переменная, не NEXT_PUBLIC_
  });

  const [tvl, totalUsers] = await Promise.all([
    client.readContract({ address: PROTOCOL, abi, functionName: 'getTVL' }),
    client.readContract({ address: PROTOCOL, abi, functionName: 'userCount' }),
  ]);

  return { tvl, totalUsers };
}

export default async function ProtocolPage() {
  const stats = await getProtocolStats(); // выполняется на сервере
  return <StatsDisplay stats={stats} />;
}

fetchв Next.js кэшируется по умолчанию. Для on-chain данных через viem нужно явное управление: revalidate: 60 в export const revalidate или ручная инвалидация через Route Handlers.

Почему multicall ускоряет загрузку в 10 раз?

Управление состоянием транзакций

Lifecycle транзакции в UI: idle → preparing → signing → pending → confirming → success/error. Каждое состояние требует отдельного UI feedback. wagmi предоставляет хуки для каждого этапа:

function TransactionButton({ tokenId }: { tokenId: bigint }) {
  const { writeContract, data: hash, isPending, error } = useWriteContract();
  const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash });

  if (isPending) return <Button disabled>Подтвердите в кошельке...</Button>;
  if (isConfirming) return <Button disabled>Ожидание подтверждения ({hash?.slice(0, 8)}...)</Button>;
  if (isSuccess) return <Button variant="success">Готово ✓</Button>;

  return (
    <Button
      onClick={() => writeContract({ address: CONTRACT, abi, functionName: 'mint', args: [tokenId] })}
    >
      Минт
    </Button>
  );
}

Оптимистичные обновления

Для операций с предсказуемым результатом (like, follow, simple toggle) — оптимистичный UI через @tanstack/react-query useMutation с onMutate/onError/onSettled. Пользователь видит изменение немедленно, откат происходит только при ошибке.

Multicall и batch запросы

Для dashboard с множеством on-chain данных — никаких параллельных одиночных RPC вызовов. Multicall3 собирает все запросы в один:

const results = await publicClient.multicall({
  contracts: tokenIds.map(id => ({
    address: NFT_CONTRACT,
    abi: erc721Abi,
    functionName: 'tokenURI',
    args: [id],
  })),
});

viem поддерживает multicall нативно. Для 100 токенов — 1 RPC запрос вместо 100. Это разница между 2 секундами и 200 миллисекундами загрузки dashboard. Server Components для статичных данных рендерятся в 3 раза быстрее Client Components, так как не включают JavaScript бандл для гидрации.

Алгоритм разработки dApp: от архитектуры до деплоя

  1. Анализ смарт-контракта и дизайн-макетов, определение точек интеграции.
  2. Проектирование компонентной архитектуры: Server vs Client, layout, роутинг.
  3. Реализация провайдера Web3 (wagmi + ConnectKit/RainbowKit).
  4. Написание серверных компонент для публичных on-chain данных с кэшированием.
  5. Реализация клиентских компонент: подключение кошелька, балансы, транзакции.
  6. Оптимизация запросов через multicall и React Query.
  7. Тестирование: unit (Vitest), e2e (Playwright), покрытие edge-кейсов транзакций.
  8. Деплой на Vercel или Docker, настройка CI/CD.

Важные детали окружения

.env.local для локальной разработки, .env.production для прода. Переменные без NEXT_PUBLIC_ не попадают в клиентский бандл — RPC URLs с API ключами должны быть без префикса (использовать только в Server Components или Route Handlers).

RPC провайдеры: Alchemy, Infura, QuickNode. Для продакшена — несколько провайдеров с fallback через wagmi fallback transport. Публичные RPC (вроде https://eth.llamarpc.com) имеют rate limits — не использовать в production без fallback. Средняя экономия на RPC-запросах существенна благодаря multicall и кэшированию.

Сравнение: Server Components vs Client Components для типичных блоков dApp

Блок Рекомендуемый тип Причина
Navbar, Footer, SEO-текст Server Component Нет зависимости от кошелька, быстрая загрузка
Кнопка подключения кошелька Client Component Требует window.ethereum
Баланс токенов Client Component Динамические данные, подписка
TVL протокола, список токенов Server Component (с кэшем) Публичные данные, улучшает SEO
Форма транзакции Client Component Взаимодействие с кошельком

Что входит в работу

  • Архитектура: выбор стратегии рендеринга (SSR/SSG/CSR), разбивка на Server/Client Components.
  • Интеграция кошельков: MetaMask, WalletConnect, Coinbase Wallet, Ledger.
  • Работа со смарт-контрактами: чтение и запись через viem, обработка lifecycle транзакций.
  • Оптимизация: multicall, кэширование, gas estimation, fallback RPC.
  • Тестирование: unit (Vitest), e2e (Playwright), покрытие edge-кейсов.
  • Документация: README, комментарии в коде, схема компонентов.
  • Поддержка: настройка CI/CD (Vercel/Docker), мониторинг через Tenderly.

Ориентиры по срокам

Этап Длительность
Базовый фронтенд (wallet + транзакции) от 1 недели
+ SSR + оптимизация + полный lifecycle от 2 недель
+ Кастомные UI-компоненты, анимации от 3 недель

Сроки уточняются после анализа вашего смарт-контракта и дизайн-макетов. Гарантируем прозрачность на каждом этапе. Свяжитесь с нами для консультации — обсудим ваш проект и предложим оптимальное решение.

Стек

Next.js (App Router), wagmi 2.x, viem 2.x, ConnectKit или RainbowKit, @tanstack/react-query 5.x, TypeScript, Tailwind CSS. Тестирование: Vitest + Playwright для e2e. Деплой: Vercel или Docker.

Закажите разработку прямо сейчас — получите консультацию по вашему проекту. Оценим объём и сложность, предложим оптимальное решение.

Вступление

Пользователь нажимает «Connect Wallet» — MetaMask открывается, подтверждает — и ничего не происходит. Или хуже: транзакция ушла, но UI завис на «pending» навечно, потому что event listener отвалился при переключении сети. Типичная ситуация: контракт задеплоен на Arbitrum, а кошелёк подключен к Ethereum Mainnet — интерфейс молча показывает нулевые балансы, хотя RPC отвечает. Web3-фронтенд это не React + API вызовы. Это работа с кошельками, нодами, реорганизациями блокчейна и состоянием, которое не принадлежит вашему серверу.

Что входит в полный спектр Web3-фронтенд разработки

Мы проектируем и реализуем интерфейсы для dApp на всех этапах: от подключения кошельков до сложной транзакционной логики с мультичейн-маршрутизацией. В работу входит:

  • Архитектура UI с учётом EIP-1193 (ethereum provider) и EIP-6963 (multi‑injected wallet)
  • Интеграция RainbowKit/ConnectKit для WalletConnect v2
  • Чтение данных через Multicall3 с настройкой кеширования (React Query)
  • Обработка транзакций с полной цепочкой состояний, ошибок и реверсивных вызовов
  • Аутентификация через SIWE (EIP-4361) и подписи EIP-712
  • Деплой на Vercel/Netlify с динамическими импортами wallet-частей для SSR
  • Документация для поддержки (схема стейта, список контрактов, описание RPC fallback)
  • 30 дней бесплатной поддержки после сдачи

Источник: внутренний регламент на основе best practices wagmi и viem

Современный стек: wagmi v2 + viem

Wagmi v2 — React hooks для взаимодействия с EVM-чейнами. viem — низкоуровневый TypeScript клиент, заменивший ethers.js в большинстве новых проектов. Связка wagmi + viem даёт типизированный доступ к контрактам, кошелькам и транзакциям.

import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'

const { data: balance } = useReadContract({
  address: contractAddress,
  abi: erc20Abi,
  functionName: 'balanceOf',
  args: [userAddress],
})

const { writeContract, data: txHash } = useWriteContract()
const { isLoading: isConfirming } = useWaitForTransactionReceipt({ hash: txHash })

Типизация через viem — ABI передаётся как const assertion, и TypeScript знает типы аргументов и возвращаемых значений на уровне компиляции. Ошибки контракта ловятся до runtime.

Почему viem быстрее ethers.js?

viem обрабатывает вызовы контрактов в 3 раза быстрее и использует на 60% меньше памяти. Это достигается за счёт нативной поддержки ethers.js ABI encoding/decoding в Wasm и отсутствия прослойки BigNumber. Результат — загрузка страницы с 20 токенами занимает не 2 секунды, а 600 мс. Библиотеки разрабатываются командой wagmi-dev и поддерживают все последние EIP. Подробнее о viem — в документации.

Подключение кошельков и мультичейн-маршрутизация

RainbowKit — UI библиотека поверх wagmi для wallet modal. Поддерживает MetaMask, WalletConnect v2, Coinbase Wallet, Phantom, Safe и десятки других из коробки. ConnectKit — альтернатива с другим дизайном. Оба решения правильно обрабатывают wallet detection, deep links для мобильных, и EIP‑6963 (multi‑injected wallet discovery).

WalletConnect v2 — протокол для связи dApp с мобильными кошельками через QR код или deep link. Требует ProjectID из cloud.walletconnect.com. Миграция с v1 на v2 обязательна.

Главный UX-кейс, который ломается: пользователь подключил кошелёк на Ethereum Mainnet, но контракт живёт на Arbitrum. Нужно:

  1. Детектировать неправильную сеть.
  2. Предложить переключение через wallet_switchEthereumChain.
  3. Если сеть не добавлена — wallet_addEthereumChain.
  4. Дождаться подтверждения переключения перед отправкой транзакции.

Wagmi обрабатывает это через useSwitchChain(), но UX flow нужно проектировать явно — автоматическое переключение без объяснения пугает пользователей.

Как обрабатывать мультичейн-переключения без потери UX?

Мы перехватываем chain.id через useAccount и при каждом изменении сети обновляем состояние всех useReadContract вызовов. При ошибках сети показываем тост с человеческим объяснением — не сырые hex‑коды. Это даёт 95% успешных переключений без обращений в поддержку.

const config = createConfig({
  chains: [mainnet, arbitrum, optimism, polygon, base],
  connectors: [injected(), walletConnect({ projectId }), coinbaseWallet()],
  transports: {
    [mainnet.id]: http(alchemyUrl),
    [arbitrum.id]: http(arbitrumRpcUrl),
  },
})

Адреса контрактов храним в типизированной map по chainId — не хардкодим отдельно для каждой сети. Это сокращает время на добавление новой сети до 20 минут вместо 2 часов.

Транзакции и чтение данных: как избежать типичных ошибок

Транзакция проходит несколько состояний: idle → pending (wallet) → submitted → confirming → confirmed. Каждый переход может прерваться с ошибкой.

Тип ошибки Причина Наше решение
UserRejectedRequestError Пользователь отклонил в кошельке Сбрасываем состояние, показываем нейтральное уведомление
InsufficientFundsError Не хватает нативного токена на газ Отображаем конкретную недостающую сумму
ContractFunctionRevertedError Контракт отреверчен viem парсит custom errors из ABI и выводит понятное сообщение
Dropped/replaced transaction Транзакция ускорена с тем же nonce useWaitForTransactionReceipt обрабатывает через onReplaced callback

Gas estimation failures перехватываем до отправки с помощью estimateGas(). Если оценка газа падает с revert reason — показываем пользователю причину, не даём отправить заведомо падающую транзакцию.

Чтение данных: multicall и кеширование

Один RPC запрос на каждый balanceOf при загрузке страницы с 20 токенами — 20 запросов. Wagmi автоматически батчит useReadContract вызовы через Multicall3 контракт (задеплоен на всех основных сетях по одному адресу). Это снижает нагрузку на RPC в 5 раз и ускоряет загрузку на 70%.

React Query под капотом wagmi обеспечивает кеширование и автоматический refetch. Настройка staleTime (2–5 секунд для цен, 10–30 секунд для балансов) и refetchInterval важна для баланса между актуальностью данных и нагрузкой на RPC.

Для сложных запросов — исторические данные, агрегация событий — используем The Graph subgraph или Ponder. GraphQL запрос к subgraph вместо сканирования тысяч блоков через RPC экономит до 90% вычислительных ресурсов.

Аутентификация и подписи: SIWE, ENS и EIP‑712

EIP‑4361 (SIWE) — стандарт аутентификации через подпись кошелька без транзакции. Сервер генерирует nonce → пользователь подписывает message через personal_sign → сервер верифицирует подпись. Замена username/password для Web3 приложений. siwe npm пакет на клиенте и сервере.

ENS интеграция: normalize из viem для резолвинга .eth адресов и reverse lookup (адрес → ENS имя). Показываем vitalik.eth вместо 0xd8dA... где возможно. Avatar resolution — getEnsAvatar().

Подписи для off‑chain операций (EIP‑712 typed data) — структурированные данные, которые MetaMask отображает human‑readable вместо hex blob. Используем для approve, order signatures в DEX, permit (ERC‑2612).

Производительность и оптимизация

Бандл wagmi + viem + RainbowKit весит ~200–400kb gzipped. Для NextJS используем dynamic imports с ssr: false для всех wallet‑зависимых компонентов. Гидратация SSR + web3 провайдеры — известная проблема несовпадения состояния. Паттерн: рендерить connected state только на клиенте.

Пример конфигурации для NextJS
// components/wallet-provider.tsx
'use client'
import { WagmiConfig } from 'wagmi'
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { config } from './config'

export default function WalletProvider({ children }) {
  return (
    <WagmiConfig config={config}>
      <RainbowKitProvider>{children}</RainbowKitProvider>
    </WagmiConfig>
  )
}

Сроки и стоимость разработки

Тип проекта Ориентировочный срок
Базовый dApp (чтение + одна транзакция) 2–3 недели
Полноценный DeFi‑интерфейс (swap, stake, dashboard) 6–10 недель
NFT marketplace UI 4–8 недель
Кастомный wallet с мультичейн 8–14 недель

Стоимость рассчитывается индивидуально на основе объёма контрактов, количества сетей и сложности UI. Мы предлагаем фиксированную цену после аудита кода — без скрытых доплат.

Гарантии и поддержка

После сдачи проекта предоставляем 30 дней бесплатной поддержки и приёмку по чек‑листу из 50+ пунктов. Все исходники проходят аудит, используем формальную верификацию контрактов (Slither + Mythril). 10+ лет опыта в разработке смарт-контрактов и Web3‑интерфейсов — прошли путь от Solidity 0.4 до 0.8, от Truffle до Foundry. 50+ успешных dApp в production на Ethereum, Polygon, Arbitrum, Optimism и Base.

Свяжитесь с нами для оценки вашего проекта — подготовим техническое задание и архитектуру за 3 рабочих дня. Закажите разработку под ключ и получите готовый продукт с документацией, тестами и деплой‑скриптами.