Claude Code + Expo:改善 React Native 程式碼生成的倉庫設定
Claude Code + Expo: The Repo Config That Makes It Write Decent React Native
作者整理 Expo/React Native 專案的 Claude Code 倉庫設定,涵蓋專案規則、命令權限、編輯後檢查及可重用 Skills。示例以 Expo SDK 54、TypeScript、Expo Router、NativeWind 和 Supabase 為技術組合,並透過 ESLint 和 tsc 把錯誤回傳給 Claude。
TL;DR
- Claude Code forgets your project every session. Write the facts down in
CLAUDE.md: stack, commands, and do/don't rules like "usenpx expo install". - Allowlist routine commands in
.claude/settings.json, put risky ones behindaskordeny. - A
PostToolUsehook runs ESLint andtscafter every edit and feeds errors back to Claude withexit 2. - Skills (
.claude/skills/<name>/SKILL.md) turn "add a screen" into a repeatable/add-screencommand. - Still read the diff and run it on a phone.
CLAUDE.md: keep it short and factual
CLAUDE.md in the repo root loads into every session. /init will draft one; then cut it down. The docs suggest staying under 200 lines. Here's a trimmed version for an Expo app:
## Stack
- Expo SDK 54, TypeScript (strict), Expo Router, NativeWind, Supabase
## Commands
- Dev: `npx expo start`
- Typecheck: `npx tsc --noEmit`
- Lint: `npx expo lint`
- Dep check: `npx expo install --check`
## Rules
- Add packages with `npx expo install <pkg>`, never npm/yarn add.
- Every file in app/ is a route. Components go in components/.
- Style with NativeWind className, not StyleSheet.create.
- Navigate with expo-router `Link` / `router`.
- Never put secrets in EXPO_PUBLIC_ vars. They're inlined into the bundle.
- Don't hand-edit ios/ or android/.
Why these lines:
-
npx expo installpicks versions that match your SDK. Plainnpm installdoesn't. - Expo Router treats anything in the routes folder as a route, so a stray
utils.tsinapp/becomes a broken screen. (Newer Expo docs usesrc/app/. Write what your repo uses.) - Expo's own docs say
EXPO_PUBLIC_values end up in plain text in your compiled app.
Got an AGENTS.md already? Put @AGENTS.md at the top of CLAUDE.md to import it instead of duplicating.
For rules that only matter in one folder, use .claude/rules/ with a paths field. They only load when Claude touches matching files:
---
paths:
- "supabase/**/*"
---
- New tables get RLS enabled in the same migration.
- Never edit an applied migration.
Permissions: stop clicking "yes" forty times
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(npx expo install *)",
"Bash(npx expo lint *)",
"Bash(npx tsc --noEmit *)",
"Bash(npx eslint *)"
],
"ask": ["Bash(git push *)", "Bash(npx expo prebuild *)", "Bash(eas *)"],
"deny": [
"Bash(rm -rf *)",
"Bash(git reset --hard *)",
"Read(./.env)",
"Read(./.env.*)"
]
}
}
Quick gotchas:
- Rules are checked deny, then ask, then allow. Deny always wins.
-
Bash(npx tsc --noEmit *)also matches the barenpx tsc --noEmit. -
cmd && othergets split, so an allow oncmddoesn't sneakotherthrough. - I skip
Bash(npx expo *). It would approveprebuild --cleantoo. - Bash deny rules don't catch
/bin/rmorbash -c. They prevent accidents, they're not a sandbox.
The hook that does the most work
CLAUDE.md is a suggestion. Hooks always run. Add this to the same settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-ts.sh" }
]
}
]
}
}
.claude/hooks/check-ts.sh (needs jq, and chmod +x):
#!/usr/bin/env bash
FILE=$(jq -r '.tool_input.file_path // empty')
case "$FILE" in *.ts|*.tsx) ;; *) exit 0 ;; esac
cd "$CLAUDE_PROJECT_DIR" || exit 0
errors=""
out=$(npx eslint "$FILE" 2>&1) || errors+="$out"$'\n'
out=$(npx tsc --noEmit --pretty false 2>&1) || errors+="$out"
if [ -n "$errors" ]; then echo "$errors" >&2; exit 2; fi
For PostToolUse, exit 2 doesn't undo the edit. It sends stderr to Claude, so it sees the type errors and fixes them right away. Check it registered with /hooks. If whole-project tsc gets slow, move it to a Stop hook.
Commands and skills
Custom commands were merged into skills. .claude/commands/check.md and .claude/skills/check/SKILL.md both give you /check. I use skills for anything multi-step:
---
name: add-screen
description: Add an Expo Router screen with NativeWind styling and navigation
argument-hint: [route-path] [what it does]
disable-model-invocation: true
allowed-tools: Bash(npx tsc --noEmit *) Bash(npx expo lint *)
---
Add a screen at app/$0.tsx. Purpose: $ARGUMENTS
1. Read app/_layout.tsx and a nearby screen to match patterns.
2. For dynamic segments like [id], use useLocalSearchParams with typed params.
3. Reusable UI goes in components/. NativeWind className only.
4. Link to it from the screen that should open it.
5. Run typecheck and lint, fix what you introduced, list changed files.
-
disable-model-invocation: truemeans only you can trigger it. -
allowed-toolspre-approves those commands while it runs. -
$0is the first argument,$ARGUMENTSis everything.
Then:
/add-screen orders/[id] "order detail with status and a cancel button"
For bigger changes, ask for a plan first. Plan mode is where you catch "I'll just add React Navigation" before it touches your files.
Don't want to write all this?
Fair. I work on AppLighter: Expo SDK 54 templates with TypeScript, Expo Router, NativeWind and Supabase (RLS on every table), shipped with CLAUDE.md, AGENTS.md, .cursorrules, and skills/commands like /add-screen, /swap-backend, /audit-security and /ship. The files above work fine on their own too.
Review before you trust it
-
git diffeverything. Look for out-of-scope files, new deps, non-route files inapp/. -
npx expo install --checkfor SDK version mismatches. -
Grep new
EXPO_PUBLIC_vars. Assume they're public, because they are. - Run it on a real phone. Back nav, keyboard, safe areas. Typecheck can't see those.
If Claude gets the same thing wrong twice, that's a missing line in CLAUDE.md.
What's in your React Native CLAUDE.md that I didn't cover? Drop your best rule in the comments.
來源:DEV Community · dev.to