mcpField
T
telegram-mini-apps-react

telegram-mini-apps-react

github.com/nailorsh開発045 回インストール23 週間前に更新
リポジトリ

telegram-mini-apps-react について

この Skill は、`@tma.js/sdk-react` で Telegram Mini Apps を開発する React 開発者向けのガイドです。Telegram の UI、ユーザーデータと起動パラメーター、テーマ、ビューポート、Telegram 外でのローカル開発を扱います。

主な機能

  1. 01React SDK の初期化と利用
  2. 02Telegram ネイティブ UI の統合
  3. 03初期データと起動パラメーターの取得
  4. 04テーマとビューポートの CSS 変数設定
  5. 05ローカル開発用 Telegram 環境のモック
  6. 06ディープリンクとプラットフォーム機能の確認

活用シーン

  1. 01Telegram 向け React Mini App の開発
  2. 02Telegram のテーマとセーフエリアに合わせたレイアウト調整
  3. 03Telegram 外でのアプリ動作テスト
  4. 04起動パラメーターを使ったアプリ内遷移

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/nailorsh/agents_utils --skill telegram-mini-apps-react

インストール後、Claude Code で/telegram-mini-apps-reactを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

examples/
back-button-modal.tsx
page-component.tsx
user-data.tsx
references/
deeplink.md
init.md
mock-env.md

詳細

React を使用した Telegram Mini Apps

このスキルでは、React と @tma.js/sdk-react パッケージを使用して Telegram Mini Apps を構築するための包括的なガイダンスを提供します。

概要

Telegram Mini Apps は、Telegram の WebView 内に表示されるウェブアプリケーションです。Telegram のネイティブ UI コンポーネント(戻るボタン、メインボタン)と連携し、ユーザーデータ、テーマパラメーター、プラットフォーム固有の機能を利用できます。

主な概念:

  • Mini Apps は Telegram ボットのアドオンです
  • Telegram の WebView 内で動作します
  • イベントとメソッドを介して Telegram クライアントと通信します
  • 起動パラメーター、初期化データ、テーマ設定を利用できます

クイックスタート

1. インストール
# For React projects, install the React-specific package
pnpm i @tma.js/sdk-react

# DO NOT install both @tma.js/sdk and @tma.js/sdk-react - this causes bugs!

重要: @tma.js/sdk-react パッケージは @tma.js/sdk を完全に再エクスポートするため、個別にインストールする必要はありません。

2. 新しいプロジェクトの作成(任意)
pnpm dlx @tma.js/create-mini-app@latest
# or
npx @tma.js/create-mini-app@latest

この CLI は、適切な設定を備えたプロジェクト一式を生成します。

プロジェクト構成

一般的な Telegram Mini App の React プロジェクト構成:

src/
├── main.tsx          # Entry point - SDK initialization
├── init.ts           # SDK configuration and component mounting
├── mockEnv.ts        # Development environment mocking
├── App.tsx           # Main React app with routing
├── components/
│   ├── Page.tsx      # Page wrapper with back button handling
│   └── EnvUnsupported.tsx  # Fallback for non-TG environments
├── hooks/
│   └── useDeeplink.ts      # Deep linking handler
└── services/
    └── analytics.ts        # Analytics with user data

主な概念

SDK の初期化

機能を使用する前に SDK を初期化する必要があります。詳しい実装については、references/init.md を参照してください。

import { 
  init as initSDK,
  setDebug,
  themeParams,
  miniApp,
  viewport,
  backButton,
  swipeBehavior,
  initData
} from '@tma.js/sdk-react';

