◉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-03-30中級

Rork × Trigger.dev でモバイルアプリのバックグラウンドジョブを自動化する方法

Rorkで開発したアプリにTrigger.devを組み合わせ、バックグラウンドジョブを自動化する実装ガイドです。コンテンツの自動更新、スケジュール通知の配信、ユーザーアクション起点のイベント駆動ジョブという3つの実践と、デプロイやモニタリングまで扱います。

Trigger.devバックグラウンドジョブ自動化13Rork585Supabase37プッシュ通知20

取り組みの背景 — なぜモバイルアプリにバックグラウンドジョブが必要なのか

モバイルアプリを開発していると、ユーザーがアプリを操作していない時間帯にもサーバー側で処理を実行したい場面に遭遇します。定期的なデータ同期、スケジュールされたプッシュ通知の配信、外部APIからのコンテンツ自動更新など、こうしたバックグラウンドジョブはアプリの価値を大きく高める要素です。

Trigger.dev は、TypeScript で書けるオープンソースのバックグラウンドジョブプラットフォームです。Cron スケジューリング、Webhook トリガー、リトライ機構が標準で備わっており、Rork で作ったモバイルアプリのバックエンド処理を効率的に自動化できます。

ここではRork アプリと Trigger.dev を組み合わせて、実用的なバックグラウンドジョブを構築する方法をステップバイステップで解説します。

Trigger.dev の基本概念と Rork との相性

Trigger.dev は Node.js / TypeScript ベースのジョブスケジューラで、以下のような特徴を持っています。

  • Cron ジョブ: 「毎朝9時」「1時間ごと」など時間指定で自動実行
  • イベント駆動: Webhook や API 呼び出しをトリガーに実行
  • リトライ: 失敗時の自動再試行(回数・間隔をカスタマイズ可能)
  • ダッシュボード: 実行履歴やエラーログをWebUIで確認

Rork で開発したアプリは React Native + Expo ベースで動作し、バックエンドには Supabase や Firebase を利用するケースが一般的です。Trigger.dev はこれらのバックエンドサービスと連携しやすく、アプリ側のコードを変更せずにサーバーサイドの自動化処理を追加できる点が魅力です。

環境構築 — Trigger.dev プロジェクトのセットアップ

まず、Trigger.dev のプロジェクトを作成します。Rork アプリ本体とは別のリポジトリで管理するのがおすすめです。

# Trigger.dev CLIのインストール
npm install -g @trigger.dev/cli
 
# プロジェクトの初期化
mkdir my-rork-app-jobs && cd my-rork-app-jobs
npx trigger.dev@latest init
 
# 依存パッケージのインストール
npm install @trigger.dev/sdk @supabase/supabase-js

初期化が完了すると、trigger.config.ts と src/trigger/ ディレクトリが生成されます。ジョブの定義はすべて src/trigger/ 配下に配置します。

プロジェクト構成

my-rork-app-jobs/
├── trigger.config.ts       # Trigger.dev設定
├── src/
│   └── trigger/
│       ├── scheduled/      # Cronジョブ
│       │   ├── daily-digest.ts
│       │   └── content-sync.ts
│       ├── events/         # イベント駆動ジョブ
│       │   ├── user-signup.ts
│       │   └── purchase-complete.ts
│       └── utils/
│           ├── supabase.ts # Supabaseクライアント
│           └── push.ts     # プッシュ通知ヘルパー
├── package.json
└── .env                    # 環境変数(APIキー等)

実践1 — コンテンツ自動更新ジョブの実装

ニュースアプリやキュレーションアプリでは、外部APIから定期的にコンテンツを取得してデータベースに保存する処理が必要です。以下は、1時間ごとに外部APIからデータを取得し、Supabase に保存するジョブの実装例です。

// src/trigger/scheduled/content-sync.ts
import { schedules } from "@trigger.dev/sdk/v3";
import { createClient } from "@supabase/supabase-js";
 
// Supabaseクライアントの初期化
const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_KEY!
);
 
