個人開発として壁紙アプリを複数運営してきて、いちばん認識が変わったのは「リリースは出発点でしかない」という点でした。ストアに並んだ初日が成果のように感じられますが、実際にはそこからの数ヶ月で、伸びるアプリと静かに沈むアプリにくっきり分かれていきます。
実装そのものは、Rorkの登場でかなり身軽になりました。自然言語で画面と機能を渡せば、動くReact Nativeのコードが返ってきます。ただ、作りやすくなった分だけ似たアプリも増えました。そこで効いてくるのは、最初の作り込みではなく「公開した後、毎日どう手を入れ続けるか」のほうです。
ここでは、Rorkで壁紙アプリを組んだ前提で、運用に入ってから実際に効いた三つの領域 — コンテンツ供給の鮮度、初週の継続率、痩せない収益設計 — を、私が運営の中で調整してきた実装メモとして共有します。新規に作る方にも、すでに運営していて頭打ちを感じている方にも、手を動かせる粒度でお届けできればと思っています。
鮮度がDAUを支える — 予約投稿で「毎日少し増える」を作る
壁紙アプリのリテンションで、もっとも素直に効くのは「行くたびに少し新しい」状態です。一度に200枚を入れて満足してしまうと、ユーザーは数日で全部見終え、戻ってこなくなります。逆に、同じ200枚でも毎日5枚ずつ解禁すると、開くたびに小さな発見が生まれます。
これを運用で支えるのが予約投稿です。コンテンツ側に公開日時を持たせ、その時刻を過ぎたものだけを配信する。アプリ側のコードは一切変えずに、データの公開日時を未来にしておくだけで「毎日少しずつ増える」状態を作れます。
-- Supabase: 壁紙テーブル。published_at で予約配信を制御する
create table wallpapers (
id uuid default gen_random_uuid() primary key,
title text not null,
category text not null check (
category in ('nature', 'city', 'abstract', 'minimal')
),
tags text[] default '{}',
-- サムネと本体を分けて通信量を抑える
thumbnail_url text not null, -- 低解像度プレビュー (400×700px 程度)
full_url text not null, -- 高解像度本体 (1080×1920px 以上)
is_premium boolean default false,
is_active boolean default true,
published_at timestamptz default now(), -- 未来日時にすると予約投稿になる
download_count integer default 0,
created_at timestamptz default now()
);
-- RLS: 公開済み(published_at <= now)かつ有効なものだけ読める
alter table wallpapers enable row level security;
create policy "Public wallpapers are readable when published"
on wallpapers for select
using (is_active = true and published_at <= now());ポイントは、配信の絞り込みをアプリ側のロジックではなくRLS(Row Level Security)に置くことです。クエリ側で where published_at <= now() を書き忘れても、ポリシーが未来分を返さないため、未公開の壁紙が事故で表に出ることがありません。私は以前、クライアントのフィルタだけに頼っていて、キャッシュ越しに未公開分が一瞬見えてしまった経験があり、それ以来この二重化を必ず入れています。
新着を目立たせる導線も、データ側で完結させておくと運用が楽になります。
// 「新着」タブ: 直近7日に公開されたものを新しい順で
async function fetchNewArrivals(supabase: SupabaseClient) {
const sevenDaysAgo = new Date(
Date.now() - 7 * 24 * 60 * 60 * 1000
).toISOString();
const { data, error } = await supabase
.from("wallpapers")
.select("id, title, thumbnail_url, is_premium, published_at")
.gte("published_at", sevenDaysAgo)
.lte("published_at", new Date().toISOString()) // 念のため未来を除外
.order("published_at", { ascending: false })
.limit(30);
if (error) throw error;
return data;
}published_at の未来分を lte で改めて除外しているのは、サーバー時刻とクライアント時刻のずれを吸収するためです。RLSがあるので原則不要ですが、新着タブのように「日時で並べる」画面では、表示順の事故を避けるために明示しておくと安心できます。
運用としては、月初にまとめて作った壁紙の published_at を1日数枚ずつ未来に散らしておくだけです。これで「毎日更新しているアプリ」の体験を、毎日手作業で投稿せずに維持できます。私の場合、この予約投稿に切り替えてから、再訪のリズムが目に見えて安定しました。
初週で離れさせない — オンボーディングと継続のきっかけ
ダウンロードされても、最初の数分で「これは自分には合わない」と思われると、その人はほぼ戻ってきません。壁紙アプリの初週離脱は、機能の不足よりも「最初の体験が地味すぎる」ことで起きがちです。
私が効果を感じたのは、起動直後にカテゴリを選ばせる軽いオンボーディングでした。好みを一度聞くだけで、ホームの初期表示をその人向けに寄せられます。聞く項目は欲張らず、1画面で終わる範囲に抑えます。
// 初回起動時に好みカテゴリを1つだけ選ばせる
function OnboardingCategory({ onDone }: { onDone: (c: string) => void }) {
const categories = [
{ key: "nature", label: "自然" },
{ key: "minimal", label: "ミニマル" },
{ key: "abstract", label: "抽象" },
{ key: "city", label: "都市" },
];
return (
<View style={styles.wrap}>
<Text style={styles.heading}>好きな雰囲気を選んでください</Text>
<Text style={styles.sub}>あとから変えられます</Text>
<View style={styles.grid}>
{categories.map((c) => (
<Pressable
key={c.key}
style={styles.card}
onPress={() => onDone(c.key)}
>
<Text style={styles.cardLabel}>{c.label}</Text>
</Pressable>
))}
</View>
</View>
);
}「あとから変えられます」の一文を添えるのは、選択のプレッシャーを下げるためです。最初の選択を重く感じさせると、そこで離脱する人が出ます。気軽に選べる空気を作っておくほうが、結果的に先へ進んでくれます。
継続のきっかけとして、お気に入りと通知は素直に効きます。ただし通知は嫌われやすいので、送る理由を「あなたが好きなカテゴリに新作が来た」に限定するのが私の基準です。全員に同じ宣伝通知を送ると、一気に通知オフかアンインストールに振れます。
// お気に入りカテゴリに新作が出たときだけ通知を予約する
async function scheduleNewArrivalNotice(
favoriteCategory: string,
newCount: number
) {
if (newCount <= 0) return;
await Notifications.scheduleNotificationAsync({
content: {
title: "新しい壁紙が届きました",
body: `「${categoryLabel(favoriteCategory)}」に${newCount}枚追加されました`,
data: { category: favoriteCategory },
},
// 翌朝8時など、開かれやすい時間に寄せる
trigger: nextMorningAt(8),
});
}通知から開いたユーザーを、トップではなく「その人のお気に入りカテゴリの新着」に着地させるのも忘れないようにします。通知の文言と着地先が噛み合っていないと、せっかく開いてもらっても期待外れになり、次から開かれなくなります。
継続率を見るときは、インストール初日を基準にした残存率で追うのが分かりやすいです。下の目安は、壁紙系で「この辺りなら健全」と私が感じている水準です。
| 指標 | 苦戦している目安 | 健全と感じる目安 |
|---|---|---|
| 翌日継続率 (D1) | 20%未満 | 30%以上 |
| 7日継続率 (D7) | 5%未満 | 12%以上 |
| 初週の平均セッション数 | 2回未満 | 4回以上 |
数字そのものより、予約投稿やオンボーディングを入れる前後でこの残存率がどう動くかを見るほうが、改善の手応えをつかみやすいです。