取り組みの背景 — なぜモバイルアプリにバックグラウンドジョブが必要なのか
モバイルアプリを開発していると、ユーザーがアプリを操作していない時間帯にもサーバー側で処理を実行したい場面に遭遇します。定期的なデータ同期、スケジュールされたプッシュ通知の配信、外部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 アプリの開発者にとって学習コストが低く、ダッシュボードによるモニタリングも充実しています。まずは無料プランで小さなジョブから始めて、アプリの運用効率を体験してみてください。