// 1時間ごとにコンテンツを同期するジョブ
export const contentSyncJob = schedules.task({
  id: "content-sync",
  // 毎時0分に実行
  cron: "0 * * * *",
  run: async () => {
    console.log("コンテンツ同期を開始します...");
 
    // 外部APIからデータを取得
    const response = await fetch("https://api.example.com/articles", {
      headers: {
        Authorization: `Bearer ${process.env.CONTENT_API_KEY}`,
      },
    });
    const articles = await response.json();
 
    // Supabaseにupsert(存在すれば更新、なければ挿入)
    const { data, error } = await supabase
      .from("articles")
      .upsert(
        articles.map((article: any) => ({
          external_id: article.id,
          title: article.title,
          body: article.body,
          thumbnail_url: article.image,
          published_at: article.publishedAt,
          updated_at: new Date().toISOString(),
        })),
        { onConflict: "external_id" }
      );
 
    if (error) {
      // エラー時はTrigger.devが自動リトライ
      throw new Error(`Supabase upsert failed: ${error.message}`);
    }
 
    return {
      syncedCount: articles.length,
      timestamp: new Date().toISOString(),
    };
    // 期待する出力例:
    // { syncedCount: 25, timestamp: "2026-03-30T05:00:00.000Z" }
  },
});

このジョブが動作すると、アプリ側では Supabase のリアルタイムサブスクリプションを通じて、新しいコンテンツが自動的に画面に反映されます。

実践2 — スケジュール通知の自動配信

ユーザーごとにパーソナライズされたプッシュ通知を、毎朝決まった時刻に自動配信するジョブを構築します。

// src/trigger/scheduled/daily-digest.ts
import { schedules } from "@trigger.dev/sdk/v3";
import { createClient } from "@supabase/supabase-js";
 
const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_KEY!
);
 
export const dailyDigestJob = schedules.task({
  id: "daily-digest-notification",
  // 毎朝9時(JST)に実行 = UTC 0時
  cron: "0 0 * * *",
  run: async () => {
    // 通知を有効にしているユーザーを取得
    const { data: users } = await supabase
      .from("user_preferences")
      .select("user_id, push_token, preferred_categories")
      .eq("daily_digest_enabled", true)
      .not("push_token", "is", null);
 
    if (!users || users.length === 0) {
      return { sent: 0, message: "対象ユーザーなし" };
    }
 
    // 過去24時間の人気記事を取得
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1);
 
    const { data: trending } = await supabase
      .from("articles")
      .select("id, title, category")
      .gte("published_at", yesterday.toISOString())
      .order("view_count", { ascending: false })
      .limit(3);
 
    // Expo Push APIで一括送信
    const messages = users.map((user: any) => ({
      to: user.push_token,
      title: "今日のおすすめ記事",
      body: trending?.[0]?.title || "新しい記事が公開されました",
      data: { articleId: trending?.[0]?.id },
    }));
 
    const pushResponse = await fetch(
      "https://exp.host/--/api/v2/push/send",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(messages),
      }
    );
 
    return {
      sent: messages.length,
      timestamp: new Date().toISOString(),
    };
    // 期待する出力例:
    // { sent: 142, timestamp: "2026-03-30T00:00:00.000Z" }
  },
});

Rork アプリ側では expo-notifications でプッシュトークンを取得し、Supabase の user_preferences テーブルに保存しておくだけで、あとは Trigger.dev が自動的に通知を配信してくれます。プッシュ通知の高度なセグメンテーションや A/B テストについては、Rork アプリのプッシュ通知マスタープラン でさらに詳しく解説しています。

実践3 — イベント駆動ジョブ(ユーザーアクション起点)

Cron ジョブだけでなく、ユーザーのアクション(会員登録、購入完了など)をトリガーにジョブを実行することもできます。

// src/trigger/events/user-signup.ts
import { task } from "@trigger.dev/sdk/v3";
import { createClient } from "@supabase/supabase-js";
 
const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_KEY!
);
 
// ユーザー登録時のウェルカムシーケンス
export const userSignupJob = task({
  id: "user-signup-welcome",
  // 最大3回リトライ、間隔は指数関数的に増加
  retry: {
    maxAttempts: 3,
    factor: 2,
    minTimeoutInMs: 1000,
    maxTimeoutInMs: 30000,
  },
  run: async (payload: { userId: string; email: string }) => {
    // 1. ウェルカムメール送信
    await sendWelcomeEmail(payload.email);
 
    // 2. 初期おすすめコンテンツの生成
    const { data: popular } = await supabase
      .from("articles")
      .select("id")
      .order("view_count", { ascending: false })
      .limit(5);
 
    await supabase.from("user_recommendations").insert(
      (popular || []).map((article: any) => ({
        user_id: payload.userId,
        article_id: article.id,
        reason: "popular",
      }))
    );
 
    // 3. 分析イベントの記録
    await supabase.from("analytics_events").insert({
      event_type: "user_onboarded",
      user_id: payload.userId,
      metadata: { source: "trigger.dev" },
    });
 
    return { success: true, userId: payload.userId };
  },
});
 
