dApp Frontend Development with Next.js

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
dApp Frontend Development with Next.js
Medium
~1-2 weeks
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1051
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    827
  • image_logo-aider_0.jpg
    AIDER company logo development
    762
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    850

dApp Frontend Development with Next.js

Next.js for Web3 is not just about SSR/SSG. It's primarily solving one contradiction: blockchain data requires client-side execution (wallet connection, transaction signing), while SEO and initial load require server rendering. The wrong boundary between server and client components breaks either wallet integration or performance.

Architecture: Server and Client Separation

The Problem of Hydration with wagmi/viem

wagmi 2.x uses localStorage and window.ethereum — both objects are unavailable on the server. Naive import of useAccount in a Server Component throws an error. Even worse — hydration mismatch: server renders "not connected", client after hydration shows "connected to MetaMask", and React issues a warning or breaks the UI.

Correct structure:

// app/providers.tsx — CLIENT component, wraps entire application
'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 component, imports Providers
import { Providers } from './providers';

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

Static content (navbar, footer, landing text) — Server Components. Wallet, balances, transaction buttons — Client Components with 'use client'.

SSR for On-chain Data

Public blockchain data (protocol TVL, token lists, prices) can be loaded on the server. Next.js 14 Server Components + fetch with caching:

// app/protocol/page.tsx — Server Component
async function getProtocolStats() {
  const client = createPublicClient({
    chain: mainnet,
    transport: http(process.env.RPC_URL), // private variable, not 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(); // runs on server
  return <StatsDisplay stats={stats} />;
}

fetch in Next.js 14 is cached by default. For on-chain data through viem explicit management is needed: revalidate: 60 in export const revalidate or manual invalidation via Route Handlers.

Managing Transaction State

Transaction Lifecycle in UI

Transaction goes through several states: idle → preparing → signing → pending → confirming → success/error. Each state requires separate UI feedback. wagmi provides hooks for each stage:

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

  if (isPending) return <Button disabled>Confirm in wallet...</Button>;
  if (isConfirming) return <Button disabled>Waiting for confirmation ({hash?.slice(0, 8)}...)</Button>;
  if (isSuccess) return <Button variant="success">Done ✓</Button>;

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

Optimistic Updates

For operations with predictable results (like, follow, simple toggle) — optimistic UI via @tanstack/react-query useMutation with onMutate/onError/onSettled. User sees changes immediately, rollback only happens on error.

Multicall and Batch Requests

For dashboard with lots of on-chain data — no parallel single RPC calls. Multicall3 collects all requests into one:

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

viem supports multicall natively. For 100 tokens — 1 RPC request instead of 100. This is the difference between 2 seconds and 200 milliseconds of dashboard load time.

Important Environment Details

.env.local for local development, .env.production for production. Variables without NEXT_PUBLIC_ don't get into the client bundle — RPC URLs with API keys should be without prefix (use only in Server Components or Route Handlers).

RPC providers: Alchemy, Infura, QuickNode. For production — multiple providers with fallback via wagmi fallback transport. Public RPCs (like https://eth.llamarpc.com) have rate limits — don't use in production without fallback.

Stack

Next.js 14+ (App Router), wagmi 2.x, viem 2.x, ConnectKit or RainbowKit, @tanstack/react-query 5.x, TypeScript, Tailwind CSS. Testing: Vitest + Playwright for e2e. Deployment: Vercel (native Next.js support) or self-hosted on Docker.

Timeline Guidelines

Basic dApp frontend with wallet connection, contract reads and transaction forms — 1 week. With SSR for public data, optimistic updates, full transaction lifecycle and mobile adaptation — 2 weeks.