◉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 — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/Rork入門
◈ Rork入門/2026-04-05中級

Rork でマッチングアプリを作る:スワイプUIとチャット機能の実装チュートリアル

Rorkでマッチングアプリの核となる機能を作るチュートリアルです。いいねとスキップのスワイプUI、Supabaseを使ったマッチング判定、リアルタイムチャット、プロフィール画像のアップロードまで、よくあるエラーの対処も交えて実装します。コーディング経験が浅くても進められる構成です。

マッチングアプリスワイプUISupabase37React Native239チュートリアル23

マッチングアプリは、現代で最も需要の高いアプリカテゴリーのひとつです。「いいね」「スキップ」のスワイプ操作、マッチング通知、そしてリアルタイムチャット——これらを個人開発で実現するのは難しそうに感じるかもしれません。しかし、Rork を使えば、コーディング経験が少なくてもマッチングアプリの核となる機能を構築できます。

このチュートリアルで作るもの

  • ユーザープロフィールカードのスワイプUI(右スワイプ=いいね、左スワイプ=スキップ)
  • 相互にいいねした際のマッチング判定
  • マッチング相手とのリアルタイムチャット(Supabase Realtime)
  • プロフィール画像のアップロード(Supabase Storage)

前提知識と環境準備

このチュートリアルを進めるには、以下が必要です。

  • Rork アカウント(無料プランでも開始可能)
  • Supabase アカウント(無料枠あり)
  • React Native の基本的な理解(必須ではないが、あると理解が深まります)

Rork の基本的な使い方がまだわからない方は、まず Rork 入門:ゼロからアプリを作る方法 を先にご覧ください。

Supabase プロジェクトの初期設定

Supabase でデータベースを構築します。マッチングアプリに必要なテーブルは次の3つです。

  • profiles:ユーザープロフィール(名前、年齢、自己紹介、画像URL)
  • swipes:スワイプの記録(誰が誰を「いいね」or「スキップ」したか)
  • matches:マッチしたペアの記録
  • messages:チャットメッセージ

以下の SQL を Supabase の SQL Editor で実行してください。

-- プロフィールテーブル
create table profiles (
  id uuid references auth.users primary key,
  name text not null,
  age integer,
  bio text,
  avatar_url text,
  created_at timestamptz default now()
);
 
-- スワイプ記録テーブル
create table swipes (
  id uuid default gen_random_uuid() primary key,
  swiper_id uuid references profiles(id),
  swiped_id uuid references profiles(id),
  direction text check (direction in ('like', 'pass')),
  created_at timestamptz default now(),
  unique(swiper_id, swiped_id)
);
 
-- マッチテーブル
create table matches (
  id uuid default gen_random_uuid() primary key,
  user1_id uuid references profiles(id),
  user2_id uuid references profiles(id),
  created_at timestamptz default now()
);
 
-- メッセージテーブル
create table messages (
  id uuid default gen_random_uuid() primary key,
  match_id uuid references matches(id),
  sender_id uuid references profiles(id),
  body text not null,
  created_at timestamptz default now()
);
 
-- Realtime を有効にする
alter publication supabase_realtime add table messages;

実行後、Supabase ダッシュボードの「Authentication」→「Policies」でそれぞれのテーブルに適切な Row Level Security(RLS)ポリシーを設定します。

スワイプUIの実装

Rork にプロジェクトを作成したら、スワイプUIのプロンプトを入力します。Rork はプロンプトから React Native のコードを生成してくれます。

Rork へのプロンプト例

マッチングアプリのスワイプ画面を作ってください。
- 画面中央にプロフィールカード(写真、名前、年齢、自己紹介)を表示
- 右スワイプで「いいね」、左スワイプで「スキップ」
- React Native Gesture Handler を使ったスムーズなアニメーション
- カード下部に「❌ スキップ」「❤️ いいね」ボタンも配置
- Supabase から profiles テーブルのデータを取得して表示

Rork が生成するコードの骨格はこのようになります。

// screens/SwipeScreen.tsx(Rork 生成コードの例)
import { PanGestureHandler } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  runOnJS,
} from 'react-native-reanimated';
 
export function SwipeScreen() {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
 
  const gestureHandler = useAnimatedGestureHandler({
    onActive: (event) => {
      translateX.value = event.translationX;
      translateY.value = event.translationY;
    },
    onEnd: (event) => {
      if (event.translationX > 100) {
        // 右スワイプ:いいね
        runOnJS(handleLike)();
      } else if (event.translationX < -100) {
        // 左スワイプ:スキップ
        runOnJS(handlePass)();
      }
      translateX.value = withSpring(0);
      translateY.value = withSpring(0);
    },
  });
 
  return (
    <PanGestureHandler onGestureEvent={gestureHandler}>
      <Animated.View style={[styles.card, animatedStyle]}>
        {/* プロフィールカードの内容 */}
      </Animated.View>
    </PanGestureHandler>
  );
}

マッチング判定ロジック

スワイプ後、Supabase に記録して相互いいねを確認します。

// utils/swipe.ts
import { supabase } from './supabase';
 
export async function handleSwipe(
  swiperId: string,
  swipedId: string,
  direction: 'like' | 'pass'
) {
  // スワイプを記録
  await supabase.from('swipes').insert({
    swiper_id: swiperId,
    swiped_id: swipedId,
    direction,
  });
 
  if (direction === 'like') {
    // 相手からのいいねが既にあるか確認
    const { data } = await supabase
      .from('swipes')
      .select('id')
      .eq('swiper_id', swipedId)
      .eq('swiped_id', swiperId)
      .eq('direction', 'like')
      .single();
 
    if (data) {
      // 相互いいね → マッチング成立
      await supabase.from('matches').insert({
        user1_id: swiperId,
        user2_id: swipedId,
      });
      return { matched: true };
    }
  }
 
  return { matched: false };
}

マッチング成立時は「マッチしました!」のモーダルを表示し、チャット画面へ誘導します。

リアルタイムチャットの実装

マッチングが成立したら、相手とのチャットが解放されます。Supabase Realtime を使うことで、メッセージがリアルタイムに双方の画面に届く仕組みを実現できます。

// screens/ChatScreen.tsx
import { useEffect, useState } from 'react';
import { supabase } from '../utils/supabase';
 
export function ChatScreen({ matchId }: { matchId: string }) {
  const [messages, setMessages] = useState([]);
 
  useEffect(() => {
    // 既存メッセージを取得
    fetchMessages();
 
    // Realtime サブスクリプション
    const channel = supabase
      .channel(`match:${matchId}`)
      .on(
        'postgres_changes',
        {
          event: 'INSERT',
          schema: 'public',
          table: 'messages',
          filter: `match_id=eq.${matchId}`,
        },
        (payload) => {
          // 新メッセージをリアルタイム反映
          setMessages((prev) => [...prev, payload.new]);
        }
      )
      .subscribe();
 
    return () => supabase.removeChannel(channel);
  }, [matchId]);
 
  const sendMessage = async (body: string) => {
    await supabase.from('messages').insert({
      match_id: matchId,
      sender_id: currentUserId,
      body,
    });
    // Realtime で自動的に相手側に届く
  };
 
  return (
    // チャット UI
  );
}

チャット機能をさらに深掘りしたい方は、Rork でリアルタイムチャットアプリを構築する完全ガイド も参考にしてください。

プロフィール画像のアップロード

ユーザーが自分の写真を登録できる機能も重要です。Supabase Storage を使います。

// utils/uploadAvatar.ts
import * as ImagePicker from 'expo-image-picker';
import { supabase } from './supabase';
import { decode } from 'base64-arraybuffer';
 
export async function uploadAvatar(userId: string): Promise<string | null> {
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true,
    aspect: [1, 1], // 正方形にトリミング
    quality: 0.8,
    base64: true,
  });
 
  if (result.canceled || !result.assets[0].base64) return null;
 
  const fileName = `avatars/${userId}-${Date.now()}.jpg`;
 
  const { error } = await supabase.storage
    .from('profiles')
    .upload(fileName, decode(result.assets[0].base64), {
      contentType: 'image/jpeg',
      upsert: true,
    });
 
  if (error) throw error;
 
  const { data } = supabase.storage.from('profiles').getPublicUrl(fileName);
  return data.publicUrl;
}

よくあるエラーと対処法

マッチングアプリ開発で遭遇しやすいエラーとその対処法を紹介します。

RLS ポリシーエラー(403 Forbidden)

Supabase で「permission denied for table swipes」などのエラーが出た場合は、RLS ポリシーが未設定か、設定が不正確です。Supabase ダッシュボードの「Authentication → Policies」で、ログイン中のユーザーが自分のデータを読み書きできるポリシーを追加してください。

スワイプアニメーションがカクつく

react-native-reanimated の runOnUI / runOnJS の呼び分けを確認してください。UI スレッドで実行すべき処理を JS スレッドで行うとカクつきの原因になります。

Supabase Realtime が機能しない

alter publication supabase_realtime add table messages; の SQL を実行しているか確認してください。これを忘れると Realtime のイベントが発火しません。

全体を振り返って

Rork を使えば、スワイプUI・マッチング判定・リアルタイムチャットという、マッチングアプリの核心機能を比較的短期間で構築できます。まずは Supabase でデータ設計を固め、Rork にプロンプトを入力しながらUIを組み上げていくのが最も効率的なアプローチです。

マッチングアプリはプッシュ通知・位置情報・プレミアム課金など、拡張要素も豊富です。MVP(最小限のプロダクト)から始めて、ユーザーの反応を見ながら機能を追加していきましょう。

個人開発でアプリをリリースして収益化する方法については、React Native × Expo アーキテクチャ完全ガイド も参考にしてください。

シェア

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

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

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

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

関連記事

◈ Rork 入門2026-09-14
Rork の新規作成から Expo が消えました。手元のプロジェクトを続けるか、SwiftUI へ作り直すか
Rork では Expo(React Native)の新規プロジェクトを作れなくなりました。既存の Expo アプリは動き続けます。続けるか SwiftUI へ作り直すかを、公式ドキュメントの記述と、作り直しで実際に抜け落ちる場所から判断する材料をまとめます。
◈ Rork 入門2026-09-08
Rork Max が要るかどうかは、料金表ではなく権限の棚卸しで判断できます
Rork Max に上げるべきか迷ったとき、料金表を先に見ないようにしています。app.json と Info.plist から実際に要求している OS 機能を抜き出し、標準版で届くもの・設定で届くもの・ネイティブが要るものへ仕分ける手順を書き残します。
◈ Rork 入門2026-10-16
Play Console の提出ボタンが押せない夜 — リリースノートは1言語500文字でした
Google Play のリリースノートは1言語あたり500文字までです。長い更新メモをそのまま貼って提出が止まった経験から、短い版の作り方と、下書きリリースが残っているときの進め方をまとめました。