adynato-mobile
React NativeとExpoを使ったAdynatoのモバイルアプリ開発における、画面遷移、ネイティブ機能の活用、パフォーマンス改善、OSごとの注意点などをまとめた、アプリ開発・修正時に役立つ情報を提供するSkill。
📜 元の英語説明(参考)
Mobile app development conventions for Adynato projects using React Native and Expo. Covers navigation patterns, native APIs, performance optimization, and platform-specific considerations. Use when building or modifying mobile applications.
🇯🇵 日本人クリエイター向け解説
React NativeとExpoを使ったAdynatoのモバイルアプリ開発における、画面遷移、ネイティブ機能の活用、パフォーマンス改善、OSごとの注意点などをまとめた、アプリ開発・修正時に役立つ情報を提供するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o adynato-mobile.zip https://jpskill.com/download/9755.zip && unzip -o adynato-mobile.zip && rm adynato-mobile.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/9755.zip -OutFile "$d\adynato-mobile.zip"; Expand-Archive "$d\adynato-mobile.zip" -DestinationPath $d -Force; ri "$d\adynato-mobile.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
adynato-mobile.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
adynato-mobileフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
[Skill 名] adynato-mobile
モバイル開発スキル
このスキルは、React Native と Expo で構築されたすべての Adynato モバイルプロジェクトで使用します。
スタック
- フレームワーク: Expo (managed workflow を推奨)
- ナビゲーション: Expo Router (ファイルベースルーティング)
- スタイリング: NativeWind (React Native 用の Tailwind)
- 状態管理: Zustand または React Context
- データフェッチ: TanStack Query
API駆動型UI (重要)
App Store の審査なしで即座に更新できるように、API駆動型のコンテンツを最大限に活用してください。
App Store の審査には 1〜7 日かかる場合があります。サーバーで制御できるUIはすべて、即座に更新できるようにサーバー制御にする必要があります。
API駆動にすべきもの
| コンポーネント | 理由 |
|---|---|
| Feature flags | リリースなしで機能を有効/無効にする |
| Copy/text | タイプミスを修正し、メッセージを即座に更新する |
| Images/assets | プロモーションバナーやアイコンを入れ替える |
| Lists/feeds | コンテンツの順序、フィルタリング、表示内容 |
| Navigation items | タブとメニューを追加/削除/並べ替える |
| Form fields | フィールドを追加/削除し、バリデーションを変更する |
| Onboarding flows | A/Bテストを行い、リリースなしで反復する |
| Pricing/plans | 価格を更新し、階層を追加する |
| App config | タイムアウト、閾値、制限 |
ネイティブである必要があるもの
- コアナビゲーション構造 (Expo Router の画面)
- ネイティブモジュールの統合
- パフォーマンスが重要なアニメーション
- オフラインファースト機能
実装パターン
// API は UI 構成を返します
interface HomeConfig {
sections: Section[]
featuredBanner?: Banner
quickActions: QuickAction[]
}
function HomeScreen() {
const { data: config } = useQuery({
queryKey: ['home', 'config'],
queryFn: () => api.get<HomeConfig>('/api/home/config'),
staleTime: 1000 * 60 * 5, // 5分キャッシュ
})
return (
<ScrollView>
{config?.featuredBanner && (
<Banner data={config.featuredBanner} />
)}
{config?.sections.map(section => (
<DynamicSection key={section.id} config={section} />
))}
</ScrollView>
)
}
Feature Flags
// lib/features.ts
import { useQuery } from '@tanstack/react-query'
interface FeatureFlags {
newCheckout: boolean
darkMode: boolean
betaFeatures: boolean
}
export function useFeatureFlags() {
return useQuery({
queryKey: ['features'],
queryFn: () => api.get<FeatureFlags>('/api/features'),
staleTime: 1000 * 60 * 15, // 15分
})
}
// 使用例
function CheckoutButton() {
const { data: flags } = useFeatureFlags()
if (flags?.newCheckout) {
return <NewCheckoutFlow />
}
return <LegacyCheckout />
}
Copy のためのリモート設定
// API からすべてのアプリのコピーを取得
const { data: copy } = useQuery({
queryKey: ['copy', locale],
queryFn: () => api.get(`/api/copy/${locale}`),
})
// フォールバックで使用
<Text>{copy?.welcomeMessage ?? 'Welcome!'}</Text>
サーバー駆動型リスト
// API は表示内容と順序を制御します
interface FeedConfig {
items: FeedItem[]
layout: 'grid' | 'list'
columns?: number
}
function Feed() {
const { data } = useQuery<FeedConfig>({
queryKey: ['feed'],
queryFn: () => api.get('/api/feed'),
})
if (data?.layout === 'grid') {
return <GridView items={data.items} columns={data.columns} />
}
return <ListView items={data.items} />
}
キャッシュ戦略
鮮度とオフラインサポートのバランスを取ります。
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5分間は新しい
gcTime: 1000 * 60 * 60 * 24, // 24時間キャッシュに保持
},
},
})
プロジェクト構成
app/
├── (tabs)/
│ ├── _layout.tsx
│ ├── index.tsx
│ └── profile.tsx
├── (auth)/
│ ├── _layout.tsx
│ ├── login.tsx
│ └── register.tsx
├── _layout.tsx
└── +not-found.tsx
components/
├── ui/
│ ├── Button.tsx
│ └── Input.tsx
└── [feature]/
hooks/
lib/
constants/
assets/
├── images/
└── fonts/
ナビゲーション
Expo Router パターン
// app/_layout.tsx - ルートレイアウト
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
</Stack>
)
}
// app/(tabs)/_layout.tsx - タブナビゲーション
import { Tabs } from 'expo-router'
import { Home, User } from 'lucide-react-native'
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <Home color={color} />,
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color }) => <User color={color} />,
}}
/>
</Tabs>
)
}
ナビゲーションアクション
import { router } from 'expo-router'
// ナビゲート
router.push('/profile')
router.replace('/home')
router.back()
// パラメータ付き
router.push({
pathname: '/user/[id]',
params: { id: '123' }
})
コンポーネント
プラットフォーム固有のスタイル
import { Platform, StyleSheet } from 'react-native'
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS === 'ios' ? 50 : 30,
...Platform.select({
ios: { shadowColor: '#000' },
android: { elevation: 4 },
}),
},
})
セーフエリアの処理
import { SafeAreaView } from 'react-native-safe-area-context'
export function Screen({ children }) {
return (
<SafeAreaView className="flex-1 bg-white dark:bg-gray-900">
{children}
</SafeAreaView>
)
}
TouchableOpacity よりも Pressable
import { Pressable, Text } from 'react-native'
<Pressable
onPress={handlePress}
className="active:opacity-70 bg-blue-500 px-4 py-2 rounded-lg"
>
<Text className="text-white font-semibold">Press Me</Text>
</Pressable>
モバイルでの画像
Expo Image の使用
React Native の Image よりも expo-image を優先します。
import { Image } from 'expo-image'
<Image
source={{ uri: 'htt 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Mobile Development Skill
Use this skill for all Adynato mobile projects built with React Native and Expo.
Stack
- Framework: Expo (managed workflow preferred)
- Navigation: Expo Router (file-based routing)
- Styling: NativeWind (Tailwind for React Native)
- State: Zustand or React Context
- Data Fetching: TanStack Query
API-Driven UI (Critical)
Maximize API-driven content to enable instant updates without App Store review.
App Store review can take 1-7 days. Any UI that can be server-controlled should be, so you can update instantly.
What Should Be API-Driven
| Component | Why |
|---|---|
| Feature flags | Enable/disable features without release |
| Copy/text | Fix typos, update messaging instantly |
| Images/assets | Swap promotional banners, icons |
| Lists/feeds | Content order, filtering, what's shown |
| Navigation items | Add/remove/reorder tabs and menus |
| Form fields | Add/remove fields, change validation |
| Onboarding flows | A/B test, iterate without releases |
| Pricing/plans | Update pricing, add tiers |
| App config | Timeouts, thresholds, limits |
What Must Be Native
- Core navigation structure (Expo Router screens)
- Native module integrations
- Performance-critical animations
- Offline-first functionality
Implementation Pattern
// API returns UI configuration
interface HomeConfig {
sections: Section[]
featuredBanner?: Banner
quickActions: QuickAction[]
}
function HomeScreen() {
const { data: config } = useQuery({
queryKey: ['home', 'config'],
queryFn: () => api.get<HomeConfig>('/api/home/config'),
staleTime: 1000 * 60 * 5, // Cache 5 min
})
return (
<ScrollView>
{config?.featuredBanner && (
<Banner data={config.featuredBanner} />
)}
{config?.sections.map(section => (
<DynamicSection key={section.id} config={section} />
))}
</ScrollView>
)
}
Feature Flags
// lib/features.ts
import { useQuery } from '@tanstack/react-query'
interface FeatureFlags {
newCheckout: boolean
darkMode: boolean
betaFeatures: boolean
}
export function useFeatureFlags() {
return useQuery({
queryKey: ['features'],
queryFn: () => api.get<FeatureFlags>('/api/features'),
staleTime: 1000 * 60 * 15, // 15 min
})
}
// Usage
function CheckoutButton() {
const { data: flags } = useFeatureFlags()
if (flags?.newCheckout) {
return <NewCheckoutFlow />
}
return <LegacyCheckout />
}
Remote Config for Copy
// Fetch all app copy from API
const { data: copy } = useQuery({
queryKey: ['copy', locale],
queryFn: () => api.get(`/api/copy/${locale}`),
})
// Use with fallback
<Text>{copy?.welcomeMessage ?? 'Welcome!'}</Text>
Server-Driven Lists
// API controls what appears and in what order
interface FeedConfig {
items: FeedItem[]
layout: 'grid' | 'list'
columns?: number
}
function Feed() {
const { data } = useQuery<FeedConfig>({
queryKey: ['feed'],
queryFn: () => api.get('/api/feed'),
})
if (data?.layout === 'grid') {
return <GridView items={data.items} columns={data.columns} />
}
return <ListView items={data.items} />
}
Cache Strategy
Balance freshness with offline support:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // Fresh for 5 min
gcTime: 1000 * 60 * 60 * 24, // Keep in cache 24h
},
},
})
Project Structure
app/
├── (tabs)/
│ ├── _layout.tsx
│ ├── index.tsx
│ └── profile.tsx
├── (auth)/
│ ├── _layout.tsx
│ ├── login.tsx
│ └── register.tsx
├── _layout.tsx
└── +not-found.tsx
components/
├── ui/
│ ├── Button.tsx
│ └── Input.tsx
└── [feature]/
hooks/
lib/
constants/
assets/
├── images/
└── fonts/
Navigation
Expo Router Patterns
// app/_layout.tsx - Root layout
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
</Stack>
)
}
// app/(tabs)/_layout.tsx - Tab navigation
import { Tabs } from 'expo-router'
import { Home, User } from 'lucide-react-native'
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <Home color={color} />,
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color }) => <User color={color} />,
}}
/>
</Tabs>
)
}
Navigation Actions
import { router } from 'expo-router'
// Navigate
router.push('/profile')
router.replace('/home')
router.back()
// With params
router.push({
pathname: '/user/[id]',
params: { id: '123' }
})
Components
Platform-Specific Styles
import { Platform, StyleSheet } from 'react-native'
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS === 'ios' ? 50 : 30,
...Platform.select({
ios: { shadowColor: '#000' },
android: { elevation: 4 },
}),
},
})
Safe Area Handling
import { SafeAreaView } from 'react-native-safe-area-context'
export function Screen({ children }) {
return (
<SafeAreaView className="flex-1 bg-white dark:bg-gray-900">
{children}
</SafeAreaView>
)
}
Pressable Over TouchableOpacity
import { Pressable, Text } from 'react-native'
<Pressable
onPress={handlePress}
className="active:opacity-70 bg-blue-500 px-4 py-2 rounded-lg"
>
<Text className="text-white font-semibold">Press Me</Text>
</Pressable>
Images in Mobile
Using Expo Image
Prefer expo-image over React Native's Image:
import { Image } from 'expo-image'
<Image
source={{ uri: 'https://example.com/image.webp' }}
style={{ width: 200, height: 200 }}
contentFit="cover"
placeholder={blurhash}
transition={200}
/>
Local Images
import { Image } from 'expo-image'
<Image
source={require('@/assets/images/logo.png')}
style={{ width: 100, height: 100 }}
/>
Native APIs
Permissions Pattern
import * as Location from 'expo-location'
async function requestLocation() {
const { status } = await Location.requestForegroundPermissionsAsync()
if (status !== 'granted') {
// Handle denial gracefully
return null
}
return await Location.getCurrentPositionAsync({})
}
Secure Storage
import * as SecureStore from 'expo-secure-store'
// Store sensitive data
await SecureStore.setItemAsync('token', authToken)
// Retrieve
const token = await SecureStore.getItemAsync('token')
// Delete
await SecureStore.deleteItemAsync('token')
Performance
List Optimization
import { FlashList } from '@shopify/flash-list'
<FlashList
data={items}
renderItem={({ item }) => <ItemCard item={item} />}
estimatedItemSize={100}
keyExtractor={(item) => item.id}
/>
Memoization
import { memo, useCallback, useMemo } from 'react'
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
const processed = useMemo(() => processData(data), [data])
const handlePress = useCallback(() => { ... }, [])
return <View>...</View>
})
Testing
Component Testing
import { render, fireEvent } from '@testing-library/react-native'
import { Button } from '@/components/ui/Button'
test('Button calls onPress', () => {
const onPress = jest.fn()
const { getByText } = render(<Button onPress={onPress}>Click</Button>)
fireEvent.press(getByText('Click'))
expect(onPress).toHaveBeenCalled()
})
Build & Deploy
EAS Build
# Development build
eas build --profile development --platform ios
# Production
eas build --profile production --platform all
Environment Variables
// Use expo-constants for env vars
import Constants from 'expo-constants'
const apiUrl = Constants.expoConfig?.extra?.apiUrl
Checklist
Before releasing:
- [ ] UI is API-driven where possible (copy, config, feature flags)
- [ ] Tested on both iOS and Android
- [ ] Safe area handling on all screens
- [ ] Keyboard avoiding views where needed
- [ ] Loading and error states for all async operations
- [ ] Offline handling considered
- [ ] Deep linking configured
- [ ] App icons and splash screen set
- [ ] Performance profiled (no jank)