「無料プランを作ったのに誰も有料に転換してくれない」——この悩みを、個人開発者から何度も聞いてきました。私自身も同じ経験をして、試行錯誤を繰り返しました。
転換率が上がらない原因の多くは、「フリーミアムの設計が間違っている」ことにあります。ユーザーが無料のまま十分満足できるプロダクトを作っておきながら、「いつか有料にしてくれるだろう」と待っているのは、戦略ではなく願望です。
ここ数年、SaaS 業界で主流になってきた Product-Led Growth(PLG) という考え方と、その延長にある リバーストライアル の手法を Rork アプリに適用することで、転換率が劇的に変わることを実感しています。その考え方と具体的な実装を順を追って整理していきます。
PLG とは何か——そして従来のフリーミアムと何が違うか
PLG(Product-Led Growth)は一言でいえば「プロダクト自体が成長エンジンになる戦略」です。営業や広告に依存せず、ユーザーがプロダクトを使うこと自体が獲得・活性化・収益化を推進する構造を作ります。
従来のフリーミアムとの違いは 「制限の設計思想」 にあります。
従来のフリーミアム:
- 無料ユーザーには最初から機能を制限する
- 「もっと使いたければお金を払え」が基本メッセージ
- ユーザーは制限の壁を感じてフラストレーションを抱える
PLG 型フリーミアム:
- まず全機能を使わせ、価値を実感させる
- 「この価値を継続したければ払う」という自然な流れ
- ユーザーは「投資として払う価値がある」と判断して転換する
この違いは大きいです。制限型は「払わない理由」ばかりを与えますが、PLG 型は「払う理由」を先に体験させます。
リバーストライアルの設計
PLG の中でも特に効果的な手法が リバーストライアル です。通常のトライアルとの違いを整理します。
通常のトライアル: 無料プラン → 有料機能を期間限定で試せる → 期限後に制限へ戻る リバーストライアル: 最初から有料プラン相当の体験 → 一定期間後に無料プランへ移行 → 「この機能を使い続けたい」という実感から転換
心理的に決定的な違いがあります。通常のトライアルは「いつか得られるかもしれないもの」の期待ですが、リバーストライアルは「今持っているものを失う」という損失回避の心理が働きます。行動経済学の知見では、人は同じ価値でも「得られる可能性」より「失う可能性」に強く反応します。
Rork アプリでの実装例です。Supabase で user_tier を管理する構成です。
// hooks/useUserTier.ts
import { useEffect, useState } from 'react';
import { supabase } from '@/lib/supabase';
interface UserTier {
tier: 'reverse_trial' | 'free' | 'pro';
trialEndsAt: Date | null;
isTrialActive: boolean;
daysRemaining: number;
}
export function useUserTier(): UserTier {
const [tierData, setTierData] = useState<UserTier>({
tier: 'reverse_trial',
trialEndsAt: null,
isTrialActive: true,
daysRemaining: 14,
});
useEffect(() => {
const fetchTier = async () => {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const { data } = await supabase
.from('user_subscriptions')
.select('tier, trial_ends_at, created_at')
.eq('user_id', user.id)
.single();
if (data) {
const trialEndsAt = data.trial_ends_at
? new Date(data.trial_ends_at)
: null;
const now = new Date();
const isTrialActive = trialEndsAt ? trialEndsAt > now : false;
const daysRemaining = trialEndsAt
? Math.ceil((trialEndsAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
: 0;
setTierData({
tier: data.tier as UserTier['tier'],
trialEndsAt,
isTrialActive,
daysRemaining: Math.max(0, daysRemaining),
});
}
};
fetchTier();
}, []);
return tierData;
}ユーザーの初回サインアップ時に trial_ends_at を NOW() + INTERVAL '14 days' でセットするトリガーを Supabase に設定しておきます。
-- Supabase Edge Function or Database Trigger
CREATE OR REPLACE FUNCTION initialize_trial()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO user_subscriptions (user_id, tier, trial_ends_at)
VALUES (NEW.id, 'reverse_trial', NOW() + INTERVAL '14 days')
ON CONFLICT (user_id) DO NOTHING;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION initialize_trial();アクティベーションメトリクスの設計
リバーストライアルを機能させるには、ユーザーが「Aha Moment(アハモーメント)」に到達しているかどうか を把握する点が肝心です。Aha Moment とは、ユーザーがプロダクトの核心的な価値を実感する瞬間のことです。
Facebook は「10 日以内に 7 人の友人を追加する」、Slack は「チームで 2,000 通のメッセージを送る」といった Aha Moment の定義が有名です。あなたのアプリにも固有の Aha Moment があります。
Rork で作るアプリの場合、まず仮説を立てます。たとえば日記アプリなら「3 日連続で日記を書いたユーザーは 30 日後の継続率が 80% 以上」という仮説を立て、それを計測します。
// lib/activationTracker.ts
import * as Analytics from 'expo-firebase-analytics';
interface ActivationEvent {
eventName: string;
properties?: Record<string, string | number | boolean>;
}
class ActivationTracker {
private static instance: ActivationTracker;
private milestones: Map<string, boolean> = new Map();
static getInstance(): ActivationTracker {
if (!this.instance) {
this.instance = new ActivationTracker();
}
return this.instance;
}
async trackMilestone(userId: string, milestone: string): Promise<void> {
const key = `${userId}:${milestone}`;
if (this.milestones.get(key)) return; // 重複送信防止
this.milestones.set(key, true);
await Analytics.logEvent('activation_milestone', {
milestone,
user_id: userId,
timestamp: Date.now(),
});
// Supabase に永続化
await supabase.from('activation_milestones').upsert({
user_id: userId,
milestone,
achieved_at: new Date().toISOString(),
});
// Aha Moment 判定
await this.checkAhaMoment(userId);
}
private async checkAhaMoment(userId: string): Promise<void> {
const { data: milestones } = await supabase
.from('activation_milestones')
.select('milestone')
.eq('user_id', userId);
const achieved = new Set(milestones?.map(m => m.milestone) ?? []);
// アプリ固有の Aha Moment 定義
const AHA_MOMENT_CRITERIA = [
'first_core_action', // 核心機能を初めて使った
'repeated_use_3x', // 3 回連続で使った
'data_created_5_items', // データを 5 件作成した
];
const isAhaMoment = AHA_MOMENT_CRITERIA.every(c => achieved.has(c));
if (isAhaMoment) {
await Analytics.logEvent('aha_moment_reached', { user_id: userId });
await supabase.from('user_subscriptions')
.update({ aha_moment_at: new Date().toISOString() })
.eq('user_id', userId);
}
}
}
export const activationTracker = ActivationTracker.getInstance();「価値を届けた後」にペイウォールを出す
最も重要な設計原則は 「ユーザーが価値を実感した直後にペイウォールを表示する」 ことです。アプリ起動直後や、まだ何もできていないタイミングでのペイウォールは逆効果です。
転換率が高いタイミングの例:
- 核心機能を使い切って「もっとやりたい」と思った瞬間
- 初めて成果(作品・データ・結果)を保存しようとした瞬間
- 連続使用 X 日目に「習慣になった」と感じた瞬間
// components/SmartPaywall.tsx
import React, { useEffect, useState } from 'react';
import { useUserTier } from '@/hooks/useUserTier';
import { PaywallModal } from './PaywallModal';
interface SmartPaywallProps {
trigger: 'value_delivered' | 'feature_limit' | 'trial_ending';
onDismiss?: () => void;
}
export function SmartPaywall({ trigger, onDismiss }: SmartPaywallProps) {
const { tier, daysRemaining, isTrialActive } = useUserTier();
const [shouldShow, setShouldShow] = useState(false);
useEffect(() => {
const checkShouldShow = async () => {
if (tier === 'pro') return; // 課金済みユーザーには表示しない
switch (trigger) {
case 'value_delivered':
// Aha Moment に達したユーザーにのみ表示
const { data } = await supabase
.from('user_subscriptions')
.select('aha_moment_at, paywall_shown_after_aha')
.eq('user_id', currentUserId)
.single();
if (data?.aha_moment_at && !data?.paywall_shown_after_aha) {
setShouldShow(true);
// 次回から再表示しない
await supabase.from('user_subscriptions')
.update({ paywall_shown_after_aha: true })
.eq('user_id', currentUserId);
}
break;
case 'trial_ending':
// トライアル残り 3 日以内
if (isTrialActive && daysRemaining <= 3) {
setShouldShow(true);
}
break;
case 'feature_limit':
// 機能制限に到達した場合(フォールバック)
setShouldShow(!isTrialActive && tier === 'free');
break;
}
};
checkShouldShow();
}, [trigger, tier, daysRemaining, isTrialActive]);
if (!shouldShow) return null;
return (
<PaywallModal
trigger={trigger}
daysRemaining={daysRemaining}
onDismiss={() => {
setShouldShow(false);
onDismiss?.();
}}
/>
);
}value_delivered トリガーを Aha Moment 到達後に呼ぶことで、「この機能を気に入った今、継続するかを選んでほしい」という自然なタイミングでの訴求が可能になります。
トライアル期限通知の設計
リバーストライアルは「期限が近づいたとき」の通知設計も重要です。通知の出し方を間違えると、急かされていると感じたユーザーが離れてしまいます。
私がよいと思っているのは 3 段階の通知構成 です。
// lib/trialNotifications.ts
import * as Notifications from 'expo-notifications';
import { addDays, differenceInDays } from 'date-fns';
export async function scheduleTrialNotifications(
trialEndsAt: Date
): Promise<void> {
const now = new Date();
const daysLeft = differenceInDays(trialEndsAt, now);
// 通知 1: トライアル残り 7 日(価値の再認識)
if (daysLeft >= 7) {
await Notifications.scheduleNotificationAsync({
content: {
title: '使い続けていただいてありがとうございます',
body: 'プレミアム機能をお使いいただいて 1 週間。引き続き全機能をお楽しみください。',
data: { type: 'trial_reminder', daysLeft: 7 },
},
trigger: {
date: addDays(now, daysLeft - 7),
},
});
}
// 通知 2: トライアル残り 3 日(穏やかな促し)
if (daysLeft >= 3) {
await Notifications.scheduleNotificationAsync({
content: {
title: 'プレミアム機能があと 3 日ご利用いただけます',
body: '継続してお使いいただくには、プランをご確認ください。',
data: { type: 'trial_reminder', daysLeft: 3 },
},
trigger: {
date: addDays(now, daysLeft - 3),
},
});
}
// 通知 3: 期限前日(最後の選択)
await Notifications.scheduleNotificationAsync({
content: {
title: 'プレミアム機能は明日終了します',
body: '継続するか、無料プランに戻るか、今日ご判断いただけます。',
data: { type: 'trial_ending', daysLeft: 1 },
},
trigger: {
date: addDays(trialEndsAt, -1),
},
});
}通知 1 は「感謝」から始めます。「使ってくれていることへの敬意」が伝わると、ユーザーの心理的な警戒心が下がります。通知 3 は「急かす」のではなく「選択肢を提示する」表現にします。「今すぐ課金してください」ではなく「今日ご判断いただけます」という違いが、印象を大きく変えます。
無料プランへの降格体験の設計
リバーストライアルでは、トライアル終了後の「降格体験」の設計がほぼ最重要です。ここを雑にすると、ユーザーは不快に感じてアンインストールします。
よくある失敗: 「機能が突然使えなくなった」とエラーを出す 理想的な設計: 「以前作ったデータは全て保持する、新規作成のみ制限」
// hooks/useFeatureAccess.ts
export function useFeatureAccess(feature: string): {
canUse: boolean;
reason: 'active' | 'trial_active' | 'free_limit' | 'upgrade_needed';
itemsRemaining?: number;
} {
const { tier, isTrialActive } = useUserTier();
const FREE_LIMITS: Record<string, number> = {
'create_item': 10, // 無料プランは 10 件まで作成可
'export_pdf': 0, // 無料プランはエクスポート不可
'ai_suggestions': 3, // 無料プランは 1 日 3 回まで
};
if (tier === 'pro') {
return { canUse: true, reason: 'active' };
}
if (isTrialActive) {
return { canUse: true, reason: 'trial_active' };
}
// 無料プランの制限チェック
const limit = FREE_LIMITS[feature];
if (limit === undefined) {
return { canUse: true, reason: 'active' }; // 制限のない機能
}
if (limit === 0) {
return { canUse: false, reason: 'upgrade_needed' };
}
// 現在の使用量チェック(省略)
const currentUsage = 0; // 実際には Supabase から取得
const remaining = Math.max(0, limit - currentUsage);
return {
canUse: remaining > 0,
reason: remaining > 0 ? 'free_limit' : 'upgrade_needed',
itemsRemaining: remaining,
};
}重要なのは 「過去のデータは絶対に消さない」 という原則です。降格後も以前作ったものは参照できます。ただし新規作成には制限がかかる。この設計により「課金すれば制限が解除される」という明確な価値提案になります。
転換率を計測・改善するダッシュボード
PLG 戦略を機能させるには、指標の計測と改善サイクルが不可欠です。最低限追うべき指標です。
アクティベーション率: サインアップ後 X 日以内に Aha Moment に到達したユーザーの割合。私の経験では、Aha Moment 到達ユーザーの転換率は未到達ユーザーの 5〜8 倍になります。
トライアル転換率: トライアル終了ユーザーのうち有料に転換した割合。業界平均は 20〜25% ですが、PLG 設計が機能すれば 35% 以上を目指せます。
Time to Value(価値到達時間): サインアップから Aha Moment までの平均時間。これが短いほど転換率が上がります。
// analytics/plgMetrics.ts
export async function fetchPLGMetrics(period: '7d' | '30d' | '90d') {
const { data } = await supabase.rpc('get_plg_metrics', { period });
return {
activationRate: data.aha_moment_users / data.total_signups,
trialConversionRate: data.paid_conversions / data.trial_completions,
timeToValueMedianHours: data.median_time_to_aha_hours,
revenueFromPLGConversions: data.revenue_plg,
};
}これらを週次でモニタリングし、Aha Moment の定義を調整していくことが、長期的な転換率向上につながります。
実際の転換率の変化
私が運営しているアプリでリバーストライアルに切り替えた際の変化を共有します。
切り替え前(通常フリーミアム): 転換率 3.2% 切り替え後(14 日リバーストライアル): 転換率 8.7%
約 2.7 倍の改善です。特に効果が大きかったのは、Aha Moment 到達後のペイウォール表示で、このトリガーからの転換率は 40% 超でした。「価値を感じた直後に課金を促す」ことの重要性を実感しました。
Rork でアプリを作っている方に伝えたいのは、転換率の問題はほとんどの場合、プロダクトの問題ではなくタイミングの問題 だということです。よいアプリを作っておきながら、間違ったタイミングで課金を求めているだけのことが多い。
PLG とリバーストライアルを試してみてください。数字が変わります。