◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/アプリ開発
◇ アプリ開発/2026-06-27中級

アップデート後の「新着情報」を、しつこくなく一度だけ出す設計 — バージョン境界とseen状態の作り方

更新後に出すアプリ内「新着情報」画面が、新規インストールにまで出たり毎回出たりして嫌われる問題を、バージョン境界とseen状態の設計で一度だけ確実に届ける。Rork(Expo)の実コードと、複数アプリで使い回す型までまとめます。

Rork585Expo219オンボーディング10リテンション設計リリース運用6

✦ プレミアム記事

新機能を入れても気づかれない、という壁に個人開発で何度もぶつかってきました。App Store の「このバージョンの新機能」欄はほとんど読まれませんし、プッシュ通知は許可していない人には届きません。残る確実な接点は、更新後に最初に開いたその瞬間の画面です。リテンションに効く機能ほど、ここで気づいてもらえるかどうかが効いてきます。

ところがこの「新着情報」画面は、作り方を一つ間違えると一気に嫌われます。インストールしたばかりの人にまで「新機能のお知らせ」が出る、開くたびに同じ案内が出る、こうなると体験を損ねるだけです。これは私自身が初期に踏んだ落とし穴でもあります。境界を正しく引いて「更新した人に、一度だけ」を成立させる設計をまとめます。

「更新した人にだけ」をどう判定するか

判定に必要なのは二つの値です。いま動いているアプリの「現在バージョン」と、このユーザーが「前回どのバージョンを使っていたか」。後者を毎回保存しておき、起動時に前者と突き合わせます。

  • 保存値が無い → 新規インストール。新着情報は出さない。
  • 保存値 < 現在 → 更新された。新着情報を出す候補。
  • 保存値 = 現在 → 同じバージョンで再起動しただけ。出さない。

現在バージョンは expo-application から取得します。app.json の version を手で持ち回るより確実です。

// lib/appVersion.ts
import * as Application from "expo-application";
 
// "1.4.0" のような表示用バージョン。null になり得るのでフォールバックを置く。
export const CURRENT_VERSION = Application.nativeApplicationVersion ?? "0.0.0";
 
// 文字列バージョンを比較可能な配列にする素朴なセムバー比較。
export function isNewer(a: string, b: string): boolean {
  const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
  const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
  for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
    const x = pa[i] ?? 0;
    const y = pb[i] ?? 0;
    if (x !== y) return x > y;
  }
  return false;
}

前回バージョンの記録と判定をまとめる

判定ロジックは小さな関数一つに閉じ込めます。ここで大事なのは「新着情報を出すか」と「前回バージョンを更新するか」を分けて考えることです。前回バージョンの保存は、新着情報を出したかどうかに関わらず、必ず行います。さもないと、新着情報のデータがまだ無い更新でも、次の本命の更新時に「2バージョン分まとめて」案内が出てしまいます。これが見落としやすい注意点です。

// lib/whatsNew.ts
import AsyncStorage from "@react-native-async-storage/async-storage";
import { CURRENT_VERSION, isNewer } from "./appVersion";
 
const LAST_VERSION_KEY = "whatsNew.lastVersion";
 
export type WhatsNewDecision =
  | { show: false }
  | { show: true; fromVersion: string; toVersion: string };
 
export async function evaluateWhatsNew(): Promise<WhatsNewDecision> {
  const last = await AsyncStorage.getItem(LAST_VERSION_KEY);
 
  // 前回バージョンは常に最新へ更新する(判定の後始末を先に確定)。
  const persist = () =>
    AsyncStorage.setItem(LAST_VERSION_KEY, CURRENT_VERSION);
 
  // 新規インストール: 記録だけ残して何も出さない。
  if (!last) {
    await persist();
    return { show: false };
  }
 
  // 同一 or 何らかの理由で現在が古い: 出さない。
  if (!isNewer(CURRENT_VERSION, last)) {
    await persist();
    return { show: false };
  }
 
  // 更新された: 出す候補。fromVersion を呼び出し側に渡す。
  const decision: WhatsNewDecision = {
    show: true,
    fromVersion: last,
    toVersion: CURRENT_VERSION,
  };
  await persist();
  return decision;
}

新規インストールで persist() だけして show: false を返すのが肝です。これで初回は静かに記録だけ取り、次回以降の更新から正しく一度だけ出せます。この挙動は実機の新規インストールで必ず確認することを推奨します。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦新規インストールには出さず、更新したユーザーにだけ一度出すための『前回バージョン記録』と判定ロジックの実装
✦expo-application で現在バージョンを取り、seen状態をバージョン単位で持つ取りこぼしのない仕組み
✦壁紙アプリを6本並行運用する立場での、新着情報データを一箇所に集約して各アプリへ流す共有設計
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

◇ アプリ開発2026-09-18
prebuild が requires the standard Expo SDK 57 Swift AppDelegate で止まったとき、まず確かめる3行
iOS 27 のために ios.enableSceneSupport を有効にすると prebuild が止まることがあります。原因は SDK の版ではなく AppDelegate.swift の形でした。確かめる3行と、手で書くときに SDK 58 の手順を真似てはいけない理由をまとめます。
◇ アプリ開発2026-09-16
消したはずのトークンが再起動で戻る — SecureStore の削除失敗を検知する設計
サインアウト直後に getItemAsync が null を返しても、Android のディスクには値が残っていることがあります。削除の成否を読み出しで確かめていた実装を、戻り値で判断する形へ書き直した記録です。
◇ アプリ開発2026-09-10
Rork のリポジトリに置く最初の EAS Workflow と、失敗した回にだけ届かない通知
Rork から書き出したリポジトリに .eas/workflows を二本置くまでの手順と、needs でつないだ通知が失敗した回にだけ届かなくなる理由を、手元で走る検査スクリプトの実行結果つきで書き残します。