skills/expo-workflow-expert/SKILL.md
Expo managed workflow expert for EAS Build, Submit, Update, config plugins, and custom dev clients. Activate on: Expo project, EAS Build, EAS Update, config plugin, expo-dev-client, Expo Router, Expo SDK 52, app.json configuration. NOT for: bare React Native (use react-native-architect), native module authoring (use react-native-architect), Flutter (use flutter-bloc-state-manager).
npx skillsauth add curiositech/windags-skills expo-workflow-expertInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert in the Expo managed workflow including EAS Build/Submit/Update, config plugins, custom dev clients, and Expo Router.
Activate on: "Expo project", "EAS Build", "EAS Submit", "EAS Update", "config plugin", "expo-dev-client", "Expo Router", "Expo SDK 52", "app.json config", "expo prebuild"
NOT for: Bare React Native → react-native-architect | Native module authoring → react-native-architect | Flutter → flutter-bloc-state-manager
npx create-expo-app@latest my-appnpx eas init and configure eas.json for build profilesnpx eas build --profile development for custom native builds| Domain | Technologies |
|--------|-------------|
| Build | EAS Build, local builds (npx expo run:ios/android), prebuild |
| Updates | EAS Update (OTA), update groups, channels, rollbacks |
| Submit | EAS Submit to App Store Connect and Google Play Console |
| Routing | Expo Router 4, file-based routing, API routes, typed routes |
| Config | Config plugins, app.json/app.config.ts, Expo Modules API |
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal",
"channel": "preview"
},
"production": {
"channel": "production",
"autoIncrement": true
}
},
"submit": {
"production": {
"ios": {
"appleId": "[email protected]",
"ascAppId": "123456789",
"appleTeamId": "ABCDE12345"
},
"android": {
"serviceAccountKeyPath": "./google-services.json",
"track": "internal"
}
}
}
}
Production Channel:
├─ Branch: production
├─ Update: JS-only changes ship instantly (no binary rebuild)
└─ Rollback: `eas update:rollback --channel production`
Preview Channel:
├─ Branch: staging
├─ Internal testers get latest changes
└─ QA before promoting to production
Flow:
Code change → Is it JS-only?
├─ YES → eas update --channel production --message "fix: ..."
│ (Users get update in < 1 minute, no store review)
└─ NO (native change) → eas build + eas submit
(Full store review cycle)
// plugins/withCustomSplash.ts
import { ConfigPlugin, withAndroidManifest } from 'expo/config-plugins';
const withCustomSplash: ConfigPlugin = (config) => {
return withAndroidManifest(config, async (modConfig) => {
const mainApplication = modConfig.modResults.manifest.application?.[0];
if (mainApplication) {
// Add custom meta-data to AndroidManifest.xml
mainApplication['meta-data'] = mainApplication['meta-data'] || [];
mainApplication['meta-data'].push({
$: {
'android:name': 'expo.modules.splashscreen.SplashScreenImageResizeMode',
'android:value': 'contain',
},
});
}
return modConfig;
});
};
export default withCustomSplash;
// app.config.ts — use the plugin
export default {
plugins: [
'./plugins/withCustomSplash',
['expo-camera', { cameraPermission: 'Allow camera for scanning' }],
],
};
expo eject when a config plugin would suffice. Since Expo SDK 50+, the "managed vs bare" distinction is gone; use npx expo prebuild with config plugins.expo-dev-client) for full-fidelity testing.app.config.ts for environment-specific bundle IDs, API URLs, and feature flags.[ ] app.config.ts used (not static app.json) for dynamic configuration
[ ] EAS Build configured with development, preview, and production profiles
[ ] EAS Update channels match build profiles
[ ] Development build created (not relying on Expo Go)
[ ] Config plugins used for native customization (no manual native edits)
[ ] Expo Router with typed routes enabled
[ ] Auto-increment build numbers in production profile
[ ] EAS Submit configured for both App Store and Google Play
[ ] OTA updates tested with preview channel before production
[ ] Rollback procedure documented and tested
[ ] Bundle size monitored (expo-optimize for image compression)
[ ] Expo SDK on latest stable version
data-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.