async function sendWelcomeEmail(email: string) {
  // メール送信ロジック(Resend, SendGrid等)
  console.log(`ウェルカムメール送信: ${email}`);
}

Rork アプリからこのジョブをトリガーするには、Supabase Edge Functions 経由で Trigger.dev の API を呼び出します。

// Supabase Edge Function: functions/on-signup/index.ts
import { tasks } from "@trigger.dev/sdk/v3";
 
Deno.serve(async (req) => {
  const { userId, email } = await req.json();
 
  // Trigger.dev ジョブをキック
  await tasks.trigger("user-signup-welcome", {
    userId,
    email,
  });
 
  return new Response(JSON.stringify({ ok: true }), {
    headers: { "Content-Type": "application/json" },
  });
});

デプロイとモニタリング

Trigger.dev のデプロイは非常にシンプルです。

# 本番環境へデプロイ
npx trigger.dev@latest deploy
 
# ローカルでの開発・テスト
npx trigger.dev@latest dev

デプロイ後は Trigger.dev のダッシュボード(https://cloud.trigger.dev)からジョブの実行状況をリアルタイムで確認できます。

  • 実行履歴: 各ジョブの成功/失敗、実行時間、出力を確認
  • リトライ状況: 失敗したジョブの自動リトライ回数と結果
  • アラート: Slack や Discord への失敗通知を設定可能

アプリの運用を自動化し、障害時にも迅速に気づける体制を整えたい方には、モバイルアプリの自律運用アーキテクチャ の記事もおすすめです。

よくあるエラーと対処法

タイムアウトエラー

Trigger.dev のデフォルトタイムアウトは 60 秒です。外部API呼び出しが遅い場合は maxDuration を設定しましょう。

export const longRunningJob = schedules.task({
  id: "long-running-sync",
  cron: "0 3 * * *",
  // タイムアウトを5分に延長
  maxDuration: 300,
  run: async () => {
    // 時間のかかる処理
  },
});

環境変数の未設定

Trigger.dev ダッシュボードの「Environment Variables」から、SUPABASE_URL、SUPABASE_SERVICE_KEY などの環境変数を設定してください。.env ファイルはローカル開発用であり、本番環境には反映されません。

リトライが多発する場合

外部APIのレートリミットに引っかかっている可能性があります。retry の factor を大きくして間隔を広げるか、バッチサイズを小さくして対応します。

まとめ

Trigger.dev を活用することで、Rork で開発したモバイルアプリに強力なバックグラウンドジョブ機能を追加できます。コンテンツの自動更新、パーソナライズ通知の配信、ユーザーアクション起点の自動処理など、アプリの価値を高める自動化をサーバーサイドで実現できるのが最大のメリットです。

TypeScript で書けるため、React Native / Expo を使った Rork アプリの開発者にとって学習コストが低く、ダッシュボードによるモニタリングも充実しています。まずは無料プランで小さなジョブから始めて、アプリの運用効率を体験してみてください。

シェア

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

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

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

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ 開発ツール2026-07-17
許可を聞かずに通知を届ける — Rork アプリの provisional 認可と、Expo の定番コードが握りつぶすもの
iOS には許可ダイアログを出さずに通知を配り始める provisional 認可があります。ところが Expo 公式が載せている定番の登録コードをそのまま使うと、provisional で通った端末が「拒否」として扱われ、避けたかったはずのダイアログが出ます。granted が false になる仕組みから、5値で許可状態を扱うフック、静かな配信を前提にした通知設計、昇格を求める判断、計測の分離までを実装込みで整理しました。
⬡ 開発ツール2026-05-01
Rork アプリに Notification Service Extension を後付けする — 画像付き通知・暗号化ペイロード復号・動的書き換えの実装メモ
Rork で作った iOS アプリに Notification Service Extension を後付けし、画像付き通知・E2E暗号化ペイロードの復号・APNs ペイロードの動的書き換えを本番品質で動かすまでの設計と、実際に踏んだ落とし穴を、動作する Swift コードとともにまとめました。
⬡ 開発ツール2026-10-15
EXPO_PUBLIC_ の値が、開発では読めて本番だけ undefined になる書き方を、変換結果で見分けます
API の URL が開発中は読めるのに、ビルドした途端に undefined になる。原因は process.env の書き方でした。babel-preset-expo の変換結果を開発・本番で並べ、置き換わる書き方と残ってしまう書き方を表にしました。