frontend-playbook

star 0

Build-to-production pipeline for Scaffold-ETH 2 dApps. Fork mode, IPFS deployment, Vercel deployment, ENS subdomain setup, custom domains, and production checklist.

andginja By andginja schedule Updated 3/6/2026

name: frontend-playbook description: Build-to-production pipeline for Scaffold-ETH 2 dApps. Fork mode, IPFS deployment, Vercel deployment, ENS subdomain setup, custom domains, and production checklist. metadata: author: Andre Ginja version: 1.0.0

Frontend Playbook: Build to Production

This skill covers the mechanics of getting your Scaffold-ETH 2 dApp from a local dev server to a production URL that real users visit. If you skip steps here, your deployment will break in ways that are obvious to users and embarrassing for you.

What You Probably Got Wrong

  1. You ran yarn chain instead of yarn fork. Your local chain has no USDC, no Uniswap, no Chainlink oracles. If your dApp touches ANY existing protocol, you need a fork. Always.

  2. You deployed to IPFS without trailingSlash: true. Every route except / returns a 404. Your users see a blank page on /dashboard, /swap, /profile. This is the single most common IPFS deployment bug.

  3. You did not clean the build directory before deploying. Stale .next and out directories contain old assets. Your deployment serves a mix of old and new code. Pages randomly break.

  4. You set environment variables in .env.local but not in Vercel. Your local build works. Your Vercel build fails silently or serves a broken app with undefined config values.

  5. You assumed IPFS gateways are fast. Public IPFS gateways are slow. Your dApp takes 10-30 seconds to load on first visit. Pin your content and use a dedicated gateway.


Fork Mode: The Right Way to Develop Locally

When to Use Fork Mode

Use yarn fork instead of yarn chain whenever your dApp interacts with:

  • Any ERC-20 token (USDC, WETH, DAI, etc.)
  • Any DeFi protocol (Uniswap, Aave, Compound, etc.)
  • Chainlink price feeds
  • ENS
  • Any deployed contract you do not own

Starting a Fork

# Fork Ethereum mainnet
yarn fork --network mainnet

# Fork Base
yarn fork --network base

# Fork Optimism
yarn fork --network optimism

# Fork Arbitrum
yarn fork --network arbitrum

This starts Anvil with a fork of the specified chain at the latest block. All contract state from that chain is available locally.

Using Forked State

On a forked chain, you can impersonate any account:

# In your deploy script or test, impersonate a whale
cast send --unlocked --from 0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8 \
  --to YOUR_ADDRESS \
  --value 100ether

You can interact with real Uniswap pools, real Aave markets, real Chainlink feeds — all locally, with zero gas cost and instant confirmation.

Fork Mode Gotchas

  1. Fork state is frozen at the block you forked from. New transactions on the live chain are not reflected. If you need fresh state, restart the fork.

  2. RPC rate limits. Forking makes many RPC calls to the upstream provider. Use an Alchemy or Infura key with reasonable limits. Public RPCs will rate-limit you quickly.

  3. Block timestamps. The forked chain starts at the fork block's timestamp. If your contract has time-dependent logic (vesting, lockups), you may need to advance time:

# Advance time by 1 day
cast rpc evm_increaseTime 86400
cast rpc evm_mine

IPFS Deployment

IPFS deployment gives you censorship resistance. No server can be taken down. The content is addressed by its hash — as long as someone pins it, it is available.

Step 1: Configure Next.js for Static Export

In packages/nextjs/next.config.js:

const nextConfig = {
  output: "export",
  trailingSlash: true,    // CRITICAL — without this, routes return 404 on IPFS
  images: {
    unoptimized: true,     // Required for static export
  },
};

Why trailingSlash: true is critical:

Without it, Next.js generates:

/dashboard.html
/swap.html

IPFS serves files by exact path. When a user visits /dashboard, IPFS looks for a file literally named dashboard (no extension). It does not find it. 404.

With trailingSlash: true, Next.js generates:

/dashboard/index.html
/swap/index.html

IPFS resolves /dashboard/ to /dashboard/index.html. It works.

This is the number one IPFS deployment bug. It will waste hours of your time if you forget it.

Step 2: Clean Build

# Remove ALL build artifacts
rm -rf packages/nextjs/.next packages/nextjs/out

# Build the static export
cd packages/nextjs && yarn build

Always clean before deploying. Stale build artifacts cause:

  • Old pages appearing alongside new ones
  • CSS mismatches (old stylesheet served with new HTML)
  • JavaScript errors from version mismatches
  • Asset 404s from deleted files still referenced in old manifests

