jpskill.com
🛠️ 開発・MCP コミュニティ

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本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
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
🪟 Windows (PowerShell)
$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. 1. 下の青いボタンを押して adynato-mobile.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → adynato-mobile フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

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)