surf-app
Surf App
Build full-stack crypto data apps with create-surf-app and @surf-ai/sdk.
- This skill: Build a web app / dashboard / visualization with crypto data.
- The
surfskill: Research, investigate, or fetch data via CLI (no web UI).
Scaffold and Start
Run these commands exactly — do not modify or skip steps:
# 1. Scaffold (ports auto-configured from environment)
npx create-surf-app .
# 2. Install dependencies
npm install --prefix backend
npm install --prefix frontend
# 3. Read the project rules BEFORE writing any code
cat CLAUDE.md
# 4. Start dev servers (backend first, then frontend)
npm run dev --prefix backend &
npm run dev --prefix frontend
# 5. Verify backend is running (port is in frontend/.env)
curl -s http://localhost:$(<frontend/.env grep VITE_BACKEND_PORT | cut -d= -f2)/api/health
IMPORTANT — do NOT:
- Use
npx viteornpx vite --port ...— always usenpm run dev - Pass
--portflag to dev commands — port is pre-configured infrontend/.env - Restart dev servers after
npm install— Vite auto-discovers new deps, backend usesnode --watch
Then read the generated CLAUDE.md at the project root — it has the full SDK reference, built-in endpoints, and rules for which files not to modify.
After npm install, also read SDK and theme docs for detailed patterns:
cat frontend/node_modules/@surf-ai/sdk/README.md— database (Drizzle ORM), cron jobs, web search, data strategy (market vs exchange), backend composition patternscat frontend/node_modules/@surf-ai/theme/CHARTS.md— MUST read before writing any ECharts code — Surf flat style contract, tooltip formatter, chart colors, time series tabscat frontend/node_modules/@surf-ai/theme/DESIGN-SYSTEM.md— Surf semantic tokens (bg/fg/border), tag colors, visualizer palette
Project Structure
frontend/src/App.tsx ← Main UI, build here
frontend/src/components/ ← Add components
backend/routes/*.js ← API routes (auto-mounted at /api/{name})
backend/db/schema.js ← Database tables (Drizzle ORM)
CLAUDE.md ← Project rules (READ FIRST)
Do not modify: vite.config.ts, server.js, entry-client.tsx, entry-server.tsx, index.html, index.css.
Workflow: Data Discovery → Code
Always start by exploring available data with the surf CLI, then map directly to SDK code. The CLI → SDK mapping is mechanical (see Step 2). Do NOT run node -e to test individual endpoints.
Step 1: Discover endpoints with CLI
surf sync # Download API spec (first time)
surf list-operations -g # List all endpoints by category
surf market-price --help # See params, enums, response schema
surf market-price --symbol BTC --time-range 7d # Fetch sample data, verify shape
CLI flags use kebab-case (e.g. --time-range). surf is a global command (NOT npx surf).
Step 2: Check SDK exports before writing code
Do not guess hook or method names. After npm install, read actual exports:
grep -o 'function use[A-Za-z]*' frontend/node_modules/@surf-ai/sdk/dist/react/index.js | sort
Naming convention — CLI → SDK:
- CLI:
surf market-ranking --limit 5→ Frontend:useMarketRanking({ limit: 5 })oruseInfiniteMarketRanking({ limit: 5 }) - CLI:
surf market-ranking --limit 5→ Backend:dataApi.market.ranking({ limit: 5 }) - CLI flags are kebab-case (
--time-range), SDK params are snake_case ({ time_range: '7d' })
Key rules:
- Use exact hook names from the
grepoutput — if onlyuseInfinite*exists (no plainuse*), use the infinite version and accessdata.pages[0]for the first page - Backend:
const { dataApi } = require('@surf-ai/sdk/server')— method names match CLI commands (surf market.price→dataApi.market.price()) - Escape hatch:
dataApi.get(path, params)if a typed method doesn't exist
Step 3: Write code
Write frontend and backend code using the confirmed names from Step 2. Do not run additional node -e calls to test individual endpoints — the SDK methods are guaranteed to work if the name exists in the export list.
Frontend — SDK Hooks
import { useMarketPrice } from '@surf-ai/sdk/react'
const { data, isLoading } = useMarketPrice({ symbol: 'BTC', time_range: '1d' })
// data.data → items array; data.meta → pagination/credits
The /proxy/* route is built-in — hooks automatically call /proxy/market/price which the Express backend forwards to the data API. No manual fetch needed.
Backend — dataApi
// backend/routes/portfolio.js → /api/portfolio
const { dataApi } = require('@surf-ai/sdk/server')
const { Router } = require('express')
const router = Router()
router.get('/', async (req, res) => {
const data = await dataApi.wallet.detail({ address: req.query.address })
res.json(data)
})
module.exports = router
Escape hatch: dataApi.get('any/path', params) / dataApi.post('any/path', body).
Built-in endpoints (do not recreate): /api/health, /api/__sync-schema, /api/cron, /proxy/*.
Styling
Tailwind CSS 4 + @surf-ai/theme (dark default). Use shadcn/ui components (npx shadcn@latest add button), echarts-for-react for charts, lucide-react for icons. All pre-installed.