Step 3: Deploy via BuidlGuidl IPFS

yarn ipfs

This uses BuidlGuidl's IPFS pinning service. It:

  1. Builds your app (static export)
  2. Uploads the out directory to IPFS
  3. Pins the content so it stays available
  4. Returns an IPFS CID (Content Identifier)

You will get a URL like:

https://ipfs.io/ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco

Step 4: Verify the Deployment

Do not skip this. Open the IPFS URL and verify:

  1. The root page (/) loads correctly
  2. Navigate to every route — they should all load
  3. Wallet connect works
  4. Contract interactions work
  5. Images and assets load
  6. No console errors

Alternative: Manual IPFS Deployment

If you are not using BuidlGuidl's service:

# Install IPFS CLI
npm install -g ipfs-car

# Build
rm -rf packages/nextjs/.next packages/nextjs/out
cd packages/nextjs && yarn build

# Upload to web3.storage, Pinata, or nft.storage
# Using Pinata as example:
npx pinata-cli upload packages/nextjs/out

IPFS Gateway Performance

Public gateways (ipfs.io, gateway.pinata.cloud, dweb.link) are slow. For production:

  1. Use a dedicated gateway — Pinata, Infura IPFS, or self-hosted
  2. Pin on multiple services — redundancy prevents downtime
  3. Use a custom domain — point your domain to the gateway (covered below)

Vercel Deployment

Vercel is the fastest path to production. It handles builds, CDN, SSL, and preview deployments automatically.

Step 1: Connect Repository

  1. Push your code to GitHub
  2. Go to vercel.com and import the repository
  3. Configure the project:
Setting Value
Framework Preset Next.js
Root Directory packages/nextjs
Build Command yarn build (or leave default)
Output Directory Leave default (.next)
Install Command yarn install

Step 2: Environment Variables

In the Vercel dashboard, go to Settings > Environment Variables. Add:

NEXT_PUBLIC_ALCHEMY_API_KEY=your_key_here
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=your_project_id

Do NOT add:

  • DEPLOYER_PRIVATE_KEY — this is for contract deployment, not frontend
  • Any secret that should not be in client-side JavaScript

Remember: NEXT_PUBLIC_ variables are embedded in the client bundle. They are visible to anyone. Only put non-secret configuration here.

Step 3: Monorepo Configuration

Scaffold-ETH 2 is a monorepo. Vercel needs to know which package to build.

Create vercel.json in the monorepo root if needed:

{
  "buildCommand": "cd packages/nextjs && yarn build",
  "installCommand": "yarn install",
  "framework": "nextjs",
  "outputDirectory": "packages/nextjs/.next"
}

Or configure via the Vercel dashboard — set Root Directory to packages/nextjs.

Step 4: Deploy

# Install Vercel CLI
npm i -g vercel

# Login
vercel login

# Deploy preview
vercel

# Deploy production
vercel --prod

Or just push to main — Vercel auto-deploys on push if you connected the repo.

Vercel Gotchas

  1. Monorepo root: If Vercel builds from the wrong directory, all imports fail. Double-check the root directory setting.

  2. Environment variables must be set in Vercel dashboard, not just in .env.local. Your local .env.local is not uploaded to Vercel.

  3. Build errors: If the build fails on Vercel but works locally, check:

    • Node.js version (set in Settings > General)
    • Missing env vars
    • Case-sensitive imports (Mac is case-insensitive, Linux is not)
  4. Preview deployments: Every PR gets a preview URL. Use this for QA before merging to main.


ENS Subdomain Setup

ENS gives your dApp a human-readable name. Instead of https://my-dapp.vercel.app, users go to mydapp.eth (via ENS-aware browsers) or mydapp.eth.limo (via any browser).

Option 1: Point ENS to IPFS Content

This is the decentralized option. Your frontend lives on IPFS, your name lives on ENS.

  1. Get an ENS name — Register at app.ens.domains

  2. Set the Content Hash:

    • Go to your ENS name on app.ens.domains
    • Click "Records"
    • Set "Content Hash" to your IPFS CID:
      ipfs://QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco
      
    • Confirm the transaction
  3. Access via .eth.limo:

    • Your dApp is now available at https://yourname.eth.limo
    • This works in any browser — no special extension needed
    • .eth.limo is a public gateway that resolves ENS content hashes
  4. Update on redeploy: Every time you redeploy to IPFS, you get a new CID. You must update the ENS content hash (onchain transaction, costs gas).

Option 2: Point ENS to a URL (CNAME)

If you are using Vercel or another host:

  1. Set a Text Record on your ENS name:
    • Key: url
    • Value: https://my-dapp.vercel.app

This is simpler but less decentralized — your host can go down.

Subdomain Setup

If you own yourbrand.eth, you can create subdomains like app.yourbrand.eth:

  1. Go to your ENS name settings
  2. Click "Subnames"
  3. Create app subdomain
  4. Set the content hash on the subdomain

Cost: One onchain transaction for the subdomain creation + one for setting the content hash.


Custom Domain Setup

With Vercel

  1. In Vercel dashboard: Settings > Domains
  2. Add your domain (e.g., app.yourdapp.com)
  3. Update your DNS:
    • A Record: 76.76.21.21
    • CNAME: cname.vercel-dns.com (for subdomains)
  4. SSL is automatic

With IPFS + Custom Domain

Use a service like Fleek or Cloudflare to proxy your custom domain to IPFS:

  1. Cloudflare: Add a CNAME record pointing to cloudflare-ipfs.com
  2. DNSLink: Set a TXT record:
    _dnslink.app.yourdapp.com -> dnslink=/ipfs/QmYourCID
    
  3. Update the TXT record on every redeploy

Go-to-Production Checklist

Run through every item before sharing the URL publicly.

Build Verification

  • Clean build: rm -rf .next out then yarn build completes without errors
  • No TypeScript errors
  • No ESLint warnings that indicate bugs (unused vars are fine, undefined vars are not)
  • Build output size is reasonable (< 5MB for initial load)

Deployment Verification

  • Production URL loads (not just the root — check every route)
  • IPFS: trailingSlash: true is set in next.config.js
  • IPFS: Content is pinned on at least one reliable service
  • Vercel: Environment variables are set in dashboard
  • Vercel: Root directory points to packages/nextjs
  • Custom domain resolves correctly
  • SSL certificate is valid (no mixed content warnings)

Wallet and Network

  • Wallet connect works on desktop (MetaMask, Coinbase Wallet)
  • Wallet connect works on mobile (WalletConnect, in-app browsers)
  • Network switching works — prompts user to switch if on wrong chain
  • Correct chain ID configured in scaffold.config.ts

Contract Integration

  • deployedContracts.ts has the production contract addresses
  • All contract interactions work on the production chain
  • Block explorer links point to the correct explorer for the chain
  • Contract is verified on the block explorer

Content and Branding

  • Default Scaffold-ETH branding is removed or customized
  • App title and meta tags are set
  • Favicon is custom (not the Scaffold-ETH default)
  • Open Graph image is set for social sharing
  • No "localhost" or "test" strings visible in the UI

Security

  • No API keys in client-side code (search for "api_key", "apikey", "secret")
  • No private keys anywhere in the repository
  • .env files are in .gitignore
  • RPC URLs do not contain embedded keys
  • CSP headers configured (if applicable)

Performance

  • First page load under 3 seconds on a 3G connection
  • No layout shift when wallet connects
  • Images are optimized
  • Fonts are preloaded or use system fonts

Monitoring

  • Error tracking set up (Sentry, LogRocket, or at minimum console.error logging)
  • Contract monitored on block explorer (set up alerts for unusual activity)
  • Uptime monitoring for Vercel deployments (UptimeRobot, Betterstack)

Common Gotchas Reference

Problem Cause Fix
All routes except / return 404 on IPFS Missing trailingSlash: true Add to next.config.js
Stale pages in production Did not clean .next and out before build rm -rf .next out then rebuild
Build works locally, fails on Vercel Missing env vars or case-sensitive imports Check Vercel build logs
Wallet does not connect Wrong chain in scaffold.config.ts Verify targetNetworks
"Module not found" on Vercel Monorepo root directory wrong Set root to packages/nextjs
Images broken on IPFS Using Next.js Image optimization Set images: { unoptimized: true }
ENS content hash update is expensive Mainnet gas for ENS update Budget 0.005-0.02 ETH per update
IPFS loads slowly Using public gateway Pin content and use dedicated gateway
CSS not loading in production Tailwind purging too aggressively Check content paths in Tailwind config
API routes return 404 on IPFS IPFS is static hosting — no server Use output: "export", move API logic onchain or to a separate backend

Every one of these has cost someone hours of debugging. Do not learn them the hard way.

Install via CLI
npx skills add https://github.com/andginja/ethskills --skill frontend-playbook
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator