Frontend Architecture
Deep dive into the Next.js frontend structure and libraries
Tech Stack
The frontend is built with modern, production-ready technologies:
Technology | Version | Purpose |
---|---|---|
Next.js | 15 | React framework with App Router |
React | 19 | UI library with latest features |
TypeScript | 5.9 | Type safety and better DX |
Tailwind CSS | v4 | Utility-first styling |
shadcn/ui | Latest | High-quality UI components |
PAPI | 1.15 | Type-safe Polkadot API |
ReactiveDOT | 0.45 | Reactive Web3 state management |
Project Structure
package.json
next.config.ts
tailwind.config.ts
tsconfig.json
Available Commands
Run these commands from the frontend directory or root with -F frontend
:
# Development
bun run dev # Start dev server with Turbo
bun run build # Build for production
bun run start # Start production server
# Code Quality
bun run lint # Run Biome + Prettier checks
bun run lint:fix # Auto-fix linting issues
bun run typecheck # TypeScript type checking
# Maintenance
bun run clean # Remove build artifacts
Key Components
Web3 Components
// Wallet connection and account selection
import { AccountSelect } from '@/components/web3/account-select'
<AccountSelect />
// Network switching component
import { ChainSelect } from '@/components/web3/chain-select'
<ChainSelect />
// Smart contract interaction UI
import { ContractCard } from '@/components/web3/contract-card'
<ContractCard contractAddress="..." />
ReactiveDOT Configuration
The chain configuration is centralized in /src/lib/reactive-dot/config.ts
:
export const config = {
chains: {
dev: {
descriptor: dev,
provider: getWsProvider("ws://127.0.0.1:9944"),
},
pop: {
descriptor: pop,
provider: getWsProvider("wss://rpc1.paseo.popnetwork.xyz"),
},
// Add more chains here
},
wallets: [walletconnect()],
}
Contract Integration
Contract deployments are managed in /src/lib/inkathon/deployments.ts
:
import { evmAddress as flipperEvmDev, ss58Address as flipperSs58Dev } from 'contracts/deployments/flipper/dev'
import { evmAddress as flipperEvmPop, ss58Address as flipperSs58Pop } from 'contracts/deployments/flipper/pop'
export const contractDeployments = {
flipper: {
dev: { evmAddress: flipperEvmDev, ss58Address: flipperSs58Dev },
pop: { evmAddress: flipperEvmPop, ss58Address: flipperSs58Pop },
},
}
Styling with Tailwind CSS v4
The project uses the latest Tailwind CSS v4 with several enhancements:
- CSS-in-JS: Direct CSS variable usage
- Mobile-first: Responsive design by default
- Dark mode: Built-in theme switching
- Custom utilities: Extended with tw-animate
Theme Configuration
/* globals.css */
@import "tailwindcss";
@theme {
--color-primary: #e6007a;
--color-background: #1a1a1a;
/* Custom theme variables */
}
Component Patterns
Server Components by Default
// Prefer server components
export function Header() {
return <header>...</header>
}
Client Components When Needed
'use client'
// Only for interactive components
export function WalletButton() {
const [connected, setConnected] = useState(false)
// ...
}
Suspense Boundaries
import { Suspense } from 'react'
import { AccountSelectSkeleton } from './skeletons'
<Suspense fallback={<AccountSelectSkeleton />}>
<AccountSelect />
</Suspense>
Environment Variables
Configure the frontend with these environment variables:
Variable | Description | Example |
---|---|---|
NEXT_PUBLIC_CHAIN | Default chain | pop |
NEXT_PUBLIC_RPC_URL | Custom RPC endpoint | wss://... |
Performance Optimizations
- Turbo Mode: Enabled by default for faster builds
- App Router: Leverages React Server Components
- Code Splitting: Automatic with Next.js
- Image Optimization: Built-in Next.js Image component
- Font Optimization: Geist font with
next/font
Best Practices
- Use functional components with the
function
keyword - Prefer named exports over default exports
- Keep components small and focused
- Use TypeScript strictly for better type safety
- Minimize 'use client' directives
- Wrap async components in Suspense boundaries
- Follow kebab-case for file naming