子ども向けアプリを初めてリリースしたとき、審査で3回リジェクトされました。最初のリジェクト理由は「ペアレンタルゲートがない」。2回目は「外部サイトへのリンクを含む機能が Kids カテゴリに含まれている」。3回目は「AdMob の広告フォーマットが Kids カテゴリの規約に準拠していない」でした。
一般アプリの審査を数十本経験していても、Kids カテゴリには独自のルールがあります。Apple のドキュメントを読んでいたのに、です。ポイントは「ドキュメントに書いてあること」と「審査で実際に見られること」の間にあるグレーゾーンで、経験を積んだ開発者でも最初は詰まりやすいのです。
このガイドでは、Rork Max を使って子ども向けアプリを App Store の Kids カテゴリに公開するための全工程を解説します。特に「どこで詰まるか」を先に示しながら、対処法を実装コードとともに示す構成にしています。
App Store Kids カテゴリが一般アプリと根本的に違う理由
App Store には「Kids」という特別なカテゴリがあります。対象は 6歳未満 / 6〜8歳 / 9〜11歳の3区分で、それぞれ許可されるコンテンツと実装の要件が異なります。
なぜ Kids カテゴリが別扱いなのかというと、子どもを守るための法律(主に米国の COPPA)と Apple 独自のガイドラインが重なっているからです。
主な違いをまとめると以下の通りです。
Kids カテゴリ固有の制約:
- ペアレンタルゲート(保護者が確認できる認証機構)が必須の場面が多い
- アプリ内・広告内を問わず、外部サイトへのリンクは全面禁止(または保護者確認が必要)
- 第三者データ収集・行動追跡 SDK の使用は COPPA 準拠のものに限定
- 全広告は Kids-safe(COPPA 準拠の子ども向けフォーマット)のみ許可
- ソーシャル機能(チャット・友達申請・レビュー投稿等)は原則禁止または大幅な制限
年齢区分と厳格なコンテンツ規制
Apple の Kids カテゴリに申請する際は、アプリの対象年齢区分を設定します。それぞれのコンテンツ規制は下記の通りです。
- 6歳未満: 最も厳格。マンガ的な暴力表現も排除。シンプルな教育コンテンツのみ
- 6〜8歳: ファンタジー的な競争ゲームは許可。チャット・SNS 要素は禁止
- 9〜11歳: 軽微なユーモア・競争要素は許可。課金は Ask to Buy を通す必要あり
ここで重要なのは、対象外の年齢区分のユーザーがアプリを使えないようにする仕組みをアプリ内で実装することではなく、コンテンツが設定した区分に適合していることを審査官が確認できる状態にしておくことです。
第三者データ収集の禁止原則
COPPA(Children's Online Privacy Protection Act)は米国の連邦法ですが、実質的に全世界の App Store に影響します。
COPPA の核心は「13歳未満の子どものオンライン個人情報収集は、保護者の明示的な同意がない限り禁止」という点です。
これが実装に影響するのは、主に以下のシーンです。
- Firebase Analytics、Facebook SDK、Amplitude などのアナリティクス SDK を使っている場合
- AdMob を使っている場合(設定変更が必要)
- ユーザーアカウント(メールアドレス、生年月日等)を収集する場合
- クラッシュレポート(Crashlytics, Sentry 等)を使っている場合
Rork Max のデフォルトでは Firebase Analytics が組み込まれることがありますが、Kids カテゴリのアプリでは COPPA モード を有効にするか、代替のプライバシー準拠 SDK に差し替える必要があります。
COPPA 対応の実装チェックリスト
具体的な実装に入りましょう。COPPA 準拠で最低限対応しておきたいのは次の3点です。
Firebase Analytics の COPPA モード設定
Firebase Analytics を使い続ける場合は、COPPA に準拠した設定を有効にします。
// app.ts または App.tsx の初期化部分
import analytics from '@react-native-firebase/analytics';
// アプリ起動時に一度だけ実行
async function initAnalyticsForKids() {
// COPPA に準拠した収集モードを有効化
// このメソッドを呼ぶと以下が自動的に無効化される:
// - 広告識別子の収集 (IDFA/GAID)
// - ユーザーの年齢・性別推定
// - クロスアプリ追跡
await analytics().setConsent({
ad_personalization: false,
ad_storage: false,
ad_user_data: false,
analytics_storage: false,
});
// Kids アプリではユーザーIDの収集自体を停止
await analytics().setUserId(null);
// 子ども向けアプリフラグを設定(Firebase SDK 10.x 以降)
// この設定を入れると広告関連の自動イベントが全て無効化される
await analytics().setDefaultEventParameters({
app_audience_type: 'children',
});
}ただし、私の経験上、Kids カテゴリのアプリでは Firebase Analytics を完全に除外する ほうが審査がシンプルになります。代わりに使えるプライバシー準拠のアナリティクスとして、Apple の SKAdNetwork ベースの計測や、データをデバイスに留める PostHog のセルフホスト版があります。
AdMob の Kids Safe 設定
AdMob を収益化に使っている場合は、Kids カテゴリの広告設定が必須です。デフォルトの広告フォーマットをそのまま使うと、行動ターゲティング広告が配信されてしまい、COPPA 違反になります。
// AdMob の初期化設定
import MobileAds, {
MaxAdContentRating,
} from 'react-native-google-mobile-ads';
async function initAdMobForKids() {
// 子ども向け広告の設定
await MobileAds().setRequestConfiguration({
// 最も厳格なコンテンツレーティングに設定
maxAdContentRating: MaxAdContentRating.G,
// 子ども向けコンテンツとして広告をリクエスト
// これにより以下が保証される:
// - 行動ターゲティングが無効化
// - COPPA 準拠のコンテンツのみ配信
tagForChildDirectedTreatment: true,
tagForUnderAgeOfConsent: true,
});
await MobileAds().initialize();
}
// 広告ユニットの作成時
// Kids カテゴリで推奨される広告フォーマット: バナー広告のみ
// 禁止フォーマット: インタースティシャル(画面遷移を強制するため禁止)
const bannerAdConfig = {
adUnitId: 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX',
size: BannerAdSize.BANNER,
requestOptions: {
requestNonPersonalizedAdsOnly: true, // 必須
networkExtras: {
max_ad_content_rating: 'G',
},
},
};よくある失敗: tagForChildDirectedTreatment: true を設定しても、AdMob ダッシュボードの「コンテンツの設定」で Adult Content が許可になっていると設定が上書きされることがあります。ダッシュボード側の設定も必ず確認してください。
収集するデータの最小化
Kids カテゴリのアプリで収集して良いデータは、アプリの機能を実現するために必要なものだけです。
// ユーザーデータの保存設計
// Bad: メールアドレスや生年月日を収集するアカウント登録
const badRegistration = {
email: 'user@example.com', // Kids アプリでは原則禁止
birthdate: '2018-01-01', // 年齢特定につながるデータ禁止
};
// Good: デバイスローカルに保存する匿名の進行管理
import AsyncStorage from '@react-native-async-storage/async-storage';
async function saveProgressLocally(level: number, score: number) {
// デバイスに紐づく匿名データのみ保存
// サーバーには送信しない
await AsyncStorage.setItem('game_progress', JSON.stringify({
level,
highScore: score,
lastPlayed: new Date().toISOString(),
}));
}サーバーサイドにデータを保存する場合は、保護者の明示的な同意を取得するフローが別途必要になります。設計を単純にするため、Kids カテゴリの多くのアプリはデバイスローカルに完結させています。
ペアレンタルゲートの実装
ペアレンタルゲートは、「子どもが意図せず操作できない」保護者向け確認機構です。Kids カテゴリのアプリでは、以下の操作に対してペアレンタルゲートの設置が求められます。
- アプリ外のリンク(サポートサイト、SNS 等)
- 有料コンテンツや課金画面への誘導
- 他のアプリへの遷移(App Store ページ含む)
- 外部サービスへのログイン
Apple が求める「知識ベースの確認」
ペアレンタルゲートの重要な要件は、子どもには回答が難しい知識を求めることです。
以下のものは Apple 審査で「ペアレンタルゲートとして機能しない」と判断されます。
- 数字のタップのみ(子どもでも回答できる)
- 単純なパターン確認
- 絵柄のマッチング(子どもが得意)
有効なペアレンタルゲートの例として、算数の計算問題(2桁×2桁)やテキスト入力が代表的です。
React Native での実装(完全コード)
// ParentalGate.tsx — 計算問題を使ったペアレンタルゲートの実装例
import React, { useState, useMemo } from 'react';
import {
View, Text, TextInput, TouchableOpacity, Modal, StyleSheet, Alert
} from 'react-native';
interface ParentalGateProps {
visible: boolean;
onSuccess: () => void;
onCancel: () => void;
}
function generateMathQuestion(): { question: string; answer: number } {
// 2桁×2桁の掛け算(大人なら計算できる、子どもには難しい)
const a = Math.floor(Math.random() * 70) + 20; // 20〜89
const b = Math.floor(Math.random() * 70) + 20; // 20〜89
return { question: `${a} × ${b}`, answer: a * b };
}
export const ParentalGate: React.FC<ParentalGateProps> = ({
visible, onSuccess, onCancel,
}) => {
const [inputValue, setInputValue] = useState('');
const [attempts, setAttempts] = useState(0);
// Modalが開くたびに新しい問題を生成
const { question, answer } = useMemo(
() => generateMathQuestion(), [visible]
);
const handleSubmit = () => {
const userAnswer = parseInt(inputValue, 10);
if (userAnswer === answer) {
setInputValue('');
setAttempts(0);
onSuccess();
} else {
const newAttempts = attempts + 1;
setAttempts(newAttempts);
setInputValue('');
if (newAttempts >= 3) {
// 3回失敗でゲートを閉じる(ブルートフォース防止)
Alert.alert(
'確認できませんでした',
'保護者の方が確認してから再度お試しください。',
[{ text: 'OK', onPress: () => { setAttempts(0); onCancel(); } }]
);
} else {
Alert.alert('もう一度お試しください', `残り ${3 - newAttempts} 回試せます。`);
}
}
};
return (
<Modal visible={visible} transparent animationType="fade" onRequestClose={onCancel}>
<View style={styles.overlay}>
<View style={styles.container}>
<Text style={styles.title}>保護者の方へ</Text>
<Text style={styles.subtitle}>
この先に進むには保護者の確認が必要です。
</Text>
<View style={styles.questionBox}>
<Text style={styles.questionLabel}>次の計算の答えを入力してください:</Text>
<Text style={styles.question}>{question} = ?</Text>
</View>
<TextInput
style={styles.input}
keyboardType="numeric"
value={inputValue}
onChangeText={setInputValue}
placeholder="答えを入力"
maxLength={6}
autoFocus
/>
<View style={styles.buttonRow}>
<TouchableOpacity style={[styles.button, styles.cancelButton]} onPress={onCancel}>
<Text style={styles.cancelButtonText}>キャンセル</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.submitButton]}
onPress={handleSubmit}
disabled={!inputValue}
>
<Text style={styles.submitButtonText}>確認</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
);
};
const styles = StyleSheet.create({
overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center' },
container: { backgroundColor: '#fff', borderRadius: 16, padding: 24, width: '85%', maxWidth: 360 },
title: { fontSize: 20, fontWeight: '700', textAlign: 'center', marginBottom: 8 },
subtitle: { fontSize: 14, color: '#666', textAlign: 'center', marginBottom: 20, lineHeight: 20 },
questionBox: { backgroundColor: '#f5f5f5', borderRadius: 12, padding: 16, marginBottom: 16, alignItems: 'center' },
questionLabel: { fontSize: 13, color: '#555', marginBottom: 8 },
question: { fontSize: 28, fontWeight: '700', color: '#333' },
input: { borderWidth: 1.5, borderColor: '#ddd', borderRadius: 10, padding: 12, fontSize: 18, textAlign: 'center', marginBottom: 20 },
buttonRow: { flexDirection: 'row', gap: 12 },
button: { flex: 1, padding: 14, borderRadius: 10, alignItems: 'center' },
cancelButton: { backgroundColor: '#f0f0f0' },
cancelButtonText: { color: '#666', fontWeight: '600' },
submitButton: { backgroundColor: '#4A90E2' },
submitButtonText: { color: '#fff', fontWeight: '700' },
});このコンポーネントを外部リンクボタンにラップして使います。
// 外部リンクには必ずペアレンタルゲートを通す
import { Linking } from 'react-native';
import { useState } from 'react';
export const ExternalLinkButton = ({ url, label }: { url: string; label: string }) => {
const [gateVisible, setGateVisible] = useState(false);
return (
<>
<TouchableOpacity onPress={() => setGateVisible(true)}>
<Text>{label}</Text>
</TouchableOpacity>
<ParentalGate
visible={gateVisible}
onSuccess={() => {
setGateVisible(false);
Linking.openURL(url); // 保護者確認後にリンクを開く
}}
onCancel={() => setGateVisible(false)}
/>
</>
);
};よくあるペアレンタルゲートの実装ミス
実際の審査でリジェクトされたケースをもとに、避けるべきパターンを3つ示します。
ミス① 数字のタップのみで確認する
単純な数字タップは子どもでも突破できるため、Apple は「知識ベースの質問ではない」として却下します。必ず計算問題かテキスト入力を使ってください。
ミス② ゲートなしで外部リンクを配置する
「サポートページを開く」「プライバシーポリシーを確認する」のようなリンクも、Kids カテゴリではペアレンタルゲートを通す必要があります。唯一の例外は Apple がホストするページ(App Store の自社アプリページ等)ですが、これも念のためゲートを設置するほうが安全です。
ミス③ App Store の他アプリページへの遷移にゲートなし
「このアプリが気に入ったら、こちらもどうぞ」のような自社アプリ紹介も、Kids カテゴリでは必ずペアレンタルゲートを通してください。
子ども向けUI/UXデザインの実践原則
コードの話から少し離れて、Kids カテゴリで実際に評価されるUI設計の原則をまとめます。
タッチターゲットと操作ミス防止
子どもの指は大人より太く、精度が低いです。タッチターゲットのサイズは最低 60×60pt(Apple の推奨は 44×44pt ですが、子ども向けはより大きく)を目安にしてください。
// 子ども向けボタンスタイルの例
const kidsButtonStyle = {
width: 80,
height: 80,
borderRadius: 20, // 角丸を大きくして親しみやすく
justifyContent: 'center' as const,
alignItems: 'center' as const,
// 隣接するボタンとの間隔を最低 16pt 確保して誤タップを防ぐ
margin: 8,
};また、「削除」「リセット」などの取り返しのつかない操作は確認ダイアログを必ず挟むか、そもそも配置しないことをおすすめします。
音・アニメーション・達成感の設計
子ども向けアプリの品質は、視覚・聴覚フィードバックで大きく変わります。3点を押さえておくだけで体験が別物になります。
正解・達成時のポジティブフィードバック: 正解時にキラキラエフェクト + 効果音を入れる。React Native の Lottie や react-native-animatable を使うと実装が楽です。
繰り返し遊びたくなる設計: 子どもは同じコンテンツを何度も楽しめます。キャラクターのセリフやリアクションのバリエーションを3〜5種類用意するだけで滞在時間が増えます。
過剰な刺激を避ける: フラッシュ・激しい点滅は光感受性発作リスクがあるため使用禁止です。アニメーション速度は大人向けより 20〜30% 遅くするのが目安です。
外部リンクと広告の制限
- SNS シェアボタン: 禁止(外部サービスへの誘導のため)
- レビュー依頼の直接リンク: ペアレンタルゲート越しのみ許可
- バナー広告: COPPA 準拠フォーマットのみ。インタースティシャルは Kids カテゴリ禁止
広告を使わずに収益化したい場合、買い切り価格での提供か、保護者が購入するサブスクリプション(後述の Ask to Buy)が現実的な選択肢です。
Ask to Buy と課金設計
子どもは保護者のデバイスを使う場合があり、課金に制限がかかっていることがほとんどです。Kids カテゴリのアプリは「Ask to Buy」のシナリオを想定した設計が必要です。
Ask to Buy は、子どものデバイスから購入リクエストが送られ、保護者デバイスで承認されるまで保留になる仕組みです。
RevenueCat での Ask to Buy 対応
import Purchases, {
PURCHASES_ERROR_CODE,
PurchasesError,
} from 'react-native-purchases';
async function handleKidsPurchase(packageToPurchase) {
try {
const { customerInfo } = await Purchases.purchasePackage(packageToPurchase);
unlockPremiumContent(customerInfo);
} catch (error) {
const purchasesError = error as PurchasesError;
// Ask to Buy の保留状態 — 子どもが操作した場合にここに入ることが多い
if (purchasesError.code === PURCHASES_ERROR_CODE.PAYMENT_PENDING_ERROR) {
// 保護者が承認するまで保留中
showAskToBuyPendingUI();
// 表示例: 「お父さん・お母さんに確認リクエストを送りました」
return;
}
if (purchasesError.code === PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR) {
return; // キャンセルは静かに扱う
}
showPurchaseErrorUI(purchasesError.message);
}
}
// アプリ起動時: 保護者が承認後にアプリを再起動したケースに対応
async function checkPendingPurchases() {
try {
const customerInfo = await Purchases.getCustomerInfo();
if (customerInfo.entitlements.active['premium'] !== undefined) {
unlockPremiumContent(customerInfo);
}
} catch (error) {
// エラーは無視(ネットワーク不良時等)
}
}保留中の Ask to Buy がある場合のUI設計では、子どもに失望させない点が肝心です。「購入できませんでした」ではなく「お父さんお母さんに確認をお願いしました。承認されたら使えるようになります」という表現が体験として自然です。
Rork Max での Kids アプリ生成プロンプト設計
Rork Max で Kids カテゴリのアプリを作る際、プロンプトの書き方によって生成されるコードの品質が大きく変わります。
ペアレンタルゲート付き外部リンクのプロンプト例
以下の要件で React Native (Expo) のコンポーネントを生成してください:
要件:
- App Store の Kids カテゴリ向け「サポートページを開く」ボタン
- ボタンタップ時にペアレンタルゲート(2桁×2桁の掛け算クイズ)を表示
- 正解後のみ Linking.openURL で外部ページを開く
- 不正解は3回まで許可し、3回失敗したらゲートを閉じる
- TypeScript + React Native (Expo) で実装
- UIは子ども向け(大きなタッチターゲット・角丸デザイン)
計算問題は大人なら答えられるが子どもには難しい難易度を想定。
このように「なぜそうするか(Kids カテゴリの要件)」と「どう実装するか」の両方を含めると、Rork Max はより意図に沿ったコードを生成してくれます。
プロンプト設計のコツ
Kids アプリ開発で Rork Max をうまく使うための原則として、「Apple のガイドラインに準拠した実装を求める」という一文をプロンプトに追加する習慣が役立ちます。
// プロンプトに追加するフレーズ例
「App Review Guidelines の Kids Category セクションに準拠した実装にしてください」
「COPPA 準拠を考慮して、個人情報の収集を最小化した設計にしてください」
「ペアレンタルゲートを適切な場所に組み込んでください」
これらを追加するだけで、Rork Max が生成するコードに外部リンクや課金導線が含まれる際に、自動的にペアレンタルゲートを組み込んでくれる確率が上がります。
App Store 審査でよくあるリジェクト理由と対処法
最後に、実際の Kids カテゴリ審査で頻出するリジェクト理由をまとめます。
リジェクト1: ペアレンタルゲートの欠如
対処法: すべての外部リンク・課金導線・他アプリ誘導に本記事のペアレンタルゲートを実装します。審査前に「外部へのリンクがある場所を全て列挙する」レビューを必ず実施してください。一つ見落とすだけでリジェクトされます。
リジェクト2: 第三者 SDK がデータを収集している
対処法: Xcode の Privacy Manifest(PrivacyInfo.xcprivacy)に全 SDK の宣言が揃っているか確認してください。特に Firebase / Crashlytics / AdMob は Required Reason API を多数使用しています。COPPA 非準拠の SDK は Kids カテゴリから除外します。
リジェクト3: 広告フォーマットが不適切
対処法: AdMob のコンソールで、Kids アプリに設定している広告ユニット全てのコンテンツ設定が G レーティングであることを確認してください。インタースティシャル広告は Kids カテゴリでは絶対に使用しないでください。
リジェクト4: コンテンツが対象年齢区分に適していない
対処法: キャラクターの表情・言葉・BGMが、設定した年齢区分(例: 6歳未満)向けになっているか第三者に確認してもらいます。「怖い」「暗い」要素はスクリーンショットの段階で審査官が判断します。
リジェクト5: プッシュ通知の許可要求が不適切なタイミング
対処法: 子ども向けアプリでのプッシュ通知は慎重に扱います。設定画面など「保護者が開く場所」からのみ許可申請するのが安全です。ゲームプレイ中に突然ダイアログが出ると審査で指摘されます。
Kids アプリ開発を始めるファーストステップ
Kids カテゴリのアプリは、一般アプリより審査が厳しい分、競合も少なく、ニッチで熱心なユーザーに届けやすいという利点があります。
まず実践してほしいのは、既存のアプリをペアレンタルゲートから整備することです。本記事のコードをコピーして、外部リンクと課金導線に順番に組み込むだけでも、Kids カテゴリへの申請準備が大きく進みます。
設計の考え方をさらに深めたい方にはおすすめです。
Kids カテゴリは、市場の規模と参入の難易度のバランスが取れている、個人開発者にとって狙い目のジャンルのひとつだと感じています。今日から一歩踏み出してみてください。