Loading...
Loading...
The complete build-to-production pipeline for Ethereum dApps. Fork mode setup, IPFS deployment, Vercel config, ENS subdomain setup, and the full production checklist. Built around Scaffold-ETH 2 but applicable to any Ethereum frontend project. Use when deploying any dApp to production.
npx skill4agent add austintgriffith/ethskills frontend-playbookyarn chainyarn chainyarn fork --network basetrailingSlash: true/NEXT_PUBLIC_PRODUCTION_URLlocalhost:3000npx create-eth@latestforge inityarn chain (WRONG) yarn fork --network base (CORRECT)
└─ Empty local chain └─ Fork of real Base mainnet
└─ No protocols └─ Uniswap, Aave, etc. available
└─ No tokens └─ Real USDC, WETH exist
└─ Testing in isolation └─ Test against REAL statenpx create-eth@latest # Select: foundry, target chain, name
cd <project-name>
yarn install
yarn fork --network base # Terminal 1: fork of real Base
yarn deploy # Terminal 2: deploy contracts to fork
yarn start # Terminal 3: Next.js frontendchains.foundry// scaffold.config.ts during development
targetNetworks: [chains.foundry], // ✅ NOT chains.base!chains.base# In a new terminal — REQUIRED for time-dependent logic
cast rpc anvil_setIntervalMining 1block.timestamppackages/foundry/package.json--block-time 1cd packages/nextjs
rm -rf .next out # ALWAYS clean first
NEXT_PUBLIC_PRODUCTION_URL="https://yourapp.yourname.eth.link" \
NODE_OPTIONS="--require ./polyfill-localstorage.cjs" \
NEXT_PUBLIC_IPFS_BUILD=true \
NEXT_PUBLIC_IGNORE_BUILD_ERROR=true \
yarn build
# Upload to BuidlGuidl IPFS
yarn bgipfs upload out
# Save the CID!localStoragegetItemsetItemnext-themeslocalStorage.getItem()TypeError: localStorage.getItem is not a function
Error occurred prerendering page "/_not-found"polyfill-localstorage.cjspackages/nextjs/if (typeof globalThis.localStorage !== "undefined" &&
typeof globalThis.localStorage.getItem !== "function") {
const store = new Map();
globalThis.localStorage = {
getItem: (key) => store.get(key) ?? null,
setItem: (key, value) => store.set(key, String(value)),
removeItem: (key) => store.delete(key),
clear: () => store.clear(),
key: (index) => [...store.keys()][index] ?? null,
get length() { return store.size; },
};
}--requireinstrumentation.ts--requirenext.config.tsinstrumentation.ts--requireoutput: "export"trailingSlash: truetrailingSlash: falsedebug.htmltrailingSlash: truedebug/index.htmlindex.html/debug/debugdebug/debug/index.htmlyarn buildconst isIpfs = process.env.NEXT_PUBLIC_IPFS_BUILD === "true";
if (isIpfs) {
nextConfig.output = "export";
nextConfig.trailingSlash = true;
nextConfig.images = { unoptimized: true };
}localStorageapp/blockexplorerapp/_blockexplorer-disabled# MANDATORY after ANY code change:
rm -rf .next out # 1. Delete old artifacts
# ... run full build command ... # 2. Rebuild from scratch
grep -l "YOUR_STRING" out/_next/static/chunks/app/*.js # 3. Verify changes present
# Timestamp check:
stat -f '%Sm' app/page.tsx # Source modified time
stat -f '%Sm' out/ # Build output time
# Source NEWER than out/ = STALE BUILD. Rebuild first!ls out/*/index.html # Each route has a directory + index.html
curl -s -o /dev/null -w "%{http_code}" -L "https://GATEWAY/ipfs/CID/debug/"
# Should return 200, not 404packages/nextjscd ../.. && yarn installnext build.next# Via API:
curl -X PATCH "https://api.vercel.com/v9/projects/PROJECT_ID" \
-H "Authorization: Bearer $VERCEL_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rootDirectory": "packages/nextjs", "installCommand": "cd ../.. && yarn install"}'| Error | Cause | Fix |
|---|---|---|
| "No Next.js version detected" | Root Directory not set | Set to |
| "cd packages/nextjs: No such file" | Build command has | Clear it — root dir handles this |
| OOM / exit code 129 | SE2 monorepo exceeds 8GB | Use IPFS instead, or |
Want to deploy SE2?
├─ IPFS (recommended) → yarn ipfs / manual build + upload
│ └─ Fully decentralized, no memory limits, works with ENS
├─ Vercel → Set rootDirectory + installCommand
│ └─ Fast CDN, but centralized. May OOM on large projects
└─ vercel --prebuilt → Build locally, push artifacts to Vercel
└─ Best of both: local build power + Vercel CDNhttps://app.ens.domains/yourname.ethmyapphttps://app.ens.domains/myapp.yourname.ethipfs://<CID># 1. Onchain content hash matches
RESOLVER=$(cast call 0x00000000000C2e074eC69A0dFb2997BA6C7d2e1e \
"resolver(bytes32)(address)" $(cast namehash myapp.yourname.eth) \
--rpc-url https://eth.llamarpc.com)
cast call $RESOLVER "contenthash(bytes32)(bytes)" \
$(cast namehash myapp.yourname.eth) --rpc-url https://eth.llamarpc.com
# 2. Gateway responds (may take 5-15 min for cache)
curl -s -o /dev/null -w "%{http_code}" -L "https://myapp.yourname.eth.link"
# 3. OG metadata correct (not localhost)
curl -s -L "https://myapp.yourname.eth.link" | grep 'og:image'.eth.link.eth.limo.eth.linkscaffold.config.tsrpcOverridespollingInterval: 3000myapp.yourname.ethmyapp.yourname.eth.linkpublic/thumbnail.pngNEXT_PUBLIC_PRODUCTION_URLog:imagecd packages/nextjs && rm -rf .next out
NEXT_PUBLIC_PRODUCTION_URL="https://myapp.yourname.eth.link" \
NODE_OPTIONS="--require ./polyfill-localstorage.cjs" \
NEXT_PUBLIC_IPFS_BUILD=true NEXT_PUBLIC_IGNORE_BUILD_ERROR=true \
yarn build
# Verify before uploading:
ls out/*/index.html # Routes exist
grep 'og:image' out/index.html # Not localhost
stat -f '%Sm' app/page.tsx # Source older than out/
stat -f '%Sm' out/
yarn bgipfs upload out # Save the CIDhttps://community.bgipfs.com/ipfs/<CID>.eth.link/debug/https://myapp.yourname.eth.link.tsx<Address/>isLoadingdisabledforge test # All tests pass
forge test --fuzz-runs 10000 # Fuzz testingyarn chainyarn fork --network <chain>forge initnpx create-eth@latestdeployedContracts.tsyarn deployscaffold.config.ts.env.localmainnet.base.org