Skip to main content
Every project created with create-nextjs-dapp follows a consistent structure based on Next.js 14+ with the App Router. This guide explains each directory and file in your generated project.

Directory overview

my-dapp/
├── app/
│   ├── layout.tsx       # Root layout with providers
│   ├── page.tsx         # Home page with demo component
│   └── globals.css      # Global styles
├── components/
│   ├── Header.tsx       # Navigation with wallet connect
│   ├── Greeting.tsx     # Demo smart contract interaction (EVM)
│   └── ThemeToggle.tsx  # Dark/light mode toggle
├── hooks/               # (EVM only)
│   └── useGreeting.ts   # Custom hook for contract reads/writes
├── abi/                 # (EVM only)
│   └── greeter.json     # Smart contract ABI
├── lib/
│   └── utils.ts         # Utility functions (cn for tailwind)
├── public/
│   ├── next.svg
│   └── vercel.svg
├── .eslintrc.json       # ESLint configuration
├── .gitignore           # Git ignore rules
├── components.json      # shadcn/ui configuration
├── next.config.js       # Next.js configuration
├── package.json         # Dependencies and scripts
├── postcss.config.js    # PostCSS configuration
├── tailwind.config.ts   # Tailwind CSS configuration
└── tsconfig.json        # TypeScript configuration

Core directories

app/ - Next.js App Router

The app directory uses Next.js App Router with React Server Components.

app/layout.tsx

Root layout that wraps your entire application. This file is customized based on your wallet provider choice. RainbowKit example:
templates/evm/rainbowkit/app/layout.tsx
import { Providers } from '@/components/Providers';
import { Toaster } from 'sonner';
import '@/app/globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <Providers>
          {children}
          <Toaster position="bottom-right" />
        </Providers>
      </body>
    </html>
  );
}
The Providers component is specific to each wallet provider and sets up:
  • Wagmi/web3 client configuration
  • Wallet connection modals
  • Theme provider for dark mode
  • React Query client

app/page.tsx

Home page with a demo smart contract interaction component:
templates/evm/app/page.tsx
import { Greeting } from "@/components/Greeting";

const Home = () => {
  return (
    <main className="min-h-[calc(100vh-65px)] flex flex-col">
      <div className="flex-1 max-w-4xl w-full mx-auto px-6 py-12 md:py-16">
        <div className="mb-8 md:mb-12">
          <h1 className="text-xl md:text-2xl font-medium text-foreground mb-2">
            greeting contract
          </h1>
          <p className="text-sm text-muted-foreground">
            read and write to an on-chain greeting message
          </p>
        </div>
        <Greeting />
      </div>
      <footer className="border-t border-border py-6">
        {/* Footer content */}
      </footer>
    </main>
  );
};

export default Home;

app/globals.css

Global styles with Tailwind directives and CSS variables for theming:
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 0 0% 3.9%;
    /* ... more CSS variables */
  }
  
  .dark {
    --background: 0 0% 3.9%;
    --foreground: 0 0% 98%;
    /* ... dark mode variables */
  }
}

components/ - React components

components/Header.tsx

Navigation bar with wallet connection button (provider-specific):
"use client";

import { ConnectButton } from '@rainbow-me/rainbowkit';
import { ThemeToggle } from './ThemeToggle';

export function Header() {
  return (
    <header className="border-b border-border">
      <div className="max-w-4xl mx-auto px-6 py-4 flex items-center justify-between">
        <h1 className="text-lg font-medium">my-dapp</h1>
        <div className="flex items-center gap-4">
          <ThemeToggle />
          <ConnectButton />
        </div>
      </div>
    </header>
  );
}
The wallet connection button changes based on your provider:
  • RainbowKit: <ConnectButton />
  • Privy: <PrivyProvider> with useLogin()
  • Dynamic: <DynamicWidget />
  • Thirdweb: <ConnectButton client={client} />

components/Greeting.tsx (EVM only)

Demo component showing smart contract interaction:
templates/evm/components/Greeting.tsx
"use client";

import { useState } from "react";
import { useGreeting } from "../hooks/useGreeting";
import { useConnectModal } from "@rainbow-me/rainbowkit";

const Greeting = () => {
  const [newGreeting, setNewGreeting] = useState<string>("");
  
  const {
    address,
    greeting,
    getGreetingLoading,
    setGreeting,
    setGreetingLoading,
  } = useGreeting({ newGreeting, onSetGreetingSuccess: () => {} });

  return (
    <div className="space-y-8">
      {/* Current greeting display */}
      <div className="p-6 border border-border bg-card/50">
        {getGreetingLoading ? (
          <div className="text-muted-foreground animate-pulse">loading...</div>
        ) : (
          <div className="text-2xl md:text-3xl font-medium">
            {greeting}
          </div>
        )}
      </div>
      
      {/* Set greeting form */}
      <div className="p-6 border border-border bg-card/50">
        <input
          onChange={(e) => setNewGreeting(e.target.value)}
          placeholder="enter new greeting..."
          disabled={!address}
          value={newGreeting}
        />
        <button
          onClick={setGreeting}
          disabled={!address || !newGreeting || setGreetingLoading}
        >
          {setGreetingLoading ? "broadcasting..." : "submit"}
        </button>
      </div>
    </div>
  );
};

export { Greeting };

components/ThemeToggle.tsx

Dark/light mode toggle using next-themes:
"use client";

import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";

export function ThemeToggle() {
  const { theme, setTheme } = useTheme();

  return (
    <button
      onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
      className="p-2 hover:bg-accent rounded-md transition-colors"
    >
      <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
    </button>
  );
}

hooks/ - Custom React hooks (EVM only)

hooks/useGreeting.ts

Custom hook for interacting with the Greeter smart contract using Wagmi:
import { useAccount, useReadContract, useWriteContract } from 'wagmi';
import greeterABI from '@/abi/greeter.json';

const GREETER_ADDRESS = '0x...'; // Contract address

export function useGreeting({ newGreeting, onSetGreetingSuccess }) {
  const { address } = useAccount();
  
  // Read current greeting
  const { data: greeting, isLoading: getGreetingLoading } = useReadContract({
    address: GREETER_ADDRESS,
    abi: greeterABI,
    functionName: 'greet',
  });
  
  // Write new greeting
  const { writeContract, isPending: setGreetingLoading } = useWriteContract({
    onSuccess: onSetGreetingSuccess,
  });
  
  const setGreeting = () => {
    writeContract({
      address: GREETER_ADDRESS,
      abi: greeterABI,
      functionName: 'setGreeting',
      args: [newGreeting],
    });
  };
  
  return {
    address,
    greeting,
    getGreetingLoading,
    setGreeting,
    setGreetingLoading,
  };
}

abi/ - Smart contract ABIs (EVM only)

abi/greeter.json

ABI (Application Binary Interface) for the demo Greeter contract:
[
  {
    "inputs": [],
    "name": "greet",
    "outputs": [{"type": "string"}],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [{"name": "_greeting", "type": "string"}],
    "name": "setGreeting",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]
Store your contract ABIs in the abi/ directory. You can export them from tools like Hardhat, Foundry, or Remix.

lib/ - Utility functions

lib/utils.ts

Utility functions like the cn helper for merging Tailwind classes:
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Configuration files

package.json

Dependencies and scripts. The exact dependencies vary by wallet provider: Base dependencies (all projects):
templates/base/package.json
{
  "dependencies": {
    "@tanstack/react-query": "^5.90.19",
    "next": "^16.1.3",
    "next-themes": "^0.4.6",
    "react": "^19.2.3",
    "react-dom": "^19.2.3",
    "viem": "^2.44.4",
    "wagmi": "^2.19.5"
  },
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}
Provider-specific additions:
  • RainbowKit: @rainbow-me/rainbowkit
  • ConnectKit: connectkit
  • Privy: @privy-io/react-auth, @privy-io/wagmi
  • Dynamic: @dynamic-labs/sdk-react-core, @dynamic-labs/wagmi-connector
  • Reown: @reown/appkit, @reown/appkit-adapter-wagmi
  • Thirdweb: thirdweb
  • GetPara: @usecapsule/rainbowkit-integration

next.config.js

Next.js configuration (provider-specific settings may be added):
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  webpack: (config) => {
    config.resolve.fallback = { fs: false, net: false, tls: false };
    return config;
  },
};

module.exports = nextConfig;

tsconfig.json

TypeScript configuration with path aliases:
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "paths": {
      "@/*": ["./*"]
    }
  }
}
The @/* path alias lets you import with @/components/Header instead of relative paths like ../../components/Header.

tailwind.config.ts

Tailwind CSS configuration with theme colors:
import type { Config } from 'tailwindcss';

const config: Config = {
  darkMode: ['class'],
  content: [
    './app/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
  ],
  theme: {
    extend: {
      colors: {
        border: 'hsl(var(--border))',
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        // ... more theme colors
      },
    },
  },
};

export default config;

Where to add your code

1

Add new pages

Create files in the app/ directory:
app/
├── page.tsx          # Home page (exists)
├── about/
   └── page.tsx      # /about route
└── dashboard/
    └── page.tsx      # /dashboard route
2

Create components

Add reusable components in components/:
components/
├── Header.tsx        # Existing
├── Greeting.tsx      # Existing demo
├── NFTGallery.tsx    # Your new component
└── TokenBalance.tsx  # Your new component
3

Add smart contract ABIs

Store contract ABIs in abi/ (EVM):
abi/
├── greeter.json      # Demo contract
├── myNFT.json        # Your NFT contract
└── myToken.json      # Your token contract
4

Create custom hooks

Add web3 hooks in hooks/ (EVM):
hooks/
├── useGreeting.ts    # Existing demo
├── useNFT.ts         # Your NFT hook
└── useToken.ts       # Your token hook
5

Add utilities

Place helper functions in lib/:
lib/
├── utils.ts          # Existing
├── formatting.ts     # Your formatters
└── validation.ts     # Your validators

Provider-specific differences

EVM projects

All EVM projects include:
  • Wagmi for Ethereum interactions
  • Viem for low-level blockchain operations
  • Demo Greeter contract interaction
  • Contract ABIs in abi/ directory
  • Custom hooks in hooks/ directory

Solana projects

Solana projects have:
  • @solana/web3.js for blockchain interactions
  • @solana/wallet-adapter-react (for wallet-adapter provider)
  • Different component structure (no hooks/ or abi/ directories)
  • Solana-specific demo components

Learn about wallet providers

Compare all 8 wallet providers and their differences

Next steps

Now that you understand the project structure:
  1. Remove the demo Greeting component
  2. Add your own smart contract ABIs
  3. Create custom hooks for your contracts
  4. Build your dApp UI in components/
  5. Add new pages in app/
The generated project is a starting point - customize it to fit your needs!

Build docs developers (and LLMs) love