export async function init(options: {
  debug: boolean;
  eruda: boolean;
  mockForMacOS: boolean;
}): Promise<void> {
  // Enable debug mode for development
  setDebug(options.debug);
  
  // Initialize the SDK (REQUIRED before using any features)
  initSDK();

  // Mount components you'll use in the app
  backButton.mount.ifAvailable();
  initData.restore();

  // Configure swipe behavior
  if (swipeBehavior.isSupported()) {
    swipeBehavior.mount();
    swipeBehavior.disableVertical();
  }

  // Setup Mini App theming
  if (miniApp.mount.isAvailable()) {
    themeParams.mount();
    miniApp.mount();
    themeParams.bindCssVars();  // Binds theme to CSS variables
  }

  // Configure viewport
  if (viewport.mount.isAvailable()) {
    viewport.mount().then(() => {
      viewport.bindCssVars();
      viewport.requestFullscreen();
    });
  }
}
エントリーポイント(main.tsx)
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { retrieveLaunchParams } from '@tma.js/sdk-react';
import { init } from './init';
import App from "./App";
import { EnvUnsupported } from "./components/EnvUnsupported";

// Mock environment for local development
import './mockEnv';

const root = ReactDOM.createRoot(document.getElementById('root')!);

try {
  const launchParams = retrieveLaunchParams();
  const { tgWebAppPlatform: platform } = launchParams;
  const debug = (launchParams.tgWebAppStartParam || '').includes('debug')
    || import.meta.env.DEV;

  await init({
    debug,
    eruda: debug && ['ios', 'android'].includes(platform),
    mockForMacOS: platform === 'macos',
  }).then(() => {
    root.render(
      <StrictMode>
        <App/>
      </StrictMode>,
    );
  });
} catch (e) {
  // Show fallback UI when not in Telegram
  root.render(<EnvUnsupported/>);
}
戻るボタンの使用

戻るボタンは、ヘッダーに表示される Telegram のネイティブ UI 要素です。

import { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { backButton, miniApp } from '@tma.js/sdk-react';

export function Page({ children, back = true }) {
  const navigate = useNavigate();
  const location = useLocation();

  useEffect(() => {
    if (back) {
      backButton.show();
      
      // onClick returns a cleanup function
      return backButton.onClick(() => {
        const isDeeplink = location.state?.fromDeeplink;
        const isFirstPage = !window.history.state || window.history.state.idx === 0;

        if (isDeeplink || isFirstPage) {
          miniApp.close();  // Close the Mini App
        } else {
          navigate(-1);     // Go back in history
        }
      });
    }
    
    backButton.hide();
  }, [back, navigate, location]);

  return <>{children}</>;
}
シグナルの使用(useSignal フック)

シグナルは、自動的に更新されるリアクティブな値です。React でシグナルを購読するには、useSignal を使用します:

import { useEffect } from 'react';
import { backButton, useSignal } from '@tma.js/sdk-react';

function BackButtonStatus() {
  const isVisible = useSignal(backButton.isVisible);

  useEffect(() => {
    console.log('Back button is', isVisible ? 'visible' : 'hidden');
  }, [isVisible]);

  return null;
}
初期化データの取得(ユーザー情報)

初期化データにはユーザー情報が含まれており、認証に使用できます:

import { initData } from '@tma.js/sdk-react';

function getUserId(): number | undefined {
  try {
    const user = initData.user();
    return user?.id;
  } catch (e) {
    return undefined;
  }
}

// Get start parameter (for deep linking)
const startParam = initData.startParam();
起動パラメーター

起動パラメーターには、プラットフォーム情報、テーマ、アプリデータが含まれます:

import { retrieveLaunchParams, useLaunchParams } from '@tma.js/sdk-react';

// In component
function Component() {
  const launchParams = useLaunchParams();
  // launchParams.tgWebAppPlatform - 'ios', 'android', 'macos', 'tdesktop', 'web', 'weba'
  // launchParams.tgWebAppVersion - SDK version supported by client
  // launchParams.tgWebAppData - init data
  // launchParams.tgWebAppThemeParams - theme colors
  // launchParams.tgWebAppStartParam - custom start parameter
}

// Outside component
const launchParams = retrieveLaunchParams();
テーマ設定

テーマパラメーターは Telegram によって自動的に提供されます。CSS 変数にバインドします:

import { themeParams, miniApp } from '@tma.js/sdk-react';

// During initialization
if (miniApp.mount.isAvailable()) {
  themeParams.mount();
  miniApp.mount();
  themeParams.bindCssVars();  // Creates CSS variables like --tg-theme-bg-color
}

バインド後に使用できる CSS 変数:

  • --tg-theme-bg-color
  • --tg-theme-text-color
  • --tg-theme-hint-color
  • --tg-theme-link-color
  • --tg-theme-button-color
  • --tg-theme-button-text-color
  • --tg-theme-secondary-bg-color
  • --tg-theme-header-bg-color
  • --tg-theme-accent-text-color
  • --tg-theme-section-bg-color
  • --tg-theme-section-header-text-color
  • --tg-theme-subtitle-text-color
  • --tg-theme-destructive-text-color
ビューポートとセーフエリア

適切なレイアウトになるよう、ビューポートとセーフエリアを処理します:

import { viewport } from '@tma.js/sdk-react';

if (viewport.mount.isAvailable()) {
  viewport.mount().then(() => {
    viewport.bindCssVars();        // Binds viewport dimensions to CSS
    viewport.requestFullscreen();   // Request fullscreen mode
  });
}

使用できる CSS 変数:

/* Safe area insets */
padding-top: var(--tg-viewport-safe-area-inset-top, 0);
padding-bottom: var(--tg-viewport-safe-area-inset-bottom, 0);

/* Content safe area (for notch, etc.) */
padding-top: var(--tg-viewport-content-safe-area-inset-top, 0);

/* Viewport dimensions */
height: var(--tg-viewport-height);
width: var(--tg-viewport-width);

CSS での使用方法:

.header {
  padding-top: max(2rem, calc(var(--tg-viewport-content-safe-area-inset-top, 0) + var(--tg-viewport-safe-area-inset-top, 0)));
}

.footer {
  padding-bottom: calc(1rem + var(--tg-viewport-safe-area-inset-bottom, 0));
}

開発環境のモック

Telegram 外でローカル開発する場合は、環境をモックします。詳しくは references/mock-env.md を参照してください。

import { emitEvent, isTMA, mockTelegramEnv } from '@tma.js/sdk-react';

if (import.meta.env.DEV) {
  if (!await isTMA('complete')) {
    const themeParams = {
      accent_text_color: '#6ab2f2',
      bg_color: '#17212b',
      button_color: '#5288c1',
      button_text_color: '#ffffff',
      destructive_text_color: '#ec3942',
      header_bg_color: '#17212b',
      hint_color: '#708499',
      link_color: '#6ab3f3',
      secondary_bg_color: '#232e3c',
      section_bg_color: '#17212b',
      section_header_text_color: '#6ab3f3',
      subtitle_text_color: '#708499',
      text_color: '#f5f5f5',
    };

    mockTelegramEnv({
      onEvent(e) {
        if (e.name === 'web_app_request_theme') {
          return emitEvent('theme_changed', { theme_params: themeParams });
        }
        if (e.name === 'web_app_request_viewport') {
          return emitEvent('viewport_changed', {
            height: window.innerHeight,
            width: window.innerWidth,
            is_expanded: true,
            is_state_stable: true,
          });
        }
        if (e.name === 'web_app_request_safe_area') {
          return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 });
        }
      },
      launchParams: new URLSearchParams([
        ['tgWebAppThemeParams', JSON.stringify(themeParams)],
        ['tgWebAppData', new URLSearchParams([
          ['auth_date', (Date.now() / 1000 | 0).toString()],
          ['hash', 'mock-hash'],
          ['signature', 'mock-signature'],
          ['user', JSON.stringify({ id: 1, first_name: 'Developer' })],
        ]).toString()],
        ['tgWebAppVersion', '8.4'],
        ['tgWebAppPlatform', 'tdesktop'],
      ]),
    });

    console.info('⚠️ Running in mocked Telegram environment');
  }
}

ディープリンク

ディープリンク用の start パラメーターを処理します。詳しくは references/deeplink.md を参照してください。

import { useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { initData } from "@tma.js/sdk-react";

export function useDeeplink() {
  const navigate = useNavigate();
  const processedRef = useRef(false);

  useEffect(() => {
    if (processedRef.current) return;

    const startParam = initData.startParam();
    if (!startParam) return;

    processedRef.current = true;

    try {
      // startParam is base64url encoded
      const base64 = startParam.replace(/-/g, '+').replace(/_/g, '/');
      const decoded = atob(base64);
      const params = new URLSearchParams(decoded);
      
      const route = params.get('route');
      if (route) {
        navigate(route, { replace: true, state: { fromDeeplink: true } });
      }
    } catch (e) {
      console.error("Failed to parse startParam:", e);
    }
  }, [navigate]);
}

ベストプラクティス

1. 必ず利用可能か確認する

メソッドを使用する前に、利用可能かどうかを確認します:

import { backButton } from '@tma.js/sdk-react';

// Option 1: Check before calling
if (backButton.show.isAvailable()) {
  backButton.show();
}

// Option 2: Call only if available (safer, no-op if unavailable)
backButton.show.ifAvailable();

// Option 3: Mount only if available
backButton.mount.ifAvailable();
2. 使用前にコンポーネントをマウントする

メソッドを使用する前に、コンポーネントをマウントする必要があります:

// ❌ Wrong - will throw error
backButton.show();

// ✅ Correct
backButton.mount();
backButton.show();
3. macOS の不具合に対処する

Telegram for macOS には既知の問題があります:

if (platform === 'macos') {
  mockTelegramEnv({
    onEvent(event, next) {
      if (event.name === 'web_app_request_theme') {
        const tp = themeParams.state() || retrieveLaunchParams().tgWebAppThemeParams;
        return emitEvent('theme_changed', { theme_params: tp });
      }
      if (event.name === 'web_app_request_safe_area') {
        return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 });
      }
      next();
    },
  });
}
4. SDK を重複してインストールしない

@tma.js/sdk と @tma.js/sdk-react の両方をインストールしないでください:

// ❌ Wrong - causes bugs
{
  "dependencies": {
    "@tma.js/sdk": "^3.0.0",
    "@tma.js/sdk-react": "^3.0.8"
  }
}

// ✅ Correct - only the React package
{
  "dependencies": {
    "@tma.js/sdk-react": "^3.0.8"
  }
}
5. 必要に応じてスワイプを無効にする

誤操作によるナビゲーションを防ぎます:

if (swipeBehavior.isSupported()) {
  swipeBehavior.mount();
  swipeBehavior.disableVertical();  // Prevents swipe-to-close
}

初期化データをサーバーに送信する

認証のため、初期化データをサーバーに送信します:

import { retrieveRawInitData } from '@tma.js/sdk-react';

const initDataRaw = retrieveRawInitData();

fetch('https://api.example.com/auth', {
  method: 'POST',
  headers: {
    Authorization: `tma ${initDataRaw}`,
  },
});

サーバー側での検証:

  • Node.js では @tma.js/init-data-node を使用します
  • ボットトークンを使ってハッシュを検証します
  • 検証せずに初期化データを信頼しないでください

対応プラットフォーム

Mini Apps は以下のプラットフォームで動作します:

  • android - Telegram for Android
  • ios - Telegram for iOS
  • macos - Telegram for macOS(一部不具合があります)
  • tdesktop - Telegram Desktop
  • weba - Telegram Web A
  • web - Telegram Web K

よくある問題

SDK が初期化されていないというエラー

SDK の機能を使用する前に init() を呼び出してください。

コンポーネントがマウントされていないというエラー

メソッドを呼び出す前にコンポーネントをマウントしてください:

backButton.mount();
backButton.show();
メソッドが利用できないというエラー

呼び出す前に利用可能かどうかを確認してください:

if (backButton.show.isAvailable()) {
  backButton.show();
}
Telegram 以外でアプリがクラッシュする

開発中は環境をモックし、フォールバック UI を用意してください。

追加リソース

関連項目